Skip to content

Commit

Permalink
feat: refactor book import logic to be more consistent and user-frien…
Browse files Browse the repository at this point in the history
…dly (#43)

This update includes:
- Refactor of single book import logic that does not overwrite existing highlights
- Dialog box to confirm overwrite of existing book highlight(s) for cases when backups are disabled
- Documentation describing the updated behavior
- Updated tests
  • Loading branch information
bandantonio committed Aug 10, 2024
1 parent 5ac0b62 commit ea9fcc5
Show file tree
Hide file tree
Showing 15 changed files with 544 additions and 84 deletions.
61 changes: 53 additions & 8 deletions docs/guide/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ For example, below are some valid folder names:
- `imported_notes/apple_books/highlights`
- `3 - Resources/My Books/Apple Books/Unprocessed`

If the highlight folder is not empty and the [Backup highlights](#backup-highlights) setting is enabled, the plugin will save the existing highlights to a backup folder before importing new highlights. If the setting is disabled, the plugin will overwrite the contents of the highlight folder.


## Import highlights on start

- Default value: Turned off
Expand All @@ -24,13 +21,61 @@ Import all highlights from all your books when Obsidian starts. Respects the [Ba
## Backup highlights

- Default value: Turned off
- Backup folder template: `<highlights-folder>-bk-<timestamp>`. For example, `ibooks-highlights-bk-1704060001`.
- Backup template:
- for the highlight folder: `<highlights-folder>-bk-<timestamp>`. For example, `ibooks-highlights-bk-1704060001`.
- for a specific book: `<highlights-file>-bk-<timestamp>`. For example, `Building a Second Brain-bk-1704060001`.

Backup highlights before import.
- When importing all highlights, the [highlight folder](#highlight-folder) contents (see the note below) will be backed up.
- When importing highlights from a specific book, the specific highlights file will be backed up, if it exists.

The backup name is pre-configured based on the template above and cannot be changed.

::: details Examples

**Import all highlights**

Initial state
```plaintext
.
└── ibooks-highlights
├── Atomic Habits - Tiny Changes, Remarkable Results
└── Building a Second Brain
```
After import
```plaintext
.
├── ibooks-highlights
│ └── <newly imported highlights>
└── ibooks-highlights-bk-1723233525489
├── Atomic Habits - Tiny Changes, Remarkable Results
└── Building a Second Brain
```
**Import highlights from a specific book**

Initial state
```plaintext
.
└── ibooks-highlights
├── Atomic Habits - Tiny Changes, Remarkable Results
└── Building a Second Brain
```
After import
```plaintext
.
└── ibooks-highlights
├── Atomic Habits - Tiny Changes, Remarkable Results
├── Atomic Habits - Tiny Changes, Remarkable Results-bk-1723234215251
└── Building a Second Brain
```

:::

Backup highlights folder before import. The backup folder name is pre-configured based on the template above and cannot be changed. The backup is created inside the [highlight folder](#highlight-folder).
> [!NOTE]
> The plugin will back up only the files that are direct children of the [highlight folder](#highlight-folder). If you (for some reason) have a nested folder structure inside the [highlight folder](#highlight-folder), these folders will not be backed up and will be overwritten on import.
> [!WARNING]
> If the setting is disabled, the plugin will overwrite the contents of the [highlight folder](#highlight-folder) on import.
> This behavior will be improved based on the feedback received: [Issue #34](https://github.com/bandantonio/obsidian-apple-books-highlights-plugin/issues/34#issuecomment-2231429171)
> [!TIP]
> To prevent accidental data loss when the setting is turned off, the plugin will display a confirmation dialog before overwriting the existing highlights.
## Highlights sorting criterion

Expand Down
24 changes: 16 additions & 8 deletions main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Notice, Plugin } from 'obsidian';
import { IBookHighlightsPluginSearchModal } from './src/search';
import { IBookHighlightsPluginSearchModal, OverwriteBookModal } from './src/search';
import { aggregateBookAndHighlightDetails } from './src/methods/aggregateDetails';
import SaveHighlights from './src/methods/saveHighlightsToVault';
import { AppleBooksHighlightsImportPluginSettings, IBookHighlightsSettingTab } from './src/settings';
Expand All @@ -17,12 +17,18 @@ export default class IBookHighlightsPlugin extends Plugin {
}

this.addRibbonIcon('book-open', this.manifest.name, async () => {
await this.aggregateAndSaveHighlights().then(() => {
new Notice('Apple Books highlights imported successfully');
}).catch((error) => {
new Notice(`[${this.manifest.name}]:\nError importing highlights. Check console for details (⌥ ⌘ I)`, 0);
try {
this.settings.backup
? await this.aggregateAndSaveHighlights().then(() => {
new Notice('Apple Books highlights imported successfully');
}).catch((error) => {
new Notice(`[${this.manifest.name}]:\nError importing highlights. Check console for details (⌥ ⌘ I)`, 0);
console.error(`[${this.manifest.name}]: ${error}`);
})
: new OverwriteBookModal(this.app, this).open();
} catch (error) {
console.error(`[${this.manifest.name}]: ${error}`);
});
}
});

this.addSettingTab(new IBookHighlightsSettingTab(this.app, this));
Expand All @@ -32,7 +38,9 @@ export default class IBookHighlightsPlugin extends Plugin {
name: 'Import all',
callback: async () => {
try {
await this.aggregateAndSaveHighlights();
this.settings.backup
? await this.aggregateAndSaveHighlights()
: new OverwriteBookModal(this.app, this).open();
} catch (error) {
new Notice(`[${this.manifest.name}]:\nError importing highlights. Check console for details (⌥ ⌘ I)`, 0);
console.error(`[${this.manifest.name}]: ${error}`);
Expand Down Expand Up @@ -74,6 +82,6 @@ export default class IBookHighlightsPlugin extends Plugin {
throw ('No highlights found. Make sure you made some highlights in your Apple Books.');
}

await this.saveHighlights.saveHighlightsToVault(highlights);
await this.saveHighlights.saveAllBooksHighlightsToVault(highlights);
}
}
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.4.0",
"version": "1.5.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.4.0",
"version": "1.5.0",
"description": "Import highlights and notes from your Apple Books to Obsidian",
"main": "main.js",
"scripts": {
Expand Down
79 changes: 58 additions & 21 deletions src/methods/saveHighlightsToVault.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { App, Vault } from 'obsidian';
import { App, TFile, Vault } from 'obsidian';
import path from 'path';
import { ICombinedBooksAndHighlights } from '../types';
import { AppleBooksHighlightsImportPluginSettings } from '../settings';
import { renderHighlightsTemplate } from './renderHighlightsTemplate';
import { sortHighlights } from 'src/methods/sortHighlights';
import BackupHighlights from 'src/utils/backupHighlights';

export default class SaveHighlights {
private app: App;
Expand All @@ -16,46 +17,82 @@ export default class SaveHighlights {
this.settings = settings;
}

async saveHighlightsToVault(highlights: ICombinedBooksAndHighlights[]): Promise<void> {
const highlightsFolderPath = this.vault.getAbstractFileByPath(
async saveAllBooksHighlightsToVault(highlights: ICombinedBooksAndHighlights[]): Promise<void> {
const highlightsFolderPath = this.vault.getFolderByPath(
this.settings.highlightsFolder
);

const isBackupEnabled = this.settings.backup;

// // Backup highlights folder if backup is enabled
if (highlightsFolderPath) {
if (isBackupEnabled) {
const highlightsFilesToBackup = (await this.vault.adapter.list(highlightsFolderPath.path)).files;
const backupMethods = new BackupHighlights(this.vault, this.settings);
await backupMethods.backupAllHighlights();
} else {
await this.vault.delete(highlightsFolderPath, true);
await this.vault.createFolder(this.settings.highlightsFolder);
}
} else {
await this.vault.createFolder(this.settings.highlightsFolder);
}

const highlightsBackupFolder = `${this.settings.highlightsFolder}-bk-${Date.now()}`;
for (const combinedHighlight of highlights) {
// Order highlights according to the value in settings
const sortedHighlights = sortHighlights(combinedHighlight, this.settings.highlightsSortingCriterion);

await this.vault.createFolder(highlightsBackupFolder);
// Save highlights to vault
const renderedTemplate = await renderHighlightsTemplate(sortedHighlights, this.settings.template);
const filePath = path.join(this.settings.highlightsFolder, `${combinedHighlight.bookTitle}.md`);

highlightsFilesToBackup.forEach(async (file: string) => {
const fileName = path.basename(file);
await this.createNewBookFile(filePath, renderedTemplate);
}
}

await this.vault.adapter.copy(file, path.join(highlightsBackupFolder, fileName))
});
}
async saveSingleBookHighlightsToVault(highlights: ICombinedBooksAndHighlights[], shouldCreateFile: boolean): Promise<void> {
const highlightsFolderPath = this.vault.getFolderByPath(
this.settings.highlightsFolder
);

await this.vault.delete(highlightsFolderPath, true);
if (!highlightsFolderPath) {
await this.vault.createFolder(this.settings.highlightsFolder);
}

await this.vault.createFolder(this.settings.highlightsFolder);

highlights.forEach(async (combinedHighlight: ICombinedBooksAndHighlights) => {
for (const combinedHighlight of highlights) {
// Order highlights according to the value in settings
const sortedHighlights = sortHighlights(combinedHighlight, this.settings.highlightsSortingCriterion);

// Save highlights to vault
const renderedTemplate = await renderHighlightsTemplate(sortedHighlights, this.settings.template);
const filePath = path.join(this.settings.highlightsFolder, `${combinedHighlight.bookTitle}.md`);

await this.vault.create(
filePath,
renderedTemplate
);
});
if (shouldCreateFile) {
await this.createNewBookFile(filePath, renderedTemplate);
} else {
const isBackupEnabled = this.settings.backup;
const backupMethods = new BackupHighlights(this.vault, this.settings);

const vaultFile = this.vault.getFileByPath(filePath) as TFile;

if (isBackupEnabled) {
backupMethods.backupSingleBookHighlights(combinedHighlight.bookTitle);
}

await this.modifyExistingBookFile(vaultFile, renderedTemplate);
}
}
}

async modifyExistingBookFile(file: TFile, data: string): Promise<void> {
await this.vault.modify(
file,
data
);
}

async createNewBookFile(filePath: string, data: string): Promise<void> {
await this.vault.create(
filePath,
data
);
}
}
Loading

0 comments on commit ea9fcc5

Please sign in to comment.