Skip to content

Commit

Permalink
feat: add import from a specific book. Add option to import all highl…
Browse files Browse the repository at this point in the history
…ights 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.
  • Loading branch information
bandantonio committed Feb 28, 2024
1 parent f06c47c commit 878de2c
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 14 deletions.
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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).
Expand Down
38 changes: 30 additions & 8 deletions main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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');
Expand All @@ -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();
},
});
}
Expand All @@ -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() {
Expand Down Expand Up @@ -84,7 +100,7 @@ export default class IBookHighlightsPlugin extends Plugin {

return JSON.parse(stdout);
}
async importHighlights(): Promise<void> {
async importHighlights(): Promise<CombinedHighlight[]> {
const books = await this.getBooks();
const annotations = await this.getAnnotations();

Expand All @@ -109,10 +125,16 @@ export default class IBookHighlightsPlugin extends Plugin {

return highlights;
}, []);

await this.saveHighlightsToVault(resultingHighlights);
return resultingHighlights;
}


async importAndSaveHighlights(): Promise<void> {
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;
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
35 changes: 35 additions & 0 deletions src/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { App, Notice, SuggestModal } from 'obsidian';
import IBookHighlightsPlugin from '../main';
import { CombinedHighlight } from './types';

abstract class IBookHighlightsPluginSuggestModal extends SuggestModal<CombinedHighlight> {
plugin: IBookHighlightsPlugin;
constructor(
app: App,
plugin: IBookHighlightsPlugin) {
super(app);
this.plugin = plugin;
}
}

export class IBookHighlightsPluginSearchModal extends IBookHighlightsPluginSuggestModal {
async getSuggestions(query: string): Promise<CombinedHighlight[]> {
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]);

}
}
17 changes: 16 additions & 1 deletion src/settings.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
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';

export const DEFAULT_SETTINGS: IBookHighlightsPluginSettings = {
highlightsFolder: 'ibooks-highlights',
backup: false,
importOnStart: false,
template: defaultTemplate,
}

Expand Down Expand Up @@ -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: <highlights-folder>-bk-<timestamp> (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();
});
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export interface IBookHighlightsPluginSettings {
highlightsFolder: string;
template: string;
backup: boolean;
importOnStart: boolean;
}

export interface IBook {
Expand Down
3 changes: 2 additions & 1 deletion versions.json
Original file line number Diff line number Diff line change
@@ -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"
}

0 comments on commit 878de2c

Please sign in to comment.