9
9
Plugin ,
10
10
PluginSettingTab ,
11
11
Setting ,
12
+ TAbstractFile ,
12
13
TFile ,
13
14
Vault
14
15
} from 'obsidian' ;
@@ -19,6 +20,11 @@ import {StatusBar} from "./status";
19
20
// the process.env variable will be replaced by its target value in the output main.js file
20
21
const baseURL = process . env . READWISE_SERVER_URL || "https://readwise.io" ;
21
22
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
+
22
28
interface ReadwiseAuthResponse {
23
29
userAccessToken : string ;
24
30
}
@@ -117,7 +123,7 @@ class AdvancedModal extends Modal {
117
123
for ( const file of readwiseExports ) {
118
124
console . log ( 'Readwise Official plugin: checking file for frontmatter' , file . path ) ;
119
125
120
- const bookId = this . plugin . settings . booksIDsMap [ file . path ] ;
126
+ const bookId = this . plugin . getFileBookId ( file ) ;
121
127
if ( ! bookId ) continue ;
122
128
123
129
await this . plugin . writeBookIdToFrontmatter ( file , bookId ) ;
@@ -249,13 +255,16 @@ export default class ReadwisePlugin extends Plugin {
249
255
}
250
256
if ( response && response . ok ) {
251
257
data = await response . json ( ) ;
258
+
252
259
if ( data . latest_id <= this . settings . lastSavedStatusID ) {
253
260
this . handleSyncSuccess ( buttonContext ) ;
254
261
this . notice ( "Readwise data is already up to date" , false , 4 , true ) ;
255
262
return ;
256
263
}
264
+
257
265
this . settings . currentSyncStatusID = data . latest_id ;
258
266
await this . saveSettings ( ) ;
267
+
259
268
if ( response . status === 201 ) {
260
269
this . notice ( "Syncing Readwise data" ) ;
261
270
return this . getExportStatus ( data . latest_id , buttonContext ) ;
@@ -302,6 +311,60 @@ export default class ReadwisePlugin extends Plugin {
302
311
} ;
303
312
}
304
313
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
+
305
368
async downloadArchive ( exportID : number , buttonContext : ButtonComponent ) : Promise < void > {
306
369
let artifactURL = `${ baseURL } /api/download_artifact/${ exportID } ` ;
307
370
if ( exportID <= this . settings . lastSavedStatusID ) {
@@ -363,6 +426,7 @@ export default class ReadwisePlugin extends Plugin {
363
426
}
364
427
await this . fs . write ( originalName , contentToSave ) ;
365
428
this . app . metadataCache . trigger ( 'readwise:write' ) ;
429
+
366
430
await this . saveSettings ( ) ;
367
431
} catch ( e ) {
368
432
console . log ( `Readwise Official plugin: error writing ${ processedFileName } :` , e ) ;
@@ -462,8 +526,8 @@ export default class ReadwisePlugin extends Plugin {
462
526
await this . saveSettings ( ) ;
463
527
}
464
528
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 ) ;
467
531
try {
468
532
fetch (
469
533
`${ baseURL } /api/refresh_book_export` ,
@@ -477,7 +541,7 @@ export default class ReadwisePlugin extends Plugin {
477
541
let booksToRefresh = this . settings . booksToRefresh ;
478
542
this . settings . booksToRefresh = booksToRefresh . filter ( n => ! [ bookId ] . includes ( n ) ) ;
479
543
this . saveSettings ( ) ;
480
- vault . delete ( vault . getAbstractFileByPath ( fileName ) ) . then ( ( ) => {
544
+ vault . delete ( vault . getAbstractFileByPath ( file . path ) ) . then ( ( ) => {
481
545
this . startSync ( ) ;
482
546
this . app . metadataCache . trigger ( 'readwise:write' ) ;
483
547
} ) ;
@@ -529,10 +593,18 @@ export default class ReadwisePlugin extends Plugin {
529
593
} ) ;
530
594
531
595
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
+
533
604
if ( ! bookId ) {
534
605
return ;
535
606
}
607
+
536
608
this . settings . booksIDsMap [ file . path ] = bookId ;
537
609
delete this . settings . booksIDsMap [ oldPath ] ;
538
610
this . saveSettings ( ) ;
@@ -569,14 +641,17 @@ export default class ReadwisePlugin extends Plugin {
569
641
id : 'readwise-official-reimport-file' ,
570
642
name : 'Delete and reimport this document' ,
571
643
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
+
574
648
if ( checking ) {
649
+ const isRWfile = ! ! this . getFileBookId ( this . app . workspace . getActiveFile ( ) ) ;
575
650
return isRWfile ;
576
651
}
652
+
577
653
if ( this . settings . reimportShowConfirmation ) {
578
654
const modal = new Modal ( this . app ) ;
579
- modal . titleEl . setText ( "Delete and reimport this document?" ) ;
580
655
modal . contentEl . createEl (
581
656
'p' ,
582
657
{
@@ -599,12 +674,12 @@ export default class ReadwisePlugin extends Plugin {
599
674
modal . close ( ) ;
600
675
} ) ;
601
676
confirmBtn . onClickEvent ( ( ) => {
602
- this . reimportFile ( this . app . vault , activeFilePath ) ;
677
+ this . reimportFile ( this . app . vault , activeFile ) ;
603
678
modal . close ( ) ;
604
679
} ) ;
605
680
modal . open ( ) ;
606
681
} else {
607
- this . reimportFile ( this . app . vault , activeFilePath ) ;
682
+ this . reimportFile ( this . app . vault , activeFile ) ;
608
683
}
609
684
}
610
685
} ) ;
0 commit comments