1- import { Notice , Plugin , TFile , Vault } from "obsidian" ;
1+ import { Notice , Plugin , TFile } from "obsidian" ;
22import {
33 DEFAULT_SETTINGS ,
44 AnkiLinkSettings ,
@@ -13,6 +13,12 @@ interface ParsedNoteData {
1313 note : Note
1414}
1515
16+ interface NoteSyncResult {
17+ added : number ;
18+ linesModified : boolean ;
19+ lines : string [ ] ;
20+ }
21+
1622export default class AnkiLink extends Plugin {
1723 settings ! : AnkiLinkSettings ;
1824
@@ -59,85 +65,94 @@ export default class AnkiLink extends Plugin {
5965 await this . saveData ( this . settings ) ;
6066 }
6167
68+ /**
69+ * Sync all notes from all markdown files in the vault.
70+ * @returns The number of notes added
71+ */
6272 async syncNotes ( ) : Promise < number > {
63- const { vault } = this . app ;
64-
65- const markdownFiles = vault . getMarkdownFiles ( ) ;
73+ const markdownFiles = this . app . vault . getMarkdownFiles ( ) ;
6674 await this . addMissingDecks ( ) ;
6775
6876 let totalAdded = 0 ;
69- const fileLines = await this . readFiles ( vault , markdownFiles ) ;
70- const fileNoteData = await this . extractNoteData ( fileLines ) ;
7177 for ( const file of markdownFiles ) {
72- let linesModified = false ;
73- const notesData = fileNoteData . get ( file ) ;
74- const lines = fileLines . get ( file ) ;
75- if ( ! notesData || ! lines ) {
76- continue ;
77- }
78- const notesWithNoId = [ ] ;
79- const notesWithId = [ ] ;
80- for ( const note of notesData ) {
81- if ( note . id == undefined ) {
82- notesWithNoId . push ( note ) ;
83- } else {
84- notesWithId . push ( note ) ;
85- }
86- }
87- for ( const noteData of notesData ) {
88- if ( noteData . id == undefined ) {
89- noteData . id = await this . sendNote ( noteData . note ) ;
90- lines [ noteData . index ] = `> [!flashcard] %%${ noteData . id } %% ${ noteData . note . fields . Front } ` ;
91- linesModified = true ;
92- totalAdded += 1 ;
93- } else {
94- const ankiNote = await getNoteById ( noteData . id ) ;
95- if ( ankiNote ) {
96- const obsidianFields = noteData . note . fields ;
97- const ankiFields = ankiNote . fields ;
98- if ( obsidianFields . Front !== ankiFields . Front . value || obsidianFields . Back !== ankiFields . Back . value ) {
99- await updateNoteById ( ankiNote . noteId , obsidianFields ) ;
100- }
101- } else {
102- // Missing note for this ID in Anki (notesInfo returned [] or [{}]). Recreate it.
103- const newId = await this . sendNote ( noteData . note ) ;
104- noteData . id = newId ;
105- lines [ noteData . index ] = `> [!flashcard] %%${ newId } %% ${ noteData . note . fields . Front } ` ;
106- linesModified = true ;
107- totalAdded += 1 ;
108- }
109- }
110- }
111- if ( linesModified ) {
112- fileLines . set ( file , lines ) ;
113- await vault . modify ( file , lines . join ( "\n" ) ) ;
114- }
78+ const added = await this . syncSingleFile ( file ) ;
79+ totalAdded += added ;
11580 }
11681 return totalAdded ;
11782 }
11883
119- private async extractNoteData ( fileLines : Map < TFile , string [ ] > ) {
120- const fileNoteData = new Map < TFile , ParsedNoteData [ ] > ( ) ;
121- for ( const [ file , lines ] of fileLines ) {
122- fileNoteData . set ( file , this . parseDocument ( lines ) )
84+ /**
85+ * Sync all notes from a document. Updates the document lines to include the new note IDs.
86+ * @param file The document to sync
87+ * @returns The number of notes added
88+ */
89+ private async syncSingleFile ( file : TFile ) : Promise < number > {
90+ const originalLines = ( await this . app . vault . read ( file ) ) . split ( "\n" ) ;
91+ const notesData = this . parseDocument ( originalLines ) ;
92+ if ( notesData . length === 0 ) return 0 ;
93+
94+ let totalAdded = 0 ;
95+ let linesModified = false ;
96+ let lines = originalLines ;
97+ for ( const noteData of notesData ) {
98+ const result = await this . syncSingleNote ( noteData , lines ) ;
99+ totalAdded += result . added ;
100+ linesModified = result . linesModified ;
101+ lines = result . lines ;
123102 }
124- return fileNoteData ;
103+
104+ if ( linesModified ) {
105+ await this . app . vault . modify ( file , lines . join ( "\n" ) ) ;
106+ }
107+ return totalAdded ;
125108 }
126109
127- private async readFiles ( vault : Vault , files : TFile [ ] ) {
128- const fileLines = new Map < TFile , string [ ] > ( ) ;
129- for ( const file of files ) {
130- const lines = ( await vault . read ( file ) ) . split ( "\n" ) ;
131- fileLines . set ( file , lines ) ;
110+ /**
111+ * Sync a single extracted note from a document.
112+ * @param noteData The note data to sync
113+ * @param lines The lines of the document
114+ * @returns The note sync result
115+ */
116+ private async syncSingleNote ( noteData : ParsedNoteData , lines : string [ ] ) : Promise < NoteSyncResult > {
117+ if ( noteData . id == undefined ) {
118+ return this . createAndWriteNoteId ( noteData , lines ) ;
119+ }
120+
121+ const ankiNote = await getNoteById ( noteData . id ) ;
122+ if ( ! ankiNote ) {
123+ // Missing note for this ID in Anki (notesInfo returned [] or [{}]). Recreate it.
124+ return this . createAndWriteNoteId ( noteData , lines ) ;
125+ }
126+
127+ const obsidianFields = noteData . note . fields ;
128+ const ankiFields = ankiNote . fields ;
129+ if ( obsidianFields . Front !== ankiFields . Front . value || obsidianFields . Back !== ankiFields . Back . value ) {
130+ await updateNoteById ( ankiNote . noteId , obsidianFields ) ;
132131 }
133- return fileLines ;
132+ return { added : 0 , linesModified : false , lines } ;
134133 }
135134
135+ /**
136+ * Create a new note in Anki and update document lines to include the new note ID.
137+ * @param noteData The note data to create
138+ * @param lines The lines of the document
139+ * @returns The note sync result
140+ */
141+ private async createAndWriteNoteId ( noteData : ParsedNoteData , lines : string [ ] ) : Promise < NoteSyncResult > {
142+ const newId = await this . sendNote ( noteData . note ) ;
143+ const updatedLines = [ ...lines ] ;
144+ updatedLines [ noteData . index ] = `> [!flashcard] %%${ newId } %% ${ noteData . note . fields . Front } ` ;
145+ return { added : 1 , linesModified : true , lines : updatedLines } ;
146+ }
147+
148+ /**
149+ * Check if the target deck exists in Anki and create it if it doesn't.
150+ */
136151 private async addMissingDecks ( ) {
137152 const deckNamesRes = await sendDeckNamesRequest ( ) ;
138153 if ( deckNamesRes . error ) throw new Error ( `AnkiConnect: ${ deckNamesRes . error } ` )
139154 const decks = deckNamesRes . result ;
140- if ( ! decks . contains ( TARGET_DECK ) ) {
155+ if ( ! decks . includes ( TARGET_DECK ) ) {
141156 const createDeckRes = await sendCreateDeckRequest ( TARGET_DECK ) ;
142157 if ( createDeckRes . error ) throw new Error ( `AnkiConnect: ${ createDeckRes . error } ` )
143158 }
0 commit comments