diff --git a/README.md b/README.md index 5622d01..e87fe75 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Import all your Apple Books highlights to Obsidian. This plugin aims to be a **fast**, **customizable**, **reliable**, and **up-to-date** solution to import your Apple Books highlights to Obsidian: - **Fast**: It takes less than 1 second to import ~1000 highlights. -- **Customizable**: Use Handlebars and Markdown to customize the output of your highlights the way you want. All the variables to use are available in the default template. Check the `Template variables` section below for more information. +- **Customizable**: Use Handlebars and Markdown to customize the output of your highlights the way you want. Check the [`Template variables`](#template-variables) section below for more information. - **Reliable**: - Import actual highlights with only the metadata you need. No visual noise with the deleted but still exported highlights, or, on the contrary, highlights and notes that make no sense without the context. @@ -40,11 +40,19 @@ Check Obsidian Help for more information about [Community plugins](https://help. - `Cmd+P > Apple Books - Import Highlights: From a specific book...` - **Ribbon**: Select the plugin icon in the Ribbon (left sidebar) -## Template variables +## Customization + +The plugin uses Handlebars and Markdown to customize the output of your highlights the way you want. + +### Template variables - `{{{bookTitle}}}` - The title of the book. - `{{bookId}}` - A unique identifier of the book. It is used to create a link to the book in Apple Books: `[Apple Books Link](ibooks://assetid/{{bookId}})`. - `{{{bookAuthor}}}` - The author of the book. +- `{{{bookGenre}}}` - The genre of the book. +- `{{bookLanguage}}` - The language of the book. +- `{{bookLastOpenedDate}}` - The date when you last opened the book. See the [Date formatting](#date-formatting) section for more information. +- `{{bookCoverUrl}}` - The URL of the book cover. - `{{annotations}}` - An array of all the annotations in the book. You can use `{{annotations.length}}` to get the total number of annotations you made in the book. Each annotation has the following properties: - `{{{chapter}}}` - The chapter of the highlight in the book. It may not be available for all highlights due to the initial formatting of the book. - `{{{contextualText}}}` - The text surrounding the highlight to give you more context. For example: @@ -52,12 +60,79 @@ Check Obsidian Help for more information about [Community plugins](https://help. - 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. + - `{{highlightStyle}}` - The style of the highlight. It can be one of the following values: + - `0` (underline) + - `1` (green) + - `2` (blue) + - `3` (yellow) + - `4` (pink) + - `5` (violet) + - `{{highlightCreationDate}}` - The date when you created the highlight. See the [Date formatting](#date-formatting) section for more information. + - `{{highlightModificationDate}}` - The date when you last modified the highlight. See the [Date formatting](#date-formatting) section for more information. + > [!NOTE] > When customizing the template, make sure to wrap variables with triple curly braces (`{{{variable}}}`) to avoid escaping the HTML characters in Markdown files (default behavior). > > If you want escaped output, use double curly braces: `{{variable}}`. +### Date formatting + +The plugin uses the `dateFormat` helper that takes a unix timestamp and the [datajs-compatible string of tokens](https://day.js.org/docs/en/display/format#list-of-all-available-formats) to format dates in the template. For example: + +```hbs +// Template +{{dateFormat dateVariable "date format"}} + +// Example +{{dateFormat bookLastOpenedDate "YYYY-MM-DD hh:mm:ss A Z"}} +// Result +2024-03-04 05:50:28 PM +01:00 + +// Example +{{dateFormat bookLastOpenedDate "ddd, MMM DD YYYY, HH:mm:ss Z"}} +Mon, Mar 04 2024, 17:50:28 +02:00 +``` + +### Templates + +#### Template with colored highlights + +![template colors](template-colors.png) + +```hbs +Title:: 📕 {{{bookTitle}}} +Author:: {{{bookAuthor}}} +Genre:: {{#if bookGenre}}{{{bookGenre}}}{{else}}N/A{{/if}} +Language:: {{#if bookLanguage}}{{bookLanguage}}{{else}}N/A{{/if}} +Last Read:: {{dateFormat bookLastOpenedDate "YYYY-MM-DD hh:mm:ss A Z"}} +Link:: [Apple Books Link](ibooks://assetid/{{bookId}}) + +{{#if bookCoverUrl}}![Book Cover]({{{bookCoverUrl}}}){{/if}} + +## Annotations + +Number of annotations:: {{annotations.length}} + +{{#each annotations}} +---- + +- 📖 Chapter:: {{#if chapter}}{{{chapter}}}{{else}}N/A{{/if}} +- 🔖 Context:: {{#if contextualText}}{{{contextualText}}}{{else}}N/A{{/if}} +{{#if (eq highlightStyle "0")}}- 🎯 Highlight:: {{{highlight}}}{{/if}} +{{#if (eq highlightStyle "1")}}- 🎯 Highlight:: {{{highlight}}}{{/if}} +{{#if (eq highlightStyle "2")}}- 🎯 Highlight:: {{{highlight}}}{{/if}} +{{#if (eq highlightStyle "3")}}- 🎯 Highlight:: {{{highlight}}}{{/if}} +{{#if (eq highlightStyle "4")}}- 🎯 Highlight:: {{{highlight}}}{{/if}} +{{#if (eq highlightStyle "5")}}- 🎯 Highlight:: {{{highlight}}}{{/if}} +- 📝 Note:: {{#if note}}{{{note}}}{{else}}N/A{{/if}} +- 📅 Highlight taken on:: {{dateFormat highlightCreationDate "YYYY-MM-DD hh:mm:ss A Z"}} +- 📅 Highlight modified on:: {{dateFormat highlightModificationDate "YYYY-MM-DD hh:mm:ss A Z"}} + +{{/each}} +``` + + ## Contributing Your feedback and ideas are more than welcome and highly appreciated! Join the discussion in the [Obsidian Forum](https://forum.obsidian.md/t/new-plugin-apple-books-import-highlights/76856). diff --git a/main.ts b/main.ts index 3d931cf..abb7755 100644 --- a/main.ts +++ b/main.ts @@ -1,6 +1,7 @@ import * as child_process from 'child_process'; import * as Handlebars from 'handlebars'; import { normalizePath, Notice, Plugin } from 'obsidian'; +import dayjs from 'dayjs'; import * as path from 'path'; import { promisify } from 'util'; import { DEFAULT_SETTINGS, IBookHighlightsSettingTab } from './src/settings'; @@ -66,7 +67,7 @@ export default class IBookHighlightsPlugin extends Plugin { async getBooks(): Promise { const IBOOK_LIBRARY = '~/Library/Containers/com.apple.iBooksX/Data/Documents/BKLibrary/BKLibrary-1-091020131601.sqlite'; const booksSql = ` - SELECT ZASSETID, ZTITLE, ZAUTHOR, ZGENRE + SELECT ZASSETID, ZTITLE, ZAUTHOR, ZGENRE, ZLANGUAGE, ZLASTOPENDATE, ZCOVERURL FROM ZBKLIBRARYASSET WHERE ZPURCHASEDATE IS NOT NULL`; @@ -85,7 +86,7 @@ export default class IBookHighlightsPlugin extends Plugin { async getAnnotations(): Promise { const IBOOK_ANNOTATION_DB = '~/Library/Containers/com.apple.iBooksX/Data/Documents/AEAnnotation/AEAnnotation_v10312011_1727_local.sqlite'; const annotationsSql = ` - SELECT ZANNOTATIONASSETID, ZFUTUREPROOFING5, ZANNOTATIONREPRESENTATIVETEXT, ZANNOTATIONSELECTEDTEXT, ZANNOTATIONNOTE + SELECT ZANNOTATIONASSETID, ZFUTUREPROOFING5, ZANNOTATIONREPRESENTATIVETEXT, ZANNOTATIONSELECTEDTEXT, ZANNOTATIONNOTE, ZANNOTATIONCREATIONDATE, ZANNOTATIONMODIFICATIONDATE, ZANNOTATIONSTYLE FROM ZAEANNOTATION WHERE ZANNOTATIONSELECTEDTEXT IS NOT NULL AND ZANNOTATIONDELETED IS 0`; @@ -118,12 +119,19 @@ export default class IBookHighlightsPlugin extends Plugin { bookTitle: normalizedBookTitle, bookId: book.ZASSETID, bookAuthor: book.ZAUTHOR, + bookGenre: book.ZGENRE, + bookLanguage: book.ZLANGUAGE, + bookLastOpenedDate: book.ZLASTOPENDATE, + bookCoverUrl: book.ZCOVERURL, annotations: bookRelatedAnnotations.map(annotation => { return { chapter: annotation.ZFUTUREPROOFING5, contextualText: annotation.ZANNOTATIONREPRESENTATIVETEXT, highlight: annotation.ZANNOTATIONSELECTEDTEXT, note: annotation.ZANNOTATIONNOTE, + highlightStyle: annotation.ZANNOTATIONSTYLE, + highlightCreationDate: annotation.ZANNOTATIONCREATIONDATE, + highlightModificationDate: annotation.ZANNOTATIONMODIFICATIONDATE } }) }) @@ -163,7 +171,18 @@ export default class IBookHighlightsPlugin extends Plugin { await this.app.vault.createFolder(this.settings.highlightsFolder); - highlights.forEach(async (highlight: CombinedHighlight) => { + highlights.forEach(async (highlight: CombinedHighlight) => { + // TODO: Consider moving to a separate file if there are several helpers to be added + Handlebars.registerHelper('eq', (a, b) => { + if (a == b) { + return this; + } + }); + + Handlebars.registerHelper('dateFormat', (date, format) => { + return dayjs('2001-01-01').add(date, 's').format(format); + }); + const template = Handlebars.compile(this.settings.template); const renderedTemplate = template(highlight); diff --git a/manifest.json b/manifest.json index 818fd5d..24395c6 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "apple-books-import-highlights", "name": "Apple Books - Import Highlights", - "version": "1.1.1", + "version": "1.2.0", "minAppVersion": "0.15.0", "description": "Import your Apple Books highlights and notes to Obsidian.", "author": "bandantonio", diff --git a/package-lock.json b/package-lock.json index 19f2e9d..a8ffd81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "obsidian-apple-books-highlights-plugin", - "version": "1.1.1", + "version": "1.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "obsidian-apple-books-highlights-plugin", - "version": "1.1.1", + "version": "1.2.0", "license": "MIT", "dependencies": { + "dayjs": "^1.11.10", "handlebars": "^4.7.8", "sqlite3": "^5.1.7-rc.0" }, @@ -1155,6 +1156,11 @@ "node": ">= 8" } }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -4003,6 +4009,11 @@ "which": "^2.0.1" } }, + "dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/package.json b/package.json index 4c89a9c..1b29a30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-apple-books-highlights-plugin", - "version": "1.1.1", + "version": "1.2.0", "description": "Import highlights and notes from your Apple Books to Obsidian", "main": "main.js", "scripts": { @@ -38,7 +38,8 @@ "typescript": "4.7.4" }, "dependencies": { + "dayjs": "^1.11.10", "handlebars": "^4.7.8", "sqlite3": "^5.1.7-rc.0" } -} \ No newline at end of file +} diff --git a/src/types.ts b/src/types.ts index 88c9bcc..bcf6e5a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,6 +10,9 @@ export interface IBook { ZTITLE: string; ZAUTHOR: string; ZGENRE: string; + ZLANGUAGE: string; + ZLASTOPENDATE: number; + ZCOVERURL: string; } export interface IBookAnnotation { @@ -18,6 +21,9 @@ export interface IBookAnnotation { ZANNOTATIONREPRESENTATIVETEXT: string; ZANNOTATIONSELECTEDTEXT: string; ZANNOTATIONNOTE: string; + ZANNOTATIONCREATIONDATE: number; + ZANNOTATIONMODIFICATIONDATE: number; + ZANNOTATIONSTYLE: 0 | 1 | 2 | 3 | 4 | 5; } export interface Highlight { @@ -30,5 +36,9 @@ export interface CombinedHighlight { bookTitle: string; bookId: string; bookAuthor: string; + bookGenre: string; + bookLanguage: string; + bookLastOpenedDate: number; + bookCoverUrl: string; annotations: Highlight[]; } \ No newline at end of file diff --git a/template-colors.png b/template-colors.png new file mode 100644 index 0000000..bf9bace Binary files /dev/null and b/template-colors.png differ diff --git a/tsconfig.json b/tsconfig.json index 7cd0e5e..3eea23a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,7 @@ "importHelpers": true, "isolatedModules": true, "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, "strictNullChecks": true, "lib": [ "DOM",