1
1
import slugify from '@sindresorhus/slugify' ;
2
2
import filenamify from 'filenamify' ;
3
- import { ConfigureOptions , Environment , Template } from 'nunjucks' ;
4
- import { CachedMetadata , normalizePath , Plugin , TFile } from 'obsidian' ;
5
3
import spacetime from 'spacetime' ;
4
+ import { type CachedMetadata , Plugin , normalizePath , TFile } from 'obsidian' ;
5
+ import { type ConfigureOptions , Template , Environment } from 'nunjucks' ;
6
6
import * as YAML from 'yaml' ;
7
7
8
- import { DEFAULT_SETTINGS , FRONTMATTER_TO_ESCAPE , YAML_TOSTRING_OPTIONS } from 'constants/index' ;
9
- import { Export , Highlight , Library , Tag , ReadwiseMetadata } from 'models/readwise' ;
10
- import { PluginSettings } from 'models/settings' ;
11
- import { YamlStringState } from 'models/yaml' ;
8
+ // Plugin classes
12
9
import ReadwiseApi from 'services/readwise-api' ;
13
10
import ReadwiseMirrorSettingTab from 'ui/settings-tab' ;
14
11
import Notify from 'ui/notify' ;
12
+
13
+ // Types
14
+ import { DEFAULT_SETTINGS , FRONTMATTER_TO_ESCAPE , YAML_TOSTRING_OPTIONS } from 'constants/index' ;
15
+ import type { Export , Highlight , Library , Tag , ReadwiseMetadata } from 'models/readwise' ;
16
+ import type { PluginSettings } from 'models/settings' ;
17
+ import type { YamlStringState } from 'models/yaml' ;
18
+
15
19
export default class ReadwiseMirror extends Plugin {
16
20
settings : PluginSettings ;
17
21
readwiseApi : ReadwiseApi ;
@@ -20,7 +24,7 @@ export default class ReadwiseMirror extends Plugin {
20
24
frontMatterTemplate : Template ;
21
25
headerTemplate : Template ;
22
26
highlightTemplate : Template ;
23
- isSyncing : boolean = false ;
27
+ isSyncing = false ;
24
28
25
29
private analyzeStringForFrontmatter ( value : string ) : YamlStringState {
26
30
return {
@@ -36,7 +40,7 @@ export default class ReadwiseMirror extends Plugin {
36
40
public escapeFrontmatter ( metadata : ReadwiseMetadata , fieldsToProcess : Array < string > ) : ReadwiseMetadata {
37
41
// Copy the metadata object to avoid modifying the original
38
42
const processedMetadata = { ...metadata } as ReadwiseMetadata ;
39
- fieldsToProcess . forEach ( ( field ) => {
43
+ for ( const field of fieldsToProcess ) {
40
44
if (
41
45
field in processedMetadata &&
42
46
processedMetadata [ field as keyof ReadwiseMetadata ] &&
@@ -47,12 +51,11 @@ export default class ReadwiseMirror extends Plugin {
47
51
( processedMetadata [ key ] as unknown ) = this . escapeYamlValue ( processedMetadata [ key ] as string ) ;
48
52
}
49
53
}
50
- } ) ;
51
-
54
+ }
52
55
return processedMetadata ;
53
56
}
54
57
55
- private escapeYamlValue ( value : string , multiline : boolean = false ) : string {
58
+ private escapeYamlValue ( value : string , multiline = false ) : string {
56
59
if ( ! value ) return '""' ;
57
60
58
61
const state = this . analyzeStringForFrontmatter ( value ) ;
@@ -62,48 +65,46 @@ export default class ReadwiseMirror extends Plugin {
62
65
63
66
// Handle multi-line strings
64
67
if ( value . includes ( '\n' ) && multiline ) {
65
- // Use folded block style (>) for titles, preserve single line ending
66
68
const indent = ' ' ;
67
69
return `>-\n${ indent } ${ value . replace ( / \n / g, `\n${ indent } ` ) } ` ;
68
70
}
69
71
70
- value = value . replace ( / \n / g, ' ' ) . replace ( / \s + / g, ' ' ) . trim ( ) ;
72
+ const cleanValue = value . replace ( / \n / g, ' ' ) . replace ( / \s + / g, ' ' ) . trim ( ) ;
71
73
72
- // No quotes in string - use simple double quotes to catch other special characters
74
+ // No quotes in string - use simple double quotes
73
75
if ( ! state . hasSingleQuotes && ! state . hasDoubleQuotes ) {
74
- return `"${ value } "` ;
76
+ return `"${ cleanValue } "` ;
75
77
}
76
78
77
79
// Has double quotes but no single quotes - use single quotes
78
80
if ( state . hasDoubleQuotes && ! state . hasSingleQuotes ) {
79
- return `'${ value } '` ;
81
+ return `'${ cleanValue } '` ;
80
82
}
81
83
82
84
// Has single quotes but no double quotes - use double quotes
83
85
if ( state . hasSingleQuotes && ! state . hasDoubleQuotes ) {
84
- return `"${ value } "` ;
86
+ return `"${ cleanValue } "` ;
85
87
}
86
88
87
89
// Has both types of quotes - escape double quotes and use double quotes
88
- return `"${ value . replace ( / " / g, '\\"' ) } "` ;
90
+ return `"${ cleanValue . replace ( / " / g, '\\"' ) } "` ;
89
91
}
90
92
91
- private formatTags ( tags : Tag [ ] , nohash : boolean = false , q : string = '' ) {
93
+ private formatTags ( tags : Tag [ ] , nohash = false , q = '' ) {
92
94
// use unique list of tags
93
95
const uniqueTags = [ ...new Set ( tags . map ( ( tag ) => tag . name . replace ( / \s / , '-' ) ) ) ] ;
94
96
95
- if ( nohash ) {
97
+ if ( nohash === true ) {
96
98
// don't return a hash in the tag name
97
99
return uniqueTags . map ( ( tag ) => `${ q } ${ tag } ${ q } ` ) . join ( ', ' ) ;
98
- } else {
99
- return uniqueTags . map ( ( tag ) => `${ q } #${ tag } ${ q } ` ) . join ( ', ' ) ;
100
100
}
101
+ return uniqueTags . map ( ( tag ) => `${ q } #${ tag } ${ q } ` ) . join ( ', ' ) ;
101
102
}
102
103
103
104
private formatHighlight ( highlight : Highlight , book : Export ) {
104
105
const { id, text, note, location, color, url, tags, highlighted_at, created_at, updated_at } = highlight ;
105
106
106
- const locationUrl = `https://readwise.io/to_kindle?action=open&asin=${ book [ ' asin' ] } &location=${ location } ` ;
107
+ const locationUrl = `https://readwise.io/to_kindle?action=open&asin=${ book . asin } &location=${ location } ` ;
107
108
108
109
const formattedTags = tags . filter ( ( tag ) => tag . name !== color ) ;
109
110
const formattedTagStr = this . formatTags ( formattedTags ) ;
@@ -158,7 +159,7 @@ export default class ReadwiseMirror extends Plugin {
158
159
if ( this . settings . highlightSortByLocation ) {
159
160
sortedHighlights = sortedHighlights . sort ( ( highlightA : Highlight , highlightB : Highlight ) => {
160
161
if ( highlightA . location < highlightB . location ) return - 1 ;
161
- else if ( highlightA . location > highlightB . location ) return 1 ;
162
+ if ( highlightA . location > highlightB . location ) return 1 ;
162
163
return 0 ;
163
164
} ) ;
164
165
@@ -175,10 +176,11 @@ export default class ReadwiseMirror extends Plugin {
175
176
// construct an array with unique values
176
177
177
178
let tags : Tag [ ] = [ ] ;
178
- this . sortHighlights ( highlights ) . forEach ( ( highlight : Highlight ) =>
179
- highlight . tags ? ( tags = [ ...tags , ...highlight . tags ] ) : tags
180
- ) ;
179
+ for ( const highlight of this . sortHighlights ( highlights ) ) {
180
+ if ( highlight . tags ) tags = [ ...tags , ...highlight . tags ] ;
181
+ }
181
182
return tags ;
183
+
182
184
}
183
185
184
186
async writeLogToMarkdown ( library : Library ) {
@@ -190,8 +192,8 @@ export default class ReadwiseMirror extends Plugin {
190
192
const now = spacetime . now ( ) ;
191
193
let logString = `# [[${ now . format ( 'iso-short' ) } ]] *(${ now . time ( ) } )*` ;
192
194
193
- for ( const bookId in library [ ' books' ] ) {
194
- const book = library [ ' books' ] [ bookId ] ;
195
+ for ( const bookId in library . books ) {
196
+ const book = library . books [ bookId ] ;
195
197
196
198
const { title, highlights } = book ;
197
199
const num_highlights = highlights . length ;
@@ -208,12 +210,12 @@ export default class ReadwiseMirror extends Plugin {
208
210
console . log ( 'logFile:' , logFile ) ;
209
211
210
212
const logFileContents = await vault . read ( logFile ) ;
211
- vault . modify ( logFile , logFileContents + ' \n\n' + logString ) ;
213
+ vault . modify ( logFile , ` ${ logFileContents } \n\n${ logString } ` ) ;
212
214
} else {
213
215
vault . create ( path , logString ) ;
214
216
}
215
217
} catch ( err ) {
216
- console . error ( ` Readwise: Error writing to sync log file` , err ) ;
218
+ console . error ( " Readwise: Error writing to sync log file" , err ) ;
217
219
}
218
220
}
219
221
@@ -323,7 +325,7 @@ export default class ReadwiseMirror extends Plugin {
323
325
324
326
// Create parent directories for all categories synchronously
325
327
try {
326
- for ( const category of library [ ' categories' ] ) {
328
+ for ( const category of library . categories ) {
327
329
const titleCaseCategory = category . charAt ( 0 ) . toUpperCase ( ) + category . slice ( 1 ) ; // Title Case the directory name
328
330
const path = `${ this . settings . baseFolderName } /${ titleCaseCategory } ` ;
329
331
const abstractFolder = vault . getAbstractFileByPath ( path ) ;
@@ -342,14 +344,14 @@ export default class ReadwiseMirror extends Plugin {
342
344
// Get total number of records
343
345
const booksTotal = Object . keys ( library . books ) . length ;
344
346
let bookCurrent = 1 ;
345
- for ( const bookId in library [ ' books' ] ) {
347
+ for ( const bookId in library . books ) {
346
348
this . notify . setStatusBarText (
347
349
`Readwise: Processing - ${ Math . floor (
348
350
( bookCurrent / booksTotal ) * 100
349
351
) } % finished (${ bookCurrent } /${ booksTotal } )`
350
352
) ;
351
353
bookCurrent += 1 ;
352
- const book : Export = library [ ' books' ] [ bookId ] ;
354
+ const book : Export = library . books [ bookId ] ;
353
355
354
356
const {
355
357
user_book_id,
@@ -369,23 +371,19 @@ export default class ReadwiseMirror extends Plugin {
369
371
// Get highlight count
370
372
const num_highlights = highlights . length ;
371
373
const created = highlights
372
- . map ( function ( highlight ) {
373
- return highlight . created_at ;
374
- } )
374
+ . map ( ( highlight ) => highlight . created_at )
375
375
. sort ( ) [ 0 ] ; // No reverse sort: we want the oldest entry
376
376
const updated = highlights
377
- . map ( function ( highlight ) {
378
- return highlight . updated_at ;
379
- } )
377
+ . map ( ( highlight ) => highlight . updated_at )
380
378
. sort ( )
381
379
. reverse ( ) [ 0 ] ;
380
+
382
381
const last_highlight_at = highlights
383
- . map ( function ( highlight ) {
384
- return highlight . highlighted_at ;
385
- } )
382
+ . map ( ( highlight ) => highlight . highlighted_at )
386
383
. sort ( )
387
384
. reverse ( ) [ 0 ] ;
388
385
386
+
389
387
// Sanitize title, replace colon with substitute from settings
390
388
const sanitizedTitle = this . settings . useSlugify
391
389
? slugify ( title . replace ( / : / g, this . settings . colonSubstitute ?? '-' ) , {
@@ -418,12 +416,12 @@ export default class ReadwiseMirror extends Plugin {
418
416
const authorStr =
419
417
authors [ 0 ] && authors ?. length > 1
420
418
? authors
421
- . filter ( ( authorName : string ) => authorName . trim ( ) != '' )
419
+ . filter ( ( authorName : string ) => authorName . trim ( ) !== '' )
422
420
. map ( ( authorName : string ) => `[[${ authorName . trim ( ) } ]]` )
423
421
. join ( ', ' )
424
422
: author
425
423
? `[[${ author } ]]`
426
- : `` ;
424
+ : "" ;
427
425
428
426
const metadata : ReadwiseMetadata = {
429
427
id : user_book_id ,
@@ -451,7 +449,7 @@ export default class ReadwiseMirror extends Plugin {
451
449
452
450
// Escape specific fields used in frontmatter
453
451
// TODO: Tidy up code. It doesn't make sense to remove the frontmatter markers and then add them back
454
- let frontmatterYaml ;
452
+ let frontmatterYaml : Record < string , unknown > ;
455
453
try {
456
454
const renderedTemplate = this . frontMatterTemplate . render (
457
455
this . escapeFrontmatter ( metadata , FRONTMATTER_TO_ESCAPE )
@@ -464,13 +462,13 @@ export default class ReadwiseMirror extends Plugin {
464
462
if ( error instanceof YAML . YAMLParseError ) {
465
463
console . error ( 'Failed to parse YAML frontmatter:' , error . message ) ;
466
464
throw new Error ( `Invalid YAML frontmatter: ${ error . message } ` ) ;
467
- } else if ( error instanceof Error ) {
465
+ }
466
+ if ( error instanceof Error ) {
468
467
console . error ( 'Error processing frontmatter template:' , error . message ) ;
469
468
throw new Error ( `Failed to process frontmatter: ${ error . message } ` ) ;
470
- } else {
471
- console . error ( 'Unknown error processing frontmatter:' , error ) ;
472
- throw new Error ( 'Failed to process frontmatter due to unknown error' ) ;
473
469
}
470
+ console . error ( 'Unknown error processing frontmatter:' , error ) ;
471
+ throw new Error ( 'Failed to process frontmatter due to unknown error' ) ;
474
472
}
475
473
const frontMatterContents = this . settings . frontMatter
476
474
? [ '---' , YAML . stringify ( frontmatterYaml , YAML_TOSTRING_OPTIONS ) , '---' ] . join ( '\n' )
@@ -680,7 +678,7 @@ export default class ReadwiseMirror extends Plugin {
680
678
`Readwise: Downloaded ${ library . highlightCount } Highlights from ${ Object . keys ( library . books ) . length } Sources`
681
679
) ;
682
680
} else {
683
- this . notify . notice ( ` Readwise: No new content available` ) ;
681
+ this . notify . notice ( " Readwise: No new content available" ) ;
684
682
}
685
683
686
684
this . settings . lastUpdated = new Date ( ) . toISOString ( ) ;
@@ -723,7 +721,7 @@ export default class ReadwiseMirror extends Plugin {
723
721
724
722
// Reload settings after external change (e.g. after sync)
725
723
async onExternalSettingsChange ( ) {
726
- console . info ( ` Reloading settings due to external change` ) ;
724
+ console . info ( " Reloading settings due to external change" ) ;
727
725
await this . loadSettings ( ) ;
728
726
if ( this . settings . lastUpdated )
729
727
this . notify . setStatusBarText ( `Readwise: Updated ${ this . lastUpdatedHumanReadableFormat ( ) } elsewhere` ) ;
@@ -792,19 +790,13 @@ export default class ReadwiseMirror extends Plugin {
792
790
this . env = new Environment ( null , { autoescape : false } as ConfigureOptions ) ;
793
791
794
792
// Add a nunjucks filter to convert newlines to "newlines + >" for quotes
795
- this . env . addFilter ( 'bq' , function ( str ) {
796
- return str . replace ( / \r | \n | \r \n / g, '\r\n> ' ) ;
797
- } ) ;
793
+ this . env . addFilter ( 'bq' , ( str ) => str . replace ( / \r | \n | \r \n / g, '\r\n> ' ) ) ;
798
794
799
795
// Add a nunjukcs filter to test whether we are a ".qa" note
800
- this . env . addFilter ( 'is_qa' , function ( str ) {
801
- return str . includes ( '.qa' ) ;
802
- } ) ;
796
+ this . env . addFilter ( 'is_qa' , ( str ) => str . includes ( '.qa' ) ) ;
803
797
804
798
// Add a nunjucks filter to convert ".qa" notes to Q& A
805
- this . env . addFilter ( 'qa' , function ( str ) {
806
- return str . replace ( / \. q a ( .* ) \? ( .* ) / g, '**Q:**$1?\r\n\r\n**A:**$2' ) ;
807
- } ) ;
799
+ this . env . addFilter ( 'qa' , ( str ) => str . replace ( / \. q a ( .* ) \? ( .* ) / g, '**Q:**$1?\r\n\r\n**A:**$2' ) ) ;
808
800
809
801
this . updateFrontmatteTemplate ( ) ;
810
802
@@ -820,7 +812,7 @@ export default class ReadwiseMirror extends Plugin {
820
812
this . readwiseApi = new ReadwiseApi ( this . settings . apiToken , this . notify ) ;
821
813
if ( this . settings . lastUpdated )
822
814
this . notify . setStatusBarText ( `Readwise: Updated ${ this . lastUpdatedHumanReadableFormat ( ) } ` ) ;
823
- else this . notify . setStatusBarText ( ` Readwise: Click to Sync` ) ;
815
+ else this . notify . setStatusBarText ( " Readwise: Click to Sync" ) ;
824
816
}
825
817
826
818
this . registerDomEvent ( statusBarItem , 'click' , this . sync . bind ( this ) ) ;
@@ -836,7 +828,7 @@ export default class ReadwiseMirror extends Plugin {
836
828
name : 'Test Readwise API key' ,
837
829
callback : async ( ) => {
838
830
const isTokenValid = await this . readwiseApi . hasValidToken ( ) ;
839
- this . notify . notice ( ' Readwise: ' + ( isTokenValid ? 'Token is valid' : 'INVALID TOKEN' ) ) ;
831
+ this . notify . notice ( ` Readwise: ${ isTokenValid ? 'Token is valid' : 'INVALID TOKEN' } ` ) ;
840
832
} ,
841
833
} ) ;
842
834
0 commit comments