Skip to content

Commit d4b821d

Browse files
feature: prefer frontmatter book IDs if present
1 parent 5ec5385 commit d4b821d

File tree

1 file changed

+85
-10
lines changed

1 file changed

+85
-10
lines changed

src/main.ts

Lines changed: 85 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
Plugin,
1010
PluginSettingTab,
1111
Setting,
12+
TAbstractFile,
1213
TFile,
1314
Vault
1415
} from 'obsidian';
@@ -19,6 +20,11 @@ import {StatusBar} from "./status";
1920
// the process.env variable will be replaced by its target value in the output main.js file
2021
const baseURL = process.env.READWISE_SERVER_URL || "https://readwise.io";
2122

23+
/** Type guard because TAbstractFile can be TFile or TFolder */
24+
function isTFile(file: TAbstractFile): file is TFile {
25+
return file instanceof TFile;
26+
}
27+
2228
interface ReadwiseAuthResponse {
2329
userAccessToken: string;
2430
}
@@ -117,7 +123,7 @@ class AdvancedModal extends Modal {
117123
for (const file of readwiseExports) {
118124
console.log('Readwise Official plugin: checking file for frontmatter', file.path);
119125

120-
const bookId = this.plugin.settings.booksIDsMap[file.path];
126+
const bookId = this.plugin.getFileBookId(file);
121127
if (!bookId) continue;
122128

123129
await this.plugin.writeBookIdToFrontmatter(file, bookId);
@@ -249,13 +255,16 @@ export default class ReadwisePlugin extends Plugin {
249255
}
250256
if (response && response.ok) {
251257
data = await response.json();
258+
252259
if (data.latest_id <= this.settings.lastSavedStatusID) {
253260
this.handleSyncSuccess(buttonContext);
254261
this.notice("Readwise data is already up to date", false, 4, true);
255262
return;
256263
}
264+
257265
this.settings.currentSyncStatusID = data.latest_id;
258266
await this.saveSettings();
267+
259268
if (response.status === 201) {
260269
this.notice("Syncing Readwise data");
261270
return this.getExportStatus(data.latest_id, buttonContext);
@@ -302,6 +311,60 @@ export default class ReadwisePlugin extends Plugin {
302311
};
303312
}
304313

314+
/** helper to extract all book IDs from frontmatter in the readwiseDir ("base folder"),
315+
perfectly matching them to the document they came from */
316+
async extractBookIDs() {
317+
console.log('Readwise Official plugin: extracting book IDs from frontmatter...');
318+
const bookIDs: { [bookID: string]: string } = {};
319+
if (!this.settings.frontmatterBookIdKey) {
320+
console.log('Readwise Official plugin: no frontmatter key defined, skipping extraction');
321+
return bookIDs;
322+
}
323+
324+
const files = this.app.vault.getMarkdownFiles();
325+
for (const file of files) {
326+
if (file.path.startsWith(this.settings.readwiseDir)) {
327+
const cache = this.app.metadataCache.getFileCache(file);
328+
// skip if there's no cache
329+
if (!cache) continue;
330+
331+
const frontmatter = cache.frontmatter;
332+
// skip if there's no frontmatter
333+
if (!frontmatter) continue;
334+
335+
const bookID = frontmatter[this.settings.frontmatterBookIdKey];
336+
if (bookID) bookIDs[file.path] = bookID;
337+
}
338+
}
339+
340+
return bookIDs;
341+
}
342+
343+
async getRWfiles() {
344+
let jsonBookIDs = this.settings.booksIDsMap;
345+
const frontmatterBookIDs = await this.extractBookIDs();
346+
347+
// merge the two objects, with frontmatterBookIDs taking precedence
348+
return { ...jsonBookIDs, ...frontmatterBookIDs };
349+
}
350+
351+
/** gets the book ID of a provided file.
352+
* prefers book ID from frontmatter if it exists,
353+
* otherwise uses the the ID found in data.json (booksIDsMap)
354+
*/
355+
getFileBookId(file: TFile): string {
356+
const frontmatterBookId = this.app.metadataCache.getFileCache(file).frontmatter?.[this.settings.frontmatterBookIdKey];
357+
// type narrowing from any -> string
358+
if (frontmatterBookId && typeof frontmatterBookId !== 'string') {
359+
throw new Error(`Readwise Official plugin: bookId not a string`);
360+
}
361+
362+
const jsonBookId = this.settings.booksIDsMap[file.path];
363+
364+
// prefer book id from frontmatter if it exists
365+
return frontmatterBookId || jsonBookId;
366+
}
367+
305368
async downloadArchive(exportID: number, buttonContext: ButtonComponent): Promise<void> {
306369
let artifactURL = `${baseURL}/api/download_artifact/${exportID}`;
307370
if (exportID <= this.settings.lastSavedStatusID) {
@@ -363,6 +426,7 @@ export default class ReadwisePlugin extends Plugin {
363426
}
364427
await this.fs.write(originalName, contentToSave);
365428
this.app.metadataCache.trigger('readwise:write');
429+
366430
await this.saveSettings();
367431
} catch (e) {
368432
console.log(`Readwise Official plugin: error writing ${processedFileName}:`, e);
@@ -462,8 +526,8 @@ export default class ReadwisePlugin extends Plugin {
462526
await this.saveSettings();
463527
}
464528

465-
reimportFile(vault: Vault, fileName: string) {
466-
const bookId = this.settings.booksIDsMap[fileName];
529+
reimportFile(vault: Vault, file: TFile) {
530+
const bookId = this.getFileBookId(file);
467531
try {
468532
fetch(
469533
`${baseURL}/api/refresh_book_export`,
@@ -477,7 +541,7 @@ export default class ReadwisePlugin extends Plugin {
477541
let booksToRefresh = this.settings.booksToRefresh;
478542
this.settings.booksToRefresh = booksToRefresh.filter(n => ![bookId].includes(n));
479543
this.saveSettings();
480-
vault.delete(vault.getAbstractFileByPath(fileName)).then(() => {
544+
vault.delete(vault.getAbstractFileByPath(file.path)).then(() => {
481545
this.startSync();
482546
this.app.metadataCache.trigger('readwise:write');
483547
});
@@ -529,10 +593,18 @@ export default class ReadwisePlugin extends Plugin {
529593
});
530594

531595
this.app.vault.on("rename", (file, oldPath) => {
532-
const bookId = this.settings.booksIDsMap[oldPath];
596+
if (!isTFile(file)) {
597+
throw new Error(`Readwise Official plugin: file is not a TFile`);
598+
}
599+
600+
// prefer book ID from frontmatter if it exists
601+
const bookId = this.getFileBookId(file) || this.settings.booksIDsMap[oldPath];
602+
// the logic in this is ^ kinda awkward... could pass oldPath to getFileBookId as a sort of override?
603+
533604
if (!bookId) {
534605
return;
535606
}
607+
536608
this.settings.booksIDsMap[file.path] = bookId;
537609
delete this.settings.booksIDsMap[oldPath];
538610
this.saveSettings();
@@ -569,14 +641,17 @@ export default class ReadwisePlugin extends Plugin {
569641
id: 'readwise-official-reimport-file',
570642
name: 'Delete and reimport this document',
571643
checkCallback: (checking: boolean) => {
572-
const activeFilePath = this.app.workspace.getActiveFile().path;
573-
const isRWfile = activeFilePath in this.settings.booksIDsMap;
644+
const activeFile = this.app.workspace.getActiveFile();
645+
// blank tab returns null for getActiveFile
646+
if (!activeFile) return false;
647+
574648
if (checking) {
649+
const isRWfile = !!this.getFileBookId(this.app.workspace.getActiveFile());
575650
return isRWfile;
576651
}
652+
577653
if (this.settings.reimportShowConfirmation) {
578654
const modal = new Modal(this.app);
579-
modal.titleEl.setText("Delete and reimport this document?");
580655
modal.contentEl.createEl(
581656
'p',
582657
{
@@ -599,12 +674,12 @@ export default class ReadwisePlugin extends Plugin {
599674
modal.close();
600675
});
601676
confirmBtn.onClickEvent(() => {
602-
this.reimportFile(this.app.vault, activeFilePath);
677+
this.reimportFile(this.app.vault, activeFile);
603678
modal.close();
604679
});
605680
modal.open();
606681
} else {
607-
this.reimportFile(this.app.vault, activeFilePath);
682+
this.reimportFile(this.app.vault, activeFile);
608683
}
609684
}
610685
});

0 commit comments

Comments
 (0)