From 878de2cab6a68c13c37c0d85e67a0a7b87dc3f14 Mon Sep 17 00:00:00 2001 From: Antonio Date: Thu, 29 Feb 2024 00:29:00 +0200 Subject: [PATCH] feat: add import from a specific book. Add option to import all highlights on startup. Other improvements include: - Update README with a list of all available template variables. - Minor textual changes of the plugin commands in the Commands palette. --- README.md | 20 ++++++++++++++++++-- main.ts | 38 ++++++++++++++++++++++++++++++-------- manifest.json | 2 +- package.json | 2 +- src/search.ts | 35 +++++++++++++++++++++++++++++++++++ src/settings.ts | 17 ++++++++++++++++- src/types.ts | 1 + versions.json | 3 ++- 8 files changed, 104 insertions(+), 14 deletions(-) create mode 100644 src/search.ts diff --git a/README.md b/README.md index 65f74b0..7bcb95a 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,8 @@ 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. +- **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. + - **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. - Back up your highlights before each import to avoid accidental data loss (optional, but recommended). @@ -31,9 +32,24 @@ Check Obsidian Help for more information about [Community plugins](https://help. ## How to use -- **Command palette**: `Cmd+P > Apple Books - Import Highlights: Import all highlights` +- **Command palette**: + - `Cmd+P > Apple Books - Import Highlights: Import all` + - `Cmd+P > Apple Books - Import Highlights: From a specific book...` - **Ribbon**: Select the plugin icon in the Ribbon (left sidebar) +## 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. +- `{{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: + - If you highlight a part of a sentence, the - `contextualText` will contain the whole sentence. + - 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. + ## 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 b822d22..f701db2 100644 --- a/main.ts +++ b/main.ts @@ -4,6 +4,7 @@ import { normalizePath, Notice, Plugin } from 'obsidian'; import * as path from 'path'; import { promisify } from 'util'; import { DEFAULT_SETTINGS, IBookHighlightsSettingTab } from './src/settings'; +import { IBookHighlightsPluginSearchModal } from './src/search'; import { CombinedHighlight, IBook, @@ -15,9 +16,14 @@ export default class IBookHighlightsPlugin extends Plugin { settings: IBookHighlightsPluginSettings; async onload() { - await this.loadSettings(); + let settings = await this.loadSettings(); + + if (settings.importOnStart) { + await this.importAndSaveHighlights(); + } + this.addRibbonIcon('book-open', this.manifest.name, async () => { - await this.importHighlights().then(() => { + await this.importAndSaveHighlights().then(() => { new Notice('Apple Books highlights imported successfully'); }).catch((error) => { new Notice('Error while importing Apple Books highlights. Check console for details'); @@ -29,9 +35,17 @@ export default class IBookHighlightsPlugin extends Plugin { this.addCommand({ id: 'import-all-highlights', - name: 'Import all highlights', + name: 'Import all', callback: async () => { - await this.importHighlights(); + await this.importAndSaveHighlights(); + }, + }); + + this.addCommand({ + id: 'import-single-highlights', + name: 'From a specific book...', + callback: () => { + new IBookHighlightsPluginSearchModal(this.app, this).open(); }, }); } @@ -40,6 +54,8 @@ export default class IBookHighlightsPlugin extends Plugin { async loadSettings() { this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); + + return this.settings; } async saveSettings() { @@ -84,7 +100,7 @@ export default class IBookHighlightsPlugin extends Plugin { return JSON.parse(stdout); } - async importHighlights(): Promise { + async importHighlights(): Promise { const books = await this.getBooks(); const annotations = await this.getAnnotations(); @@ -109,10 +125,16 @@ export default class IBookHighlightsPlugin extends Plugin { return highlights; }, []); - - await this.saveHighlightsToVault(resultingHighlights); + + return resultingHighlights; } - + + async importAndSaveHighlights(): Promise { + const highlights = await this.importHighlights(); + + await this.saveHighlightsToVault(highlights); + } + async saveHighlightsToVault(highlights: CombinedHighlight[]) { const highlightsFolderPath = this.app.vault.getAbstractFileByPath(this.settings.highlightsFolder); const isBackupEnabled = this.settings.backup; diff --git a/manifest.json b/manifest.json index 1146e7d..9a27b6c 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "apple-books-import-highlights", "name": "Apple Books - Import Highlights", - "version": "1.0.1", + "version": "1.1.0", "minAppVersion": "0.15.0", "description": "Import your Apple Books highlights and notes to Obsidian.", "author": "bandantonio", diff --git a/package.json b/package.json index 878c8e3..444eb7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-apple-books-highlights-plugin", - "version": "1.0.1", + "version": "1.1.0", "description": "Import highlights and notes from your Apple Books to Obsidian", "main": "main.js", "scripts": { diff --git a/src/search.ts b/src/search.ts new file mode 100644 index 0000000..49fdfee --- /dev/null +++ b/src/search.ts @@ -0,0 +1,35 @@ +import { App, Notice, SuggestModal } from 'obsidian'; +import IBookHighlightsPlugin from '../main'; +import { CombinedHighlight } from './types'; + +abstract class IBookHighlightsPluginSuggestModal extends SuggestModal { + plugin: IBookHighlightsPlugin; + constructor( + app: App, + plugin: IBookHighlightsPlugin) { + super(app); + this.plugin = plugin; + } +} + +export class IBookHighlightsPluginSearchModal extends IBookHighlightsPluginSuggestModal { + async getSuggestions(query: string): Promise { + const allBooks = await this.plugin.importHighlights(); + return allBooks.filter(book => { + const titleMatch = book.bookTitle.toLowerCase().includes(query.toLowerCase()); + const authorMatch = book.bookAuthor.toLowerCase().includes(query.toLowerCase()); + + return titleMatch || authorMatch; + }); + } + + renderSuggestion(value: CombinedHighlight, el: HTMLElement) { + el.createEl('div', { text: value.bookTitle }); + el.createEl('small', { text: value.bookAuthor }); + } + + onChooseSuggestion(item: CombinedHighlight, event: MouseEvent | KeyboardEvent) { + this.plugin.saveHighlightsToVault([item]); + + } +} \ No newline at end of file diff --git a/src/settings.ts b/src/settings.ts index d65c55f..b6b3329 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -1,4 +1,4 @@ -import { App, PluginSettingTab, Setting } from 'obsidian'; +import { App, Notice, PluginSettingTab, Setting } from 'obsidian'; import IBookHighlightsPlugin from '../main'; import defaultTemplate from './template'; import { IBookHighlightsPluginSettings } from './types'; @@ -6,6 +6,7 @@ import { IBookHighlightsPluginSettings } from './types'; export const DEFAULT_SETTINGS: IBookHighlightsPluginSettings = { highlightsFolder: 'ibooks-highlights', backup: false, + importOnStart: false, template: defaultTemplate, } @@ -33,12 +34,26 @@ class IBookHighlightsSettingTab extends PluginSettingTab { await this.plugin.saveSettings(); })); + new Setting(containerEl) + .setName('Import highlights on start') + .setDesc('Import all hightlights from all your books when Obsidian starts') + .addToggle((toggle) => { + toggle.setValue(this.plugin.settings.importOnStart) + .onChange(async (value) => { + this.plugin.settings.importOnStart = value; + await this.plugin.saveSettings(); + }); + }); + new Setting(containerEl) .setName('Backup highlights') .setDesc('Backup highlights folder before import. Backup folder template: -bk- (For example, ibooks-highlights-bk-1704060001)') .addToggle((toggle) => { toggle.setValue(this.plugin.settings.backup) .onChange(async (value) => { + if (!value) { + new Notice('Disabling backups imposes a risk of data loss. Please use with caution.', 0); + } this.plugin.settings.backup = value; await this.plugin.saveSettings(); }); diff --git a/src/types.ts b/src/types.ts index 4aad2ba..88c9bcc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,6 +2,7 @@ export interface IBookHighlightsPluginSettings { highlightsFolder: string; template: string; backup: boolean; + importOnStart: boolean; } export interface IBook { diff --git a/versions.json b/versions.json index f66b649..db543dd 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,6 @@ { "1.0.0": "0.15.0", "1.0.1": "0.15.0", - "1.0.2": "0.15.0" + "1.0.2": "0.15.0", + "1.1.0": "0.15.0" }