Skip to content

Commit

Permalink
feat: links to highlighted fragments in Apple Books (#27)
Browse files Browse the repository at this point in the history
* refactor(core): update types, constants, schemas, seedData and methods
* refactor(core): update default template to include highlight location
* test(db): update migrations to enable new database tests
* test: add basic highlight links tests for db
* test: update mock tests, plugin docs and plugin info
* docs(README): update preview screenshot to show highlight links feature
* docs(README): add highlight location template variable
  • Loading branch information
absorpheus authored Jul 11, 2024
1 parent d735e68 commit e27aa2f
Show file tree
Hide file tree
Showing 19 changed files with 202 additions and 1 deletion.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ node_modules

# tests
coverage
test/mocks/testDatabase.sqlite
# Don't include the compiled main.js file in the repo.
# They should be uploaded to GitHub releases instead.
main.js
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ The plugin uses Handlebars and Markdown to customize the output of your highligh
- If you highlight parts of two adjacent sentences, the `contextualText` will contain both sentences.
- `{{{highlight}}}` - The highlighted text.
- `{{{note}}}` - A note you added for the highlight.
- `{{{highlightLocation}}}` - A unique identifier of the highlighted text. It is used to create a link to the highlighted text in Apple Books. For example: `[Apple Books Highlight Link](ibooks://assetid/{{bookId}}#{{highlightLocation}})`.
- `{{highlightStyle}}` - The style of the highlight. It can be one of the following values:
- `0` (underline)
- `1` (green)
Expand Down
1 change: 1 addition & 0 deletions migrations/0001_marvelous_mentallo.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE ZAEANNOTATION ADD `ZANNOTATIONLOCATION` text;
151 changes: 151 additions & 0 deletions migrations/meta/0001_snapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
{
"version": "5",
"dialect": "sqlite",
"id": "0e719ef5-4a90-4b4c-8245-5cf855c5b3f1",
"prevId": "9ab8f1f7-710a-4b2c-9ba7-b286cb403847",
"tables": {
"ZAEANNOTATION": {
"name": "ZAEANNOTATION",
"columns": {
"ZANNOTATIONASSETID": {
"name": "ZANNOTATIONASSETID",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"ZFUTUREPROOFING5": {
"name": "ZFUTUREPROOFING5",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"ZANNOTATIONREPRESENTATIVETEXT": {
"name": "ZANNOTATIONREPRESENTATIVETEXT",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"ZANNOTATIONSELECTEDTEXT": {
"name": "ZANNOTATIONSELECTEDTEXT",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"ZANNOTATIONLOCATION": {
"name": "ZANNOTATIONLOCATION",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"ZANNOTATIONNOTE": {
"name": "ZANNOTATIONNOTE",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"ZANNOTATIONCREATIONDATE": {
"name": "ZANNOTATIONCREATIONDATE",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"ZANNOTATIONMODIFICATIONDATE": {
"name": "ZANNOTATIONMODIFICATIONDATE",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"ZANNOTATIONSTYLE": {
"name": "ZANNOTATIONSTYLE",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"ZANNOTATIONDELETED": {
"name": "ZANNOTATIONDELETED",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"ZBKLIBRARYASSET": {
"name": "ZBKLIBRARYASSET",
"columns": {
"ZASSETID": {
"name": "ZASSETID",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"ZTITLE": {
"name": "ZTITLE",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"ZAUTHOR": {
"name": "ZAUTHOR",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"ZGENRE": {
"name": "ZGENRE",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"ZLANGUAGE": {
"name": "ZLANGUAGE",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"ZLASTOPENDATE": {
"name": "ZLASTOPENDATE",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"ZCOVERURL": {
"name": "ZCOVERURL",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
}
},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
}
}
7 changes: 7 additions & 0 deletions migrations/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
"when": 1713726520358,
"tag": "0000_outstanding_wolf_cub",
"breakpoints": true
},
{
"idx": 1,
"version": "5",
"when": 1720611694414,
"tag": "0001_marvelous_mentallo",
"breakpoints": true
}
]
}
Binary file modified preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/db/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const HIGHLIGHTS_LIBRARY_COLUMNS = [
'ZFUTUREPROOFING5',
'ZANNOTATIONREPRESENTATIVETEXT',
'ZANNOTATIONSELECTEDTEXT',
'ZANNOTATIONLOCATION',
'ZANNOTATIONNOTE',
'ZANNOTATIONCREATIONDATE',
'ZANNOTATIONMODIFICATIONDATE',
Expand Down
1 change: 1 addition & 0 deletions src/db/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const annotations = sqliteTable(HIGHLIGHTS_LIBRARY_NAME, {
ZFUTUREPROOFING5: text('ZFUTUREPROOFING5'),
ZANNOTATIONREPRESENTATIVETEXT: text('ZANNOTATIONREPRESENTATIVETEXT'),
ZANNOTATIONSELECTEDTEXT: text('ZANNOTATIONSELECTEDTEXT').notNull(),
ZANNOTATIONLOCATION: text('ZANNOTATIONLOCATION'),
ZANNOTATIONNOTE: text('ZANNOTATIONNOTE'),
ZANNOTATIONCREATIONDATE: integer('ZANNOTATIONCREATIONDATE'),
ZANNOTATIONMODIFICATIONDATE: integer('ZANNOTATIONMODIFICATIONDATE'),
Expand Down
8 changes: 8 additions & 0 deletions src/db/seedData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const defaultAnnotations = [{
"ZFUTUREPROOFING5": "Introduction",
"ZANNOTATIONREPRESENTATIVETEXT": "This is a contextual text for the hightlight from the iPhone User Guide",
"ZANNOTATIONSELECTEDTEXT": "hightlight from the iPhone User Guide",
"ZANNOTATIONLOCATION": "test-highlight-link-from-the-iphone-user-guide",
"ZANNOTATIONNOTE": "Test note for the hightlight from the iPhone User Guide",
"ZANNOTATIONCREATIONDATE": 685151385.91602,
"ZANNOTATIONMODIFICATIONDATE": 685151385.91602,
Expand All @@ -47,6 +48,7 @@ export const defaultAnnotations = [{
"ZFUTUREPROOFING5": "Introduction",
"ZANNOTATIONREPRESENTATIVETEXT": "This is a contextual text for the hightlight from the iPad User Guide",
"ZANNOTATIONSELECTEDTEXT": "hightlight from the iPad User Guide",
"ZANNOTATIONLOCATION": "test-highlight-link-from-the-ipad-user-guide",
"ZANNOTATIONNOTE": "Test note for the hightlight from the iPad User Guide",
"ZANNOTATIONCREATIONDATE": 685151385.91602,
"ZANNOTATIONMODIFICATIONDATE": 685151385.91602,
Expand All @@ -57,6 +59,7 @@ export const defaultAnnotations = [{
"ZFUTUREPROOFING5": "Introduction",
"ZANNOTATIONREPRESENTATIVETEXT": "This is a contextual text for the hightlight from the Mac User Guide",
"ZANNOTATIONSELECTEDTEXT": "hightlight from the Mac User Guide",
"ZANNOTATIONLOCATION": "test-highlight-link-from-the-mac-user-guide",
"ZANNOTATIONNOTE": "Test note for the hightlight from the Mac User Guide",
"ZANNOTATIONCREATIONDATE": 685151385.91602,
"ZANNOTATIONMODIFICATIONDATE": 685151385.91602,
Expand All @@ -67,6 +70,7 @@ export const defaultAnnotations = [{
"ZFUTUREPROOFING5": "Introduction",
"ZANNOTATIONREPRESENTATIVETEXT": "This is a contextual text for the hightlight from the Apple Watch User Guide",
"ZANNOTATIONSELECTEDTEXT": "hightlight from the Apple Watch User Guide",
"ZANNOTATIONLOCATION": "test-highlight-link-from-the-apple-watch-user-guide",
"ZANNOTATIONNOTE": "Test note for the hightlight from the Apple Watch User Guide",
"ZANNOTATIONCREATIONDATE": 685151385.91602,
"ZANNOTATIONMODIFICATIONDATE": 685151385.91602,
Expand All @@ -77,6 +81,7 @@ export const defaultAnnotations = [{
"ZFUTUREPROOFING5": "Introduction",
"ZANNOTATIONREPRESENTATIVETEXT": "This is a contextual text for the hightlight from the iPhone User Guide",
"ZANNOTATIONSELECTEDTEXT": "hightlight from the iPhone User Guide",
"ZANNOTATIONLOCATION": "test-highlight-link-from-the-iphone-user-guide",
"ZANNOTATIONNOTE": "Test note for the deleted hightlight from the iPhone User Guide",
"ZANNOTATIONCREATIONDATE": 685151385.91602,
"ZANNOTATIONMODIFICATIONDATE": 685151385.91602,
Expand All @@ -87,6 +92,7 @@ export const defaultAnnotations = [{
"ZFUTUREPROOFING5": "Introduction",
"ZANNOTATIONREPRESENTATIVETEXT": "This is a contextual text for the hightlight from the iPad User Guide",
"ZANNOTATIONSELECTEDTEXT": "hightlight from the iPad User Guide",
"ZANNOTATIONLOCATION": "test-highlight-link-from-the-ipad-user-guide",
"ZANNOTATIONNOTE": "Test note for the deleted hightlight from the iPad User Guide",
"ZANNOTATIONCREATIONDATE": 685151385.91602,
"ZANNOTATIONMODIFICATIONDATE": 685151385.91602,
Expand All @@ -97,6 +103,7 @@ export const defaultAnnotations = [{
"ZFUTUREPROOFING5": "Introduction",
"ZANNOTATIONREPRESENTATIVETEXT": "This is a contextual text for the hightlight from the Mac User Guide",
"ZANNOTATIONSELECTEDTEXT": "hightlight from the Mac User Guide",
"ZANNOTATIONLOCATION": "test-highlight-link-from-the-mac-user-guide",
"ZANNOTATIONNOTE": "Test note for the deleted hightlight from the Mac User Guide",
"ZANNOTATIONCREATIONDATE": 685151385.91602,
"ZANNOTATIONMODIFICATIONDATE": 685151385.91602,
Expand All @@ -107,6 +114,7 @@ export const defaultAnnotations = [{
"ZFUTUREPROOFING5": "Introduction",
"ZANNOTATIONREPRESENTATIVETEXT": "This is a contextual text for the hightlight from the Apple Watch User Guide",
"ZANNOTATIONSELECTEDTEXT": "hightlight from the Apple Watch User Guide",
"ZANNOTATIONLOCATION": "test-highlight-link-from-the-apple-watch-user-guide",
"ZANNOTATIONNOTE": "Test note for the deleted hightlight from the Apple Watch User Guide",
"ZANNOTATIONCREATIONDATE": 685151385.91602,
"ZANNOTATIONMODIFICATIONDATE": 685151385.91602,
Expand Down
1 change: 1 addition & 0 deletions src/methods/aggregateDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const aggregateBookAndHighlightDetails = async (): Promise<ICombinedBooks
contextualText: textForContext ? preserveNewlineIndentation(textForContext) : textForContext,
highlight: preserveNewlineIndentation(annotation.ZANNOTATIONSELECTEDTEXT),
note: userNote ? preserveNewlineIndentation(userNote) : userNote,
highlightLocation: annotation.ZANNOTATIONLOCATION,
highlightStyle: annotation.ZANNOTATIONSTYLE,
highlightCreationDate: annotation.ZANNOTATIONCREATIONDATE,
highlightModificationDate: annotation.ZANNOTATIONMODIFICATIONDATE
Expand Down
1 change: 1 addition & 0 deletions src/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Number of annotations:: {{annotations.length}}
- 🔖 Context:: {{#if contextualText}}{{{contextualText}}}{{else}}N/A{{/if}}
- 🎯 Highlight:: {{{highlight}}}
- 📝 Note:: {{#if note}}{{{note}}}{{else}}N/A{{/if}}
- 📙 Highlight Link:: {{#if highlightLocation}}[Apple Books Highlight Link](ibooks://assetid/{{../bookId}}#{{highlightLocation}}){{else}}N/A{{/if}}
{{/each}}
`;
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface IBookAnnotation {
ZFUTUREPROOFING5: string;
ZANNOTATIONREPRESENTATIVETEXT: string;
ZANNOTATIONSELECTEDTEXT: string;
ZANNOTATIONLOCATION: string;
ZANNOTATIONNOTE: string;
ZANNOTATIONCREATIONDATE: number;
ZANNOTATIONMODIFICATIONDATE: number;
Expand All @@ -23,6 +24,7 @@ export interface IHighlight {
chapter: string;
contextualText: string;
highlight: string;
highlightLocation: string;
note: string;
}
export interface ICombinedBooksAndHighlights {
Expand Down
17 changes: 17 additions & 0 deletions test/db.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,19 @@ describe('Database operations', () => {
expect(highlights[0].ZANNOTATIONNOTE).toEqual('Test note for the hightlight from the iPhone User Guide');
expect(highlights[3].ZANNOTATIONREPRESENTATIVETEXT).toEqual('This is a contextual text for the hightlight from the Apple Watch User Guide');
});

test('Should return a highlight link for each highlight when highlights library is not empty', async () => {
await seedDatabase(annotations, defaultAnnotations);

const dbPath = path.join(process.cwd(), TEST_DATABASE_PATH);
const highlights = await annotationsRequest(dbPath, `SELECT * FROM ${HIGHLIGHTS_LIBRARY_NAME} WHERE ZANNOTATIONDELETED = 0`);

expect(highlights.length).toEqual(4);
expect(highlights[0].ZANNOTATIONLOCATION).toEqual('test-highlight-link-from-the-iphone-user-guide');
expect(highlights[1].ZANNOTATIONLOCATION).toEqual('test-highlight-link-from-the-ipad-user-guide');
expect(highlights[2].ZANNOTATIONLOCATION).toEqual('test-highlight-link-from-the-mac-user-guide');
expect(highlights[3].ZANNOTATIONLOCATION).toEqual('test-highlight-link-from-the-apple-watch-user-guide');
});
});

describe('Database load testing', () => {
Expand All @@ -89,6 +102,7 @@ describe('Database load testing', () => {
ZFUTUREPROOFING5: `Introduction ${j}`,
ZANNOTATIONREPRESENTATIVETEXT: `This is a contextual text for the hightlight from the Book ${i}`,
ZANNOTATIONSELECTEDTEXT: `hightlight from the Book ${i}`,
ZANNOTATIONLOCATION: `test-highlight-link-from-the-book-${i}`,
ZANNOTATIONNOTE: `Test note for the hightlight from the Book ${i}`,
ZANNOTATIONCREATIONDATE: 685151385.91602,
ZANNOTATIONMODIFICATIONDATE: 685151385.91602,
Expand All @@ -110,6 +124,9 @@ describe('Database load testing', () => {
expect(dbBooks.length).toEqual(1000);
expect(dbAnnotations.length).toEqual(3000);

const dbAnnotationLocations = dbAnnotations.filter(({ZANNOTATIONLOCATION}) => ZANNOTATIONLOCATION !== null && ZANNOTATIONLOCATION !== undefined);
expect(dbAnnotationLocations.length).toEqual(3000);

expect(endTime - startTime).toBeLessThan(500);
});
});
4 changes: 4 additions & 0 deletions test/mocks/aggregatedDetailsData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const annotationsToAggregate = [{
"ZFUTUREPROOFING5": "Aggregated Introduction",
"ZANNOTATIONREPRESENTATIVETEXT": "This is a contextual text for the aggregated hightlight from the Apple iPhone User Guide",
"ZANNOTATIONSELECTEDTEXT": "aggregated hightlight from the Apple iPhone User Guide",
"ZANNOTATIONLOCATION": "aggregated-highlight-link-from-the-apple-iphone-user-guide",
"ZANNOTATIONNOTE": "Test note for the aggregated hightlight from the Apple iPhone User Guide",
"ZANNOTATIONCREATIONDATE": 731876693.002279,
"ZANNOTATIONMODIFICATIONDATE": 731876693.002279,
Expand All @@ -23,6 +24,7 @@ export const annotationsToAggregate = [{
"ZFUTUREPROOFING5": "Another aggregated Introduction",
"ZANNOTATIONREPRESENTATIVETEXT": "This is a contextual text for the aggregated hightlight from the Apple iPhone User Guide\n\ncontaining a new line to test the preservation of indentation",
"ZANNOTATIONSELECTEDTEXT": "aggregated hightlight from the Apple iPhone User Guide\n\ncontaining a new line to test the preservation of indentation",
"ZANNOTATIONLOCATION": "aggregated-highlight-link-from-the-apple-iphone-user-guide",
"ZANNOTATIONNOTE": "Test note for the aggregated hightlight from the Apple iPhone User Guide\n\nalong with a new line to test the preservation of indentation",
"ZANNOTATIONCREATIONDATE": 731876693.002279,
"ZANNOTATIONMODIFICATIONDATE": 731876693.002279,
Expand All @@ -43,6 +45,7 @@ export const aggregatedHighlights = [{
"contextualText": "This is a contextual text for the aggregated hightlight from the Apple iPhone User Guide",
"highlight": "aggregated hightlight from the Apple iPhone User Guide",
"note": "Test note for the aggregated hightlight from the Apple iPhone User Guide",
"highlightLocation": "aggregated-highlight-link-from-the-apple-iphone-user-guide",
"highlightStyle": 3,
"highlightCreationDate": 731876693.002279,
"highlightModificationDate": 731876693.002279
Expand All @@ -51,6 +54,7 @@ export const aggregatedHighlights = [{
"contextualText": "This is a contextual text for the aggregated hightlight from the Apple iPhone User Guide\ncontaining a new line to test the preservation of indentation",
"highlight": "aggregated hightlight from the Apple iPhone User Guide\ncontaining a new line to test the preservation of indentation",
"note": "Test note for the aggregated hightlight from the Apple iPhone User Guide\nalong with a new line to test the preservation of indentation",
"highlightLocation": "aggregated-highlight-link-from-the-apple-iphone-user-guide",
"highlightStyle": 3,
"highlightCreationDate": 731876693.002279,
"highlightModificationDate": 731876693.002279
Expand Down
1 change: 1 addition & 0 deletions test/mocks/rawTemplates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Number of annotations:: {{annotations.length}}
{{else if (eq highlightStyle "5")}}- 🎯 Highlight:: <mark style="background:rgb(214,192,238); color:#000; padding:2px;">{{{highlight}}}</mark>
{{/if}}
- 📝 Note:: {{#if note}}{{{note}}}{{else}}N/A{{/if}}
- 📙 Highlight Link:: {{#if highlightLocation}}[Apple Books Highlight Link](ibooks://assetid/{{../bookId}}#{{highlightLocation}}){{else}}N/A{{/if}}
- <small>📅 Highlight taken on:: {{dateFormat highlightCreationDate "YYYY-MM-DD hh:mm:ss A Z"}}</small>
- <small>📅 Highlight modified on:: {{dateFormat highlightModificationDate "YYYY-MM-DD hh:mm:ss A Z"}}</small>
Expand Down
Loading

0 comments on commit e27aa2f

Please sign in to comment.