From 2ce8de72c3ace6cdbfe3bc04da8eb52a0aa84bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ronnie=20Andr=C3=A9=20Bj=C3=B8rvik=20Sletta?= Date: Wed, 5 Apr 2023 23:30:31 +0200 Subject: [PATCH 1/9] Fix typo --- src/database/repository.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/database/repository.ts b/src/database/repository.ts index 7b8d4b1..c692104 100644 --- a/src/database/repository.ts +++ b/src/database/repository.ts @@ -10,7 +10,7 @@ export class Repository { async getAllBookmark(): Promise { const res = this.db.exec(`select Text, ContentID, annotation, DateCreated from Bookmark where Text is not null;`) - const bookmakrs: Bookmark[] = [] + const bookmarks: Bookmark[] = [] res[0].values.forEach(row => { if (!(row[0] && row[1] && row[3])) { @@ -24,7 +24,7 @@ export class Repository { return } - bookmakrs.push({ + bookmarks.push({ text: row[0].toString().replace(/\s+/g, ' ').trim(), contentId: row[1].toString(), note: row[2]?.toString(), @@ -32,7 +32,7 @@ export class Repository { }) }); - return bookmakrs + return bookmarks } async getTotalBookmark(): Promise { From 901097ecd7c9ec21cdeec4508b2cbd8f087b70a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ronnie=20Andr=C3=A9=20Bj=C3=B8rvik=20Sletta?= Date: Thu, 6 Apr 2023 00:04:48 +0200 Subject: [PATCH 2/9] Fix typo --- src/template/template.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/template/template.ts b/src/template/template.ts index 792d9b7..77f42d6 100644 --- a/src/template/template.ts +++ b/src/template/template.ts @@ -5,11 +5,11 @@ export const defaultTemplate = ` ` export function applyTemplateTransformations( - rawTemaple: string, + rawTemplate: string, highlights: string, bookTitle: string, ): string { - return rawTemaple + return rawTemplate .replace( /{{\s*highlights\s*}}/gi, highlights, From 8934ac7e6f002ab71a390055055510c8ff012b5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ronnie=20Andr=C3=A9=20Bj=C3=B8rvik=20Sletta?= Date: Thu, 6 Apr 2023 02:25:16 +0200 Subject: [PATCH 3/9] Add support for keeping existing content --- README.md | 9 ++++ src/database/Highlight.test.ts | 71 ++++++++++++++++++++++------- src/database/Highlight.ts | 48 ++++++++++++++----- src/database/interfaces.ts | 1 + src/database/repository.ts | 28 +++++++----- src/modal/ExtractHighlightsModal.ts | 24 +++++++--- 6 files changed, 134 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 4f2e12c..e89c189 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,15 @@ bookTitle: {{title}} | highlights | Will get replaced with the extracted highlights. | `{{highlights}}` | | title | The title of the book | `{{title}}` | +## Highlight markers +The plugin uses comments as highlight markers, to enable support for keeping existing highlights. All content between these markers will be transferred to the updated file. + +``` +%%START-%% +...highlight + +%%END-%% +``` ## Helping Screenshots ![](./README_assets/step1.png) diff --git a/src/database/Highlight.test.ts b/src/database/Highlight.test.ts index ef420c0..75e9a4d 100644 --- a/src/database/Highlight.test.ts +++ b/src/database/Highlight.test.ts @@ -13,6 +13,7 @@ describe('HighlightService', async function () { before(async function () { const repo = {} repo.getContentByContentId = () => Promise.resolve({ + bookmarkId: "c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe", title: "Chapter Eight: Holden", contentId: "file:///mnt/onboard/Corey, James S.A_/Nemesis Games - James S.A. Corey.epub#(12)OEBPS/Text/ch09.html", bookTitle: "Nemesis Games", @@ -29,12 +30,13 @@ describe('HighlightService', async function () { before(async function () { const dateCreated = new Date(Date.UTC(2022, 7, 5, 20, 46, 41, 0)) const bookmark: Bookmark = { + bookmarkId: "c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe", text: "“I guess I can’t be. How do you prove a negative?”", contentId: "file:///mnt/onboard/Corey, James S.A_/Nemesis Games - James S.A. Corey.epub#(12)OEBPS/Text/ch09.html", note: '', dateCreated } - highlight = await service.createHilightFromBookmark(bookmark) + highlight = await service.createHighlightFromBookmark(bookmark) dateCreatedText = moment(dateCreated).format("") }) @@ -52,7 +54,10 @@ describe('HighlightService', async function () { chai.assert.deepEqual( markdown, `## Chapter Eight: Holden -> “I guess I can’t be. How do you prove a negative?” — [[` + dateCreatedText + `]]` +%%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% +> “I guess I can’t be. How do you prove a negative?” — [[` + dateCreatedText + `]] + +%%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) }) @@ -70,7 +75,10 @@ describe('HighlightService', async function () { chai.assert.deepEqual( markdown, `## Chapter Eight: Holden -> “I guess I can’t be. How do you prove a negative?”` +%%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% +> “I guess I can’t be. How do you prove a negative?” + +%%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) }) @@ -88,8 +96,11 @@ describe('HighlightService', async function () { chai.assert.deepEqual( markdown, `## Chapter Eight: Holden +%%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > [!quote] -> “I guess I can’t be. How do you prove a negative?”` +> “I guess I can’t be. How do you prove a negative?” + +%%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) }) @@ -107,8 +118,11 @@ describe('HighlightService', async function () { chai.assert.deepEqual( markdown, `## Chapter Eight: Holden +%%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > [!quote] -> “I guess I can’t be. How do you prove a negative?” — [[` + dateCreatedText + `]]` +> “I guess I can’t be. How do you prove a negative?” — [[` + dateCreatedText + `]] + +%%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) }) @@ -126,8 +140,11 @@ describe('HighlightService', async function () { chai.assert.deepEqual( markdown, `## Chapter Eight: Holden +%%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > [!bug] -> “I guess I can’t be. How do you prove a negative?”` +> “I guess I can’t be. How do you prove a negative?” + +%%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) }) @@ -145,8 +162,11 @@ describe('HighlightService', async function () { chai.assert.deepEqual( markdown, `## Chapter Eight: Holden +%%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > [!bug] -> “I guess I can’t be. How do you prove a negative?” — [[` + dateCreatedText + `]]` +> “I guess I can’t be. How do you prove a negative?” — [[` + dateCreatedText + `]] + +%%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) }) }) @@ -159,12 +179,13 @@ describe('HighlightService', async function () { before(async function () { const dateCreated = new Date(Date.UTC(2022, 7, 5, 20, 46, 41, 0)) const bookmark: Bookmark = { + bookmarkId: "c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe", text: "“I guess I can’t be. How do you prove a negative?”", contentId: "file:///mnt/onboard/Corey, James S.A_/Nemesis Games - James S.A. Corey.epub#(12)OEBPS/Text/ch09.html", note: 'This is a great note!', dateCreated } - highlight = await service.createHilightFromBookmark(bookmark) + highlight = await service.createHighlightFromBookmark(bookmark) dateCreatedText = moment(dateCreated).format("") }) @@ -182,9 +203,12 @@ describe('HighlightService', async function () { chai.assert.deepEqual( markdown, `## Chapter Eight: Holden +%%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > “I guess I can’t be. How do you prove a negative?” -This is a great note! — [[` + dateCreatedText + `]]` +This is a great note! — [[` + dateCreatedText + `]] + +%%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) }) @@ -202,9 +226,12 @@ This is a great note! — [[` + dateCreatedText + `]]` chai.assert.deepEqual( markdown, `## Chapter Eight: Holden +%%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > “I guess I can’t be. How do you prove a negative?” -This is a great note!` +This is a great note! + +%%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) }) @@ -222,10 +249,13 @@ This is a great note!` chai.assert.deepEqual( markdown, `## Chapter Eight: Holden +%%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > [!quote] > “I guess I can’t be. How do you prove a negative?” >> [!note] -> This is a great note!` +> This is a great note! + +%%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) }) @@ -243,10 +273,13 @@ This is a great note!` chai.assert.deepEqual( markdown, `## Chapter Eight: Holden +%%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > [!quote] > “I guess I can’t be. How do you prove a negative?” >> [!note] -> This is a great note! — [[` + dateCreatedText + `]]` +> This is a great note! — [[` + dateCreatedText + `]] + +%%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) }) @@ -264,10 +297,13 @@ This is a great note!` chai.assert.deepEqual( markdown, `## Chapter Eight: Holden +%%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > [!quote] > “I guess I can’t be. How do you prove a negative?” >> [!bug] -> This is a great note!` +> This is a great note! + +%%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) }) @@ -285,10 +321,13 @@ This is a great note!` chai.assert.deepEqual( markdown, `## Chapter Eight: Holden +%%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > [!quote] > “I guess I can’t be. How do you prove a negative?” >> [!bug] -> This is a great note! — [[` + dateCreatedText + `]]` +> This is a great note! — [[` + dateCreatedText + `]] + +%%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) }) }) @@ -395,14 +434,14 @@ This is a great note!` }) for (const [id, content] of contentMap) { - it(`createHilightFromBookmark ${id}`, async function () { + it(`createHighlightFromBookmark ${id}`, async function () { const bookmark = await repo.getBookmarkById(id) if (!bookmark) { chai.assert.isNotNull(bookmark) return } - const highlight = await service.createHilightFromBookmark(bookmark) + const highlight = await service.createHighlightFromBookmark(bookmark) chai.assert.deepEqual(highlight, { content: content, bookmark: bookmark diff --git a/src/database/Highlight.ts b/src/database/Highlight.ts index bcfc507..f2e321c 100644 --- a/src/database/Highlight.ts +++ b/src/database/Highlight.ts @@ -4,7 +4,10 @@ import { Repository } from "./repository"; type bookTitle = string type chapter = string -type highlight = string +type bookmark = { + bookmarkId: string + content: string +} export class HighlightService { repo: Repository @@ -13,11 +16,29 @@ export class HighlightService { this.repo = repo } - fromMapToMarkdown(chapters: Map): string { + extractExistingHighlight(bookmarkId: string, existingFile: string): string { + // Define search terms + const startSearch = `%%START-${bookmarkId}%%` + const endSearch = `%%END-${bookmarkId}%%` + // Find substring indices + const start = existingFile.indexOf(startSearch) + const end = existingFile.indexOf(endSearch) + endSearch.length + 1 // Add length of search term to include it in substring extraction + // Return the extracted substring + return existingFile.substring(start, end) + } + + fromMapToMarkdown(chapters: Map, existingFile?: string): string { let markdown = ""; for (const [chapter, highlights] of chapters) { markdown += `## ${chapter.trim()}\n\n` - markdown += highlights.join('\n\n').trim() + //markdown += highlights.join('\n\n').trim() + markdown += highlights.map((highlight) => { + if (existingFile?.includes(highlight.bookmarkId)) { + return this.extractExistingHighlight(highlight.bookmarkId, existingFile) + } else { + return highlight.content + } + }).join('\n\n').trim() markdown += `\n\n` } @@ -31,15 +52,16 @@ export class HighlightService { includeCallouts: boolean, highlightCallout: string, annotationCallout: string, - ): Map> { - const m = new Map>() + ): Map> { + const m = new Map>() arr.forEach(x => { if (!x.content.bookTitle) { throw new Error("bookTitle must be set") } - let text = ``; + // Start annotation + let text = `%%START-${x.bookmark.bookmarkId}%%\n`; if (includeCallouts) { text += `> [!` + highlightCallout + `]\n` @@ -61,19 +83,21 @@ export class HighlightService { if (includeDate) { text += ` — [[${moment(x.bookmark.dateCreated).format(dateFormat)}]]` } + // End annotation + text += `\n\n%%END-${x.bookmark.bookmarkId}%%\n`; const existingBook = m.get(x.content.bookTitle) - + const highlight: bookmark = {bookmarkId: x.bookmark.bookmarkId, content: text} if (existingBook) { const existingChapter = existingBook.get(x.content.title) if (existingChapter) { - existingChapter.push(text) + existingChapter.push(highlight) } else { - existingBook.set(x.content.title, [text]) + existingBook.set(x.content.title, [highlight]) } } else { - m.set(x.content.bookTitle, new Map().set(x.content.title, [text])) + m.set(x.content.bookTitle, new Map().set(x.content.title, [highlight])) } }) @@ -85,7 +109,7 @@ export class HighlightService { const bookmarks = await this.repo.getAllBookmark() for (const bookmark of bookmarks) { - highlights.push(await this.createHilightFromBookmark(bookmark)) + highlights.push(await this.createHighlightFromBookmark(bookmark)) } return highlights.sort(function (a, b): number { @@ -98,7 +122,7 @@ export class HighlightService { }) } - async createHilightFromBookmark(bookmark: Bookmark): Promise { + async createHighlightFromBookmark(bookmark: Bookmark): Promise { let content = await this.repo.getContentByContentId(bookmark.contentId) if (content == null) { diff --git a/src/database/interfaces.ts b/src/database/interfaces.ts index f31c703..39c0ec1 100644 --- a/src/database/interfaces.ts +++ b/src/database/interfaces.ts @@ -1,4 +1,5 @@ export interface Bookmark { + bookmarkId: string, text: string; contentId: string; note?: string; diff --git a/src/database/repository.ts b/src/database/repository.ts index c692104..4fca0ad 100644 --- a/src/database/repository.ts +++ b/src/database/repository.ts @@ -9,26 +9,29 @@ export class Repository { } async getAllBookmark(): Promise { - const res = this.db.exec(`select Text, ContentID, annotation, DateCreated from Bookmark where Text is not null;`) + const res = this.db.exec(`select BookmarkID, Text, ContentID, annotation, DateCreated from Bookmark where Text is not null;`) const bookmarks: Bookmark[] = [] res[0].values.forEach(row => { - if (!(row[0] && row[1] && row[3])) { + if (!(row[0] && row[1] && row[2] && row[4])) { console.warn( "Skipping bookmark with invalid values", row[0], row[1], + row[2], row[3], + row[4], ) return } bookmarks.push({ - text: row[0].toString().replace(/\s+/g, ' ').trim(), - contentId: row[1].toString(), - note: row[2]?.toString(), - dateCreated: new Date(row[3].toString()) + bookmarkId: row[0].toString(), + text: row[1].toString().replace(/\s+/g, ' ').trim(), + contentId: row[2].toString(), + note: row[3]?.toString(), + dateCreated: new Date(row[4].toString()) }) }); @@ -43,7 +46,7 @@ export class Repository { async getBookmarkById(id: string): Promise { const statement = this.db.prepare( - `select Text, ContentID, annotation, DateCreated from Bookmark where BookmarkID = $id;`, + `select BookmarkID, Text, ContentID, annotation, DateCreated from Bookmark where BookmarkID = $id;`, { $id: id } @@ -55,15 +58,16 @@ export class Repository { const row = statement.get() - if (!(row[0] && row[1] && row[3])) { + if (!(row[0] && row[1] && row[2] && row[4])) { throw new Error("Bookmark column returned unexpected null") } return { - text: row[0].toString().replace(/\s+/g, ' ').trim(), - contentId: row[1].toString(), - note: row[2]?.toString(), - dateCreated: new Date(row[3].toString()) + bookmarkId: row[0].toString(), + text: row[1].toString().replace(/\s+/g, ' ').trim(), + contentId: row[2].toString(), + note: row[3]?.toString(), + dateCreated: new Date(row[4].toString()) } } diff --git a/src/modal/ExtractHighlightsModal.ts b/src/modal/ExtractHighlightsModal.ts index f0266c8..9ce1f26 100644 --- a/src/modal/ExtractHighlightsModal.ts +++ b/src/modal/ExtractHighlightsModal.ts @@ -52,13 +52,23 @@ export class ExtractHighlightsModal extends Modal { const template = await getTemplateContents(this.app, this.settings.templatePath) for (const [bookTitle, chapters] of content) { - const markdown = service.fromMapToMarkdown(chapters) - const saniizedBookName = sanitize(bookTitle) - const fileName = normalizePath(`${this.settings.storageFolder}/${saniizedBookName}.md`) - await this.app.vault.adapter.write( - fileName, - applyTemplateTransformations(template, markdown, bookTitle) - ) + const sanitizedBookName = sanitize(bookTitle) + const fileName = normalizePath(`${this.settings.storageFolder}/${sanitizedBookName}.md`) + // Check if file already exists + let existingFile; + try { + existingFile = await this.app.vault.adapter.read(fileName) + } catch (error) { + console.warn("Attempted to read file, but it does not already exist.") + } + const markdown = service.fromMapToMarkdown(chapters, existingFile) + + // Write file + await this.app.vault.adapter.write( + fileName, + applyTemplateTransformations(template, markdown, bookTitle) + ) + } } From cc65d0ce477fa8a23753e6550445f6e1cabc301b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ronnie=20Andr=C3=A9=20Bj=C3=B8rvik=20Sletta?= Date: Thu, 6 Apr 2023 11:22:02 +0200 Subject: [PATCH 4/9] Add test for import with existing files --- src/database/Highlight.test.ts | 58 ++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/database/Highlight.test.ts b/src/database/Highlight.test.ts index 75e9a4d..3f9732d 100644 --- a/src/database/Highlight.test.ts +++ b/src/database/Highlight.test.ts @@ -327,6 +327,64 @@ This is a great note! >> [!bug] > This is a great note! — [[` + dateCreatedText + `]] +%%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` + ) + }) + }) + + describe('Sample Bookmark with annotation, with existing highlights and added notes', async function(){ + let highlight: Highlight + let dateCreatedText: string + let existingFile: string + + before(async function () { + const dateCreated = new Date(Date.UTC(2022, 7, 5, 20, 46, 41, 0)) + const bookmark: Bookmark = { + bookmarkId: "c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe", + text: "“I guess I can’t be. How do you prove a negative?”", + contentId: "file:///mnt/onboard/Corey, James S.A_/Nemesis Games - James S.A. Corey.epub#(12)OEBPS/Text/ch09.html", + note: 'This is a great note!', + dateCreated + } + highlight = await service.createHighlightFromBookmark(bookmark) + dateCreatedText = moment(dateCreated).format("") + existingFile = `## Chapter Eight: Holden + +%%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% +> “I guess I can’t be. How do you prove a negative?” + +This is a great note! — [[` + dateCreatedText + `]] + +This is an exising note, added to the highlight. + +^325d95 + +%%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` + }) + + it('fromMaptoMarkdown with existing file', async function () { + const map = service + .convertToMap([highlight], true, "", false, '[!quote]', '[!note]') + .get(highlight.content.bookTitle ?? "") + + if(!map) { + chai.assert.isNotNull(map) + return + } + + const markdown = service.fromMapToMarkdown(map, existingFile) + chai.assert.deepEqual( + markdown, `## Chapter Eight: Holden + +%%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% +> “I guess I can’t be. How do you prove a negative?” + +This is a great note! — [[` + dateCreatedText + `]] + +This is an exising note, added to the highlight. + +^325d95 + %%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) }) From d93da0fd0b3a44d48efceccb7dad08bcd099ba2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ronnie=20Andr=C3=A9=20Bj=C3=B8rvik=20Sletta?= Date: Thu, 6 Apr 2023 11:30:44 +0200 Subject: [PATCH 5/9] Some cleanup work --- src/database/Highlight.ts | 5 ++--- src/database/{repositroy.test.ts => repository.test.ts} | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) rename src/database/{repositroy.test.ts => repository.test.ts} (97%) diff --git a/src/database/Highlight.ts b/src/database/Highlight.ts index f2e321c..71e980b 100644 --- a/src/database/Highlight.ts +++ b/src/database/Highlight.ts @@ -31,7 +31,6 @@ export class HighlightService { let markdown = ""; for (const [chapter, highlights] of chapters) { markdown += `## ${chapter.trim()}\n\n` - //markdown += highlights.join('\n\n').trim() markdown += highlights.map((highlight) => { if (existingFile?.includes(highlight.bookmarkId)) { return this.extractExistingHighlight(highlight.bookmarkId, existingFile) @@ -60,7 +59,7 @@ export class HighlightService { throw new Error("bookTitle must be set") } - // Start annotation + // Start annotation marker let text = `%%START-${x.bookmark.bookmarkId}%%\n`; if (includeCallouts) { @@ -83,7 +82,7 @@ export class HighlightService { if (includeDate) { text += ` — [[${moment(x.bookmark.dateCreated).format(dateFormat)}]]` } - // End annotation + // End annotation marker text += `\n\n%%END-${x.bookmark.bookmarkId}%%\n`; const existingBook = m.get(x.content.bookTitle) diff --git a/src/database/repositroy.test.ts b/src/database/repository.test.ts similarity index 97% rename from src/database/repositroy.test.ts rename to src/database/repository.test.ts index de0b7ea..a0486c4 100644 --- a/src/database/repositroy.test.ts +++ b/src/database/repository.test.ts @@ -2,7 +2,7 @@ import * as chai from 'chai'; import { readFileSync } from 'fs'; import SqlJs, { Database } from 'sql.js'; import { binary } from '../binaries/sql-wasm'; -import { Repository } from '../database/repository'; +import { Repository } from './repository'; describe('Repository', async function () { let db: Database From 62a3e20f1903657be64d087f22137dc6129ba08e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ronnie=20Andr=C3=A9=20Bj=C3=B8rvik=20Sletta?= Date: Thu, 6 Apr 2023 12:26:31 +0200 Subject: [PATCH 6/9] Add sort highlight by chapter progress --- src/database/Highlight.ts | 4 ++-- src/database/repository.ts | 9 +++++++-- src/modal/ExtractHighlightsModal.ts | 2 +- src/settings/Settings.ts | 30 +++++++++++++++++++++++------ 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/database/Highlight.ts b/src/database/Highlight.ts index 71e980b..96b9607 100644 --- a/src/database/Highlight.ts +++ b/src/database/Highlight.ts @@ -103,10 +103,10 @@ export class HighlightService { return m } - async getAllHighlight(): Promise { + async getAllHighlight(sortByChapterProgress?: boolean): Promise { const highlights: Highlight[] = [] - const bookmarks = await this.repo.getAllBookmark() + const bookmarks = await this.repo.getAllBookmark(sortByChapterProgress) for (const bookmark of bookmarks) { highlights.push(await this.createHighlightFromBookmark(bookmark)) } diff --git a/src/database/repository.ts b/src/database/repository.ts index 4fca0ad..6ac48f8 100644 --- a/src/database/repository.ts +++ b/src/database/repository.ts @@ -8,8 +8,13 @@ export class Repository { this.db = db } - async getAllBookmark(): Promise { - const res = this.db.exec(`select BookmarkID, Text, ContentID, annotation, DateCreated from Bookmark where Text is not null;`) + async getAllBookmark(sortByChapterProgress?: boolean): Promise { + let res + if (sortByChapterProgress) { + res = this.db.exec(`select BookmarkID, Text, ContentID, annotation, DateCreated, ChapterProgress from Bookmark where Text is not null order by ChapterProgress ASC, DateCreated ASC;`) + } else { + res = this.db.exec(`select BookmarkID, Text, ContentID, annotation, DateCreated, ChapterProgress from Bookmark where Text is not null order by DateCreated ASC;`) + } const bookmarks: Bookmark[] = [] res[0].values.forEach(row => { diff --git a/src/modal/ExtractHighlightsModal.ts b/src/modal/ExtractHighlightsModal.ts index 9ce1f26..aee96ad 100644 --- a/src/modal/ExtractHighlightsModal.ts +++ b/src/modal/ExtractHighlightsModal.ts @@ -41,7 +41,7 @@ export class ExtractHighlightsModal extends Modal { ) const content = service.convertToMap( - await service.getAllHighlight(), + await service.getAllHighlight(this.settings.sortByChapterProgress), this.settings.includeCreatedDate, this.settings.dateFormat, this.settings.includeCallouts, diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index d3a23cd..83deee5 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -7,6 +7,7 @@ export const DEFAULT_SETTINGS: KoboHighlightsImporterSettings = { storageFolder: '', includeCreatedDate: false, dateFormat: "YYYY-MM-DD", + sortByChapterProgress: false, templatePath: "", includeCallouts: true, highlightCallout: "quote", @@ -17,6 +18,7 @@ export interface KoboHighlightsImporterSettings { storageFolder: string; includeCreatedDate: boolean; dateFormat: string; + sortByChapterProgress: boolean; templatePath: string; includeCallouts: boolean, highlightCallout: string, @@ -34,8 +36,9 @@ export class KoboHighlightsImporterSettingsTab extends PluginSettingTab { this.add_destination_folder(); this.add_enable_creation_date(); - this.add_date_fromat(); + this.add_date_format(); this.add_template_path(); + this.add_sort_by_chapter_progress(); this.add_enable_callouts(); this.add_highlight_callouts_format(); this.add_annotation_callouts_format(); @@ -58,8 +61,8 @@ export class KoboHighlightsImporterSettingsTab extends PluginSettingTab { add_template_path(): void { new Setting(this.containerEl) - .setName('Tempalte Path') - .setDesc('Which tempalte to use for extracted highlights') + .setName('Template Path') + .setDesc('Which template to use for extracted highlights') .addSearch((cb) => { new FileSuggestor(this.app, cb.inputEl); cb.setPlaceholder("Example: folder1/template") @@ -76,15 +79,15 @@ export class KoboHighlightsImporterSettingsTab extends PluginSettingTab { .setName("Add creation date") .setDesc(`If the exported higlights should include '- [[${this.plugin.settings.dateFormat}]]'`) .addToggle((cb) => { - cb.setValue(this.plugin.settings.includeCreatedDate) + cb.setValue(this.plugin.settings.sortByChapterProgress) .onChange((toggle) => { - this.plugin.settings.includeCreatedDate = toggle; + this.plugin.settings.sortByChapterProgress = toggle; this.plugin.saveSettings(); }) }) } - add_date_fromat(): void { + add_date_format(): void { new Setting(this.containerEl) .setName("Date format") .setDesc("The format of date to use") @@ -98,6 +101,21 @@ export class KoboHighlightsImporterSettingsTab extends PluginSettingTab { }) } + add_sort_by_chapter_progress(): void { + const desc = document.createDocumentFragment(); + desc.append("Turn on to sort highlights by chapter progess. If turned off, highlights are sorted by creation date and time.") + + new Setting(this.containerEl) + .setName("Sort by chapter progress") + .setDesc(desc) + .addToggle((cb) => { + cb.setValue(this.plugin.settings.sortByChapterProgress) + .onChange((toggle) => { + this.plugin.settings.sortByChapterProgress = toggle; + this.plugin.saveSettings(); + }); + }) + } add_enable_callouts(): void { const desc = document.createDocumentFragment(); desc.append("When enabled Kobo highlights importer will make use of Obsidian callouts for highlights and annotations.", From 407ca8e864c4fe8f76222d2010f0063f651a0c11 Mon Sep 17 00:00:00 2001 From: Kevin Hellemun <17928966+OGKevin@users.noreply.github.com> Date: Thu, 11 Jan 2024 09:47:20 +0000 Subject: [PATCH 7/9] chore: fix failing tests --- package-lock.json | 51 ++++++++++++++++++++++++++++------ package.json | 4 ++- src/database/Highlight.test.ts | 10 +++++-- src/database/repository.ts | 2 +- 4 files changed, 54 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index a410f66..be91aaf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,13 +14,15 @@ "esbuild-plugin-wat": "0.2.7", "moment": "2.30.1", "sanitize-filename-ts": "1.0.2", - "sql.js": "1.8.0" + "sql.js": "1.8.0", + "uuid": "^9.0.1" }, "devDependencies": { "@types/chai": "4.3.11", "@types/mocha": "10.0.6", "@types/node": "20.10.6", "@types/sql.js": "1.4.9", + "@types/uuid": "^9.0.7", "@typescript-eslint/eslint-plugin": "6.17.0", "@typescript-eslint/parser": "6.17.0", "builtin-modules": "3.3.0", @@ -1291,6 +1293,12 @@ "@types/estree": "*" } }, + "node_modules/@types/uuid": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", + "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==", + "dev": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "6.17.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.17.0.tgz", @@ -3115,6 +3123,15 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", @@ -4501,10 +4518,13 @@ "integrity": "sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==" }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } @@ -5535,6 +5555,12 @@ "@types/estree": "*" } }, + "@types/uuid": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", + "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==", + "dev": true + }, "@typescript-eslint/eslint-plugin": { "version": "6.17.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.17.0.tgz", @@ -6827,6 +6853,14 @@ "p-map": "^3.0.0", "rimraf": "^3.0.0", "uuid": "^8.3.2" + }, + "dependencies": { + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + } } }, "istanbul-lib-report": { @@ -7836,10 +7870,9 @@ "integrity": "sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==" }, "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" }, "v8-compile-cache-lib": { "version": "3.0.1", diff --git a/package.json b/package.json index 58ee9fb..fbbaf19 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@types/mocha": "10.0.6", "@types/node": "20.10.6", "@types/sql.js": "1.4.9", + "@types/uuid": "^9.0.7", "@typescript-eslint/eslint-plugin": "6.17.0", "@typescript-eslint/parser": "6.17.0", "builtin-modules": "3.3.0", @@ -38,7 +39,8 @@ "esbuild-plugin-wat": "0.2.7", "moment": "2.30.1", "sanitize-filename-ts": "1.0.2", - "sql.js": "1.8.0" + "sql.js": "1.8.0", + "uuid": "^9.0.1" }, "engines": { "node": ">=18.0.0 <21.0.0" diff --git a/src/database/Highlight.test.ts b/src/database/Highlight.test.ts index 51cd406..86a7c38 100644 --- a/src/database/Highlight.test.ts +++ b/src/database/Highlight.test.ts @@ -3,6 +3,7 @@ import { Repository } from './repository'; import { HighlightService } from './Highlight'; import { Bookmark, Content, Highlight } from './interfaces'; import moment from 'moment'; +import {v4 as uuidv4} from 'uuid'; describe('HighlightService', async function () { @@ -408,13 +409,15 @@ This is an exising note, added to the highlight. before(async function () { const dateCreated = new Date(Date.UTC(2022, 7, 5, 20, 46, 41, 0)) + const bookmarkID = uuidv4() const bookmark: Bookmark = { + bookmarkId: bookmarkID, text: "“I guess I can’t be. How do you prove a negative?”", contentId: "missing-content-id", note: '', dateCreated } - highlight = await service.createHilightFromBookmark(bookmark) + highlight = await service.createHighlightFromBookmark(bookmark) dateCreatedText = moment(dateCreated).format("") }) @@ -432,7 +435,10 @@ This is an exising note, added to the highlight. chai.assert.deepEqual( markdown, `## Unknown Title -> “I guess I can’t be. How do you prove a negative?” — [[` + dateCreatedText + `]]` +%%START-` + highlight.bookmark.bookmarkId + `%% +> “I guess I can’t be. How do you prove a negative?” — [[` + dateCreatedText + `]] + +%%END-` + highlight.bookmark.bookmarkId + `%%` ) }) }) diff --git a/src/database/repository.ts b/src/database/repository.ts index ddd640e..18b3871 100644 --- a/src/database/repository.ts +++ b/src/database/repository.ts @@ -20,7 +20,7 @@ export class Repository { if (res[0].values == undefined) { console.warn("Bookmarks table returend no results, do you have any annotations created?") - return bookmakrs + return bookmarks } res[0].values.forEach(row => { From fe15fa3387ba3c7539527e32d45ce1bdab0421d6 Mon Sep 17 00:00:00 2001 From: Kevin Hellemun <17928966+OGKevin@users.noreply.github.com> Date: Thu, 11 Jan 2024 11:13:49 +0000 Subject: [PATCH 8/9] feat: add inner block that contains the extracted higlight. This allowes to add notes to the highlight, without overwriting the notes and keeping the higlight up to date. --- Earthfile | 2 +- src/database/Highlight.test.ts | 223 +++++++++++++++++++++++++++++---- src/database/Highlight.ts | 66 ++++++---- 3 files changed, 242 insertions(+), 49 deletions(-) diff --git a/Earthfile b/Earthfile index 7546c38..1b4981d 100644 --- a/Earthfile +++ b/Earthfile @@ -35,7 +35,7 @@ kobo-test-db: COPY +creds-aws/creds /root/.aws/credentials RUN aws --endpoint-url http://100.82.97.39:9000 s3 cp s3://repo-obsidian-kobo-highlights-import/KoboReader.sqlite KoboReader.sqlite - SAVE ARTIFACT KoboReader.sqlite + SAVE ARTIFACT KoboReader.sqlite AS LOCAL KoboReader.sqlite test: FROM +node diff --git a/src/database/Highlight.test.ts b/src/database/Highlight.test.ts index 86a7c38..0295410 100644 --- a/src/database/Highlight.test.ts +++ b/src/database/Highlight.test.ts @@ -1,9 +1,9 @@ import * as chai from 'chai'; -import { Repository } from './repository'; -import { HighlightService } from './Highlight'; -import { Bookmark, Content, Highlight } from './interfaces'; import moment from 'moment'; -import {v4 as uuidv4} from 'uuid'; +import { v4 as uuidv4 } from 'uuid'; +import { HighlightService, typeWhateverYouWantPlaceholder } from './Highlight'; +import { Bookmark, Content, Highlight } from './interfaces'; +import { Repository } from './repository'; describe('HighlightService', async function () { @@ -12,7 +12,7 @@ describe('HighlightService', async function () { let service: HighlightService before(async function () { - const repo = {} + const repo = {} repo.getContentByContentId = () => Promise.resolve({ bookmarkId: "c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe", title: "Chapter Eight: Holden", @@ -46,7 +46,7 @@ describe('HighlightService', async function () { .convertToMap([highlight], true, "", false, '[!quote]', '[!note]') .get(highlight.content.bookTitle ?? "") - if(!map) { + if (!map) { chai.assert.isNotNull(map) return } @@ -56,7 +56,14 @@ describe('HighlightService', async function () { markdown, `## Chapter Eight: Holden %%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} + +%%START-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > “I guess I can’t be. How do you prove a negative?” — [[` + dateCreatedText + `]] +%%END-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} %%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) @@ -67,7 +74,7 @@ describe('HighlightService', async function () { .convertToMap([highlight], false, "", false, '[!quote]', '[!note]') .get(highlight.content.bookTitle ?? "") - if(!map) { + if (!map) { chai.assert.isNotNull(map) return } @@ -77,7 +84,14 @@ describe('HighlightService', async function () { markdown, `## Chapter Eight: Holden %%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} + +%%START-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > “I guess I can’t be. How do you prove a negative?” +%%END-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} %%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) @@ -88,7 +102,7 @@ describe('HighlightService', async function () { .convertToMap([highlight], false, "", true, 'quote', 'note') .get(highlight.content.bookTitle ?? "") - if(!map) { + if (!map) { chai.assert.isNotNull(map) return } @@ -98,8 +112,15 @@ describe('HighlightService', async function () { markdown, `## Chapter Eight: Holden %%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} + +%%START-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > [!quote] > “I guess I can’t be. How do you prove a negative?” +%%END-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} %%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) @@ -110,7 +131,7 @@ describe('HighlightService', async function () { .convertToMap([highlight], true, "", true, 'quote', 'note') .get(highlight.content.bookTitle ?? "") - if(!map) { + if (!map) { chai.assert.isNotNull(map) return } @@ -120,8 +141,15 @@ describe('HighlightService', async function () { markdown, `## Chapter Eight: Holden %%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} + +%%START-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > [!quote] > “I guess I can’t be. How do you prove a negative?” — [[` + dateCreatedText + `]] +%%END-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} %%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) @@ -132,7 +160,7 @@ describe('HighlightService', async function () { .convertToMap([highlight], false, "", true, 'bug', 'note') .get(highlight.content.bookTitle ?? "") - if(!map) { + if (!map) { chai.assert.isNotNull(map) return } @@ -142,8 +170,15 @@ describe('HighlightService', async function () { markdown, `## Chapter Eight: Holden %%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} + +%%START-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > [!bug] > “I guess I can’t be. How do you prove a negative?” +%%END-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} %%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) @@ -154,7 +189,7 @@ describe('HighlightService', async function () { .convertToMap([highlight], true, "", true, 'bug', 'note') .get(highlight.content.bookTitle ?? "") - if(!map) { + if (!map) { chai.assert.isNotNull(map) return } @@ -164,8 +199,15 @@ describe('HighlightService', async function () { markdown, `## Chapter Eight: Holden %%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} + +%%START-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > [!bug] > “I guess I can’t be. How do you prove a negative?” — [[` + dateCreatedText + `]] +%%END-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} %%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) @@ -195,7 +237,7 @@ describe('HighlightService', async function () { .convertToMap([highlight], true, "", false, '[!quote]', '[!note]') .get(highlight.content.bookTitle ?? "") - if(!map) { + if (!map) { chai.assert.isNotNull(map) return } @@ -205,9 +247,16 @@ describe('HighlightService', async function () { markdown, `## Chapter Eight: Holden %%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} + +%%START-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > “I guess I can’t be. How do you prove a negative?” This is a great note! — [[` + dateCreatedText + `]] +%%END-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} %%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) @@ -218,7 +267,7 @@ This is a great note! — [[` + dateCreatedText + `]] .convertToMap([highlight], false, "", false, '[!quote]', '[!note]') .get(highlight.content.bookTitle ?? "") - if(!map) { + if (!map) { chai.assert.isNotNull(map) return } @@ -228,9 +277,16 @@ This is a great note! — [[` + dateCreatedText + `]] markdown, `## Chapter Eight: Holden %%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} + +%%START-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > “I guess I can’t be. How do you prove a negative?” This is a great note! +%%END-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} %%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) @@ -241,7 +297,7 @@ This is a great note! .convertToMap([highlight], false, "", true, 'quote', 'note') .get(highlight.content.bookTitle ?? "") - if(!map) { + if (!map) { chai.assert.isNotNull(map) return } @@ -251,10 +307,17 @@ This is a great note! markdown, `## Chapter Eight: Holden %%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} + +%%START-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > [!quote] > “I guess I can’t be. How do you prove a negative?” >> [!note] > This is a great note! +%%END-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} %%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) @@ -265,7 +328,7 @@ This is a great note! .convertToMap([highlight], true, "", true, 'quote', 'note') .get(highlight.content.bookTitle ?? "") - if(!map) { + if (!map) { chai.assert.isNotNull(map) return } @@ -275,10 +338,17 @@ This is a great note! markdown, `## Chapter Eight: Holden %%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} + +%%START-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > [!quote] > “I guess I can’t be. How do you prove a negative?” >> [!note] > This is a great note! — [[` + dateCreatedText + `]] +%%END-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} %%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) @@ -289,7 +359,7 @@ This is a great note! .convertToMap([highlight], false, "", true, 'quote', 'bug') .get(highlight.content.bookTitle ?? "") - if(!map) { + if (!map) { chai.assert.isNotNull(map) return } @@ -299,10 +369,17 @@ This is a great note! markdown, `## Chapter Eight: Holden %%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} + +%%START-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > [!quote] > “I guess I can’t be. How do you prove a negative?” >> [!bug] > This is a great note! +%%END-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} %%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) @@ -313,7 +390,7 @@ This is a great note! .convertToMap([highlight], true, "", true, 'quote', 'bug') .get(highlight.content.bookTitle ?? "") - if(!map) { + if (!map) { chai.assert.isNotNull(map) return } @@ -323,21 +400,28 @@ This is a great note! markdown, `## Chapter Eight: Holden %%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} + +%%START-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > [!quote] > “I guess I can’t be. How do you prove a negative?” >> [!bug] > This is a great note! — [[` + dateCreatedText + `]] +%%END-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} %%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` ) }) }) - describe('Sample Bookmark with annotation, with existing highlights and added notes', async function(){ + describe('Sample Bookmark with annotation, with existing highlights and added notes', async function () { let highlight: Highlight let dateCreatedText: string let existingFile: string - + before(async function () { const dateCreated = new Date(Date.UTC(2022, 7, 5, 20, 46, 41, 0)) const bookmark: Bookmark = { @@ -352,23 +436,28 @@ This is a great note! existingFile = `## Chapter Eight: Holden %%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} + +%%START-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > “I guess I can’t be. How do you prove a negative?” This is a great note! — [[` + dateCreatedText + `]] +%%END-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% This is an exising note, added to the highlight. ^325d95 %%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` - }) + }) it('fromMaptoMarkdown with existing file', async function () { const map = service .convertToMap([highlight], true, "", false, '[!quote]', '[!note]') .get(highlight.content.bookTitle ?? "") - if(!map) { + if (!map) { chai.assert.isNotNull(map) return } @@ -378,9 +467,82 @@ This is an exising note, added to the highlight. markdown, `## Chapter Eight: Holden %%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} + +%%START-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% > “I guess I can’t be. How do you prove a negative?” This is a great note! — [[` + dateCreatedText + `]] +%%END-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +This is an exising note, added to the highlight. + +^325d95 + +%%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` + ) + }) + }) + + describe('Sample Bookmark with annotation, with outdated existing highlights and added notes', async function () { + let highlight: Highlight + let dateCreatedText: string + let existingFile: string + + before(async function () { + const dateCreated = new Date(Date.UTC(2022, 7, 5, 20, 46, 41, 0)) + const bookmark: Bookmark = { + bookmarkId: "c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe", + text: "“I guess I can’t be. How do you prove a negative?”", + contentId: "file:///mnt/onboard/Corey, James S.A_/Nemesis Games - James S.A. Corey.epub#(12)OEBPS/Text/ch09.html", + note: 'This is a great note!', + dateCreated + } + highlight = await service.createHighlightFromBookmark(bookmark) + dateCreatedText = moment(dateCreated).format("") + existingFile = `## Chapter Eight: Holden + +%%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} + +%%START-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% +> “I guess I can’t be.” + +This is a great note! — [[` + dateCreatedText + `]] +%%END-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +This is an exising note, added to the highlight. + +^325d95 + +%%END-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%%` + }) + + it('fromMaptoMarkdown with existing file', async function () { + const map = service + .convertToMap([highlight], true, "", false, '[!quote]', '[!note]') + .get(highlight.content.bookTitle ?? "") + + if (!map) { + chai.assert.isNotNull(map) + return + } + + const markdown = service.fromMapToMarkdown(map, existingFile) + chai.assert.deepEqual( + markdown, `## Chapter Eight: Holden + +%%START-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% + +${typeWhateverYouWantPlaceholder} + +%%START-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% +> “I guess I can’t be. How do you prove a negative?” + +This is a great note! — [[` + dateCreatedText + `]] +%%END-EXTRACTED-HIGHLIGHT-c5b2637d-ddaf-4f15-9a81-dd701e0ad8fe%% This is an exising note, added to the highlight. @@ -398,7 +560,7 @@ This is an exising note, added to the highlight. before(async function () { const repo = {} - repo.getContentByContentId = () => Promise.resolve(null); + repo.getContentByContentId = () => Promise.resolve(null); repo.getContentLikeContentId = () => Promise.resolve(null); service = new HighlightService(repo) }) @@ -436,7 +598,14 @@ This is an exising note, added to the highlight. markdown, `## Unknown Title %%START-` + highlight.bookmark.bookmarkId + `%% + +${typeWhateverYouWantPlaceholder} + +%%START-EXTRACTED-HIGHLIGHT-${highlight.bookmark.bookmarkId}%% > “I guess I can’t be. How do you prove a negative?” — [[` + dateCreatedText + `]] +%%END-EXTRACTED-HIGHLIGHT-${highlight.bookmark.bookmarkId}%% + +${typeWhateverYouWantPlaceholder} %%END-` + highlight.bookmark.bookmarkId + `%%` ) @@ -488,7 +657,7 @@ This is an exising note, added to the highlight. const bookmarkMap = new Map([ [ "e7f8f92d-38ca-4556-bab8-a4d902e9c430", - { + { text: "“I guess I can’t be. How do you prove a negative?”", contentId: "e7f8f92d-38ca-4556-bab8-a4d902e9c430", note: '', @@ -497,7 +666,7 @@ This is an exising note, added to the highlight. ], [ "d40c9071-993f-4f1f-ae53-594847d9fd27", - { + { text: "“I guess I can’t be. How do you prove a negative?”", contentId: "d40c9071-993f-4f1f-ae53-594847d9fd27", note: '', @@ -506,7 +675,7 @@ This is an exising note, added to the highlight. ], [ "3408844d-65a6-4d23-9d99-8f189ca07d0b", - { + { text: "“I guess I can’t be. How do you prove a negative?”", contentId: "3408844d-65a6-4d23-9d99-8f189ca07d0b", note: '', @@ -515,7 +684,7 @@ This is an exising note, added to the highlight. ], [ "c0d92aca-e4bb-476a-8131-ee0c0c21ced5", - { + { text: "“I guess I can’t be. How do you prove a negative?”", contentId: "c0d92aca-e4bb-476a-8131-ee0c0c21ced5", note: '', @@ -528,7 +697,7 @@ This is an exising note, added to the highlight. let service: HighlightService before(async function () { - repo = {} + repo = {} repo.getContentByContentId = (contentId) => Promise.resolve(contentMap.get(contentId) ?? null) repo.getTotalBookmark = () => Promise.resolve(contentMap.size); const bookmarks = new Array() diff --git a/src/database/Highlight.ts b/src/database/Highlight.ts index 868e21e..7343814 100644 --- a/src/database/Highlight.ts +++ b/src/database/Highlight.ts @@ -6,9 +6,14 @@ type bookTitle = string type chapter = string type bookmark = { bookmarkId: string - content: string + // This contains the whole block, from START- to END- + fullContent: string + // This contains only the content inside START-EXTRACTED and END-EXTRACTED + highlightContent: string } +export const typeWhateverYouWantPlaceholder = `--> Here you can type whatever you want, it will not be overwritten by the plugin. <--` + export class HighlightService { repo: Repository @@ -18,26 +23,36 @@ export class HighlightService { this.repo = repo } - extractExistingHighlight(bookmarkId: string, existingFile: string): string { + extractExistingHighlight(bookmark: bookmark, existingContent: string): string { // Define search terms - const startSearch = `%%START-${bookmarkId}%%` - const endSearch = `%%END-${bookmarkId}%%` + const startSearch = `%%START-${bookmark.bookmarkId}%%` + const endSearch = `%%END-${bookmark.bookmarkId}%%` // Find substring indices - const start = existingFile.indexOf(startSearch) - const end = existingFile.indexOf(endSearch) + endSearch.length + 1 // Add length of search term to include it in substring extraction + const start = existingContent.indexOf(startSearch) + const end = existingContent.indexOf(endSearch) + endSearch.length + 1 // Add length of search term to include it in substring extraction // Return the extracted substring - return existingFile.substring(start, end) + return this.upateHighlightFromExtractedFile(bookmark, existingContent.substring(start, end)) + } + + upateHighlightFromExtractedFile(bookmark: bookmark, existingContent: string): string { + const startSearch = `%%START-EXTRACTED-HIGHLIGHT-${bookmark.bookmarkId}%%` + const endSearch = `%%END-EXTRACTED-HIGHLIGHT-${bookmark.bookmarkId}%%` + + const start = existingContent.indexOf(startSearch) + startSearch.length + 1 + const end = existingContent.indexOf(endSearch) - 1 // Add length of search term to include it in substring extraction + + return existingContent.replace(existingContent.substring(start, end), bookmark.highlightContent) } - fromMapToMarkdown(chapters: Map, existingFile?: string): string { + fromMapToMarkdown(chapters: Map, existingFileContents?: string): string { let markdown = ""; for (const [chapter, highlights] of chapters) { markdown += `## ${chapter.trim()}\n\n` markdown += highlights.map((highlight) => { - if (existingFile?.includes(highlight.bookmarkId)) { - return this.extractExistingHighlight(highlight.bookmarkId, existingFile) + if (existingFileContents?.includes(highlight.bookmarkId)) { + return this.extractExistingHighlight(highlight, existingFileContents) } else { - return highlight.content + return highlight.fullContent } }).join('\n\n').trim() markdown += `\n\n` @@ -62,33 +77,42 @@ export class HighlightService { } // Start annotation marker - let text = `%%START-${x.bookmark.bookmarkId}%%\n`; + let text = `%%START-${x.bookmark.bookmarkId}%%\n\n`; + text += `${typeWhateverYouWantPlaceholder}\n\n` + text += `%%START-EXTRACTED-HIGHLIGHT-${x.bookmark.bookmarkId}%%\n` + + let higlightContent = "" if (includeCallouts) { - text += `> [!` + highlightCallout + `]\n` + higlightContent += `> [!` + highlightCallout + `]\n` } - text += `> ${x.bookmark.text}` + higlightContent += `> ${x.bookmark.text}` if (x.bookmark.note) { - text += `\n` + higlightContent += `\n` if (includeCallouts) { - text += `>> [!` + annotationCallout + `]` - text += `\n> ${x.bookmark.note}`; + higlightContent += `>> [!` + annotationCallout + `]` + higlightContent += `\n> ${x.bookmark.note}`; } else { - text += `\n${x.bookmark.note}`; + higlightContent += `\n${x.bookmark.note}`; } } if (includeDate) { - text += ` — [[${moment(x.bookmark.dateCreated).format(dateFormat)}]]` + higlightContent += ` — [[${moment(x.bookmark.dateCreated).format(dateFormat)}]]` } + + text += higlightContent + // End annotation marker - text += `\n\n%%END-${x.bookmark.bookmarkId}%%\n`; + text += `\n%%END-EXTRACTED-HIGHLIGHT-${x.bookmark.bookmarkId}%%\n\n` + text += `${typeWhateverYouWantPlaceholder}\n\n` + text += `%%END-${x.bookmark.bookmarkId}%%\n`; const existingBook = m.get(x.content.bookTitle) - const highlight: bookmark = {bookmarkId: x.bookmark.bookmarkId, content: text} + const highlight: bookmark = { bookmarkId: x.bookmark.bookmarkId, fullContent: text, highlightContent: higlightContent } if (existingBook) { const existingChapter = existingBook.get(x.content.title) From 6bc945f26436eaa74f677b585fca44b5e697f099 Mon Sep 17 00:00:00 2001 From: Kevin Hellemun <17928966+OGKevin@users.noreply.github.com> Date: Thu, 11 Jan 2024 11:26:11 +0000 Subject: [PATCH 9/9] chore: update readme --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e89c189..d0d1a74 100644 --- a/README.md +++ b/README.md @@ -58,9 +58,16 @@ The plugin uses comments as highlight markers, to enable support for keeping exi ``` %%START-%% + +--> Here you can type whatever you want, it will not be overwritten by the plugin. <-- + +%%START-EXTRACTED-HIGHLIGHT-%% ...highlight +%%END-EXTRACTED-HIGHLIGHT-%% + +--> Here you can type whatever you want, it will not be overwritten by the plugin. <-- -%%END-%% +%%END-%%` ``` ## Helping Screenshots