@@ -188,6 +188,115 @@ export default class SimpleAnkiSyncPlugin extends Plugin {
188188 return cells . filter ( ( c ) => c !== '' || cells . length === 1 ) ;
189189 }
190190
191+ private extractInlineNoteId ( back : string ) : { noteId ?: number ; cleanedBack : string } {
192+ const match = back . match ( NOTE_ID_COMMENT ) ;
193+ if ( ! match ) {
194+ return { cleanedBack : back } ;
195+ }
196+
197+ const noteId = parseInt ( match [ 1 ] , 10 ) ;
198+ const trailingPattern = new RegExp (
199+ `(?:<br\\s*\\/?>\\s*)*${ NOTE_ID_COMMENT . source } \\s*$` ,
200+ 'i'
201+ ) ;
202+ let cleanedBack = back ;
203+
204+ if ( trailingPattern . test ( cleanedBack ) ) {
205+ cleanedBack = cleanedBack . replace ( trailingPattern , '' ) ;
206+ cleanedBack = cleanedBack . replace ( / \s + $ / , '' ) ;
207+ } else {
208+ const inlinePattern = new RegExp ( NOTE_ID_COMMENT . source , 'g' ) ;
209+ cleanedBack = cleanedBack . replace ( inlinePattern , '' ) ;
210+ }
211+
212+ return { noteId, cleanedBack } ;
213+ }
214+
215+ private appendNoteIdToRow ( row : string , noteId : number ) : string {
216+ if ( NOTE_ID_COMMENT . test ( row ) ) {
217+ return row ;
218+ }
219+
220+ const match = row . match ( / ^ ( \s * \| ) ( .* ) ( \| \s * ) $ / ) ;
221+ if ( ! match ) {
222+ return row ;
223+ }
224+
225+ const [ , left , cell , right ] = match ;
226+ const trimmedCell = cell . replace ( / \s + $ / , '' ) ;
227+ const spacer = trimmedCell . length ? '<br><br>' : '' ;
228+ const updatedCell = `${ trimmedCell } ${ spacer } <!--ANKI_NOTE_ID:${ noteId } -->` ;
229+ return `${ left } ${ updatedCell } ${ right } ` ;
230+ }
231+
232+ private stripNoteIdFromCell (
233+ cell : string ,
234+ noteId ?: number
235+ ) : { updatedCell : string ; removed : boolean } {
236+ if ( ! cell . includes ( 'ANKI_NOTE_ID' ) ) {
237+ return { updatedCell : cell , removed : false } ;
238+ }
239+ if ( noteId && ! cell . includes ( `<!--ANKI_NOTE_ID:${ noteId } -->` ) ) {
240+ return { updatedCell : cell , removed : false } ;
241+ }
242+
243+ const idPattern = noteId
244+ ? `<!--ANKI_NOTE_ID:${ noteId } -->`
245+ : NOTE_ID_COMMENT . source ;
246+ const trailingPattern = new RegExp (
247+ `(?:<br\\s*\\/?>\\s*)*${ idPattern } \\s*$` ,
248+ 'i'
249+ ) ;
250+ let updatedCell = cell ;
251+
252+ if ( trailingPattern . test ( updatedCell ) ) {
253+ const trimmed = updatedCell . replace ( trailingPattern , '' ) . replace ( / \s + $ / , '' ) ;
254+ return { updatedCell : trimmed , removed : trimmed !== cell } ;
255+ }
256+
257+ const inlinePattern = noteId
258+ ? new RegExp ( `<!--ANKI_NOTE_ID:${ noteId } -->` , 'g' )
259+ : new RegExp ( NOTE_ID_COMMENT . source , 'g' ) ;
260+ updatedCell = updatedCell . replace ( inlinePattern , '' ) ;
261+ return { updatedCell, removed : updatedCell !== cell } ;
262+ }
263+
264+ private stripNoteIdFromLine (
265+ line : string ,
266+ noteId ?: number
267+ ) : { updatedLine : string ; removed : boolean ; removeLine : boolean } {
268+ if ( ! line . includes ( 'ANKI_NOTE_ID' ) ) {
269+ return { updatedLine : line , removed : false , removeLine : false } ;
270+ }
271+
272+ const trimmed = line . trim ( ) ;
273+ const linePattern = noteId
274+ ? new RegExp ( `^<!--ANKI_NOTE_ID:${ noteId } -->$` )
275+ : new RegExp ( `^${ NOTE_ID_COMMENT . source } $` ) ;
276+ if ( linePattern . test ( trimmed ) ) {
277+ return { updatedLine : '' , removed : true , removeLine : true } ;
278+ }
279+
280+ const rowMatch = line . match ( / ^ ( \s * \| ) ( .* ) ( \| \s * ) $ / ) ;
281+ if ( rowMatch ) {
282+ const [ , left , cell , right ] = rowMatch ;
283+ const { updatedCell, removed } = this . stripNoteIdFromCell ( cell , noteId ) ;
284+ if ( removed ) {
285+ return {
286+ updatedLine : `${ left } ${ updatedCell } ${ right } ` ,
287+ removed : true ,
288+ removeLine : false ,
289+ } ;
290+ }
291+ }
292+
293+ const inlinePattern = noteId
294+ ? new RegExp ( `<!--ANKI_NOTE_ID:${ noteId } -->` , 'g' )
295+ : new RegExp ( NOTE_ID_COMMENT . source , 'g' ) ;
296+ const updatedLine = line . replace ( inlinePattern , '' ) ;
297+ return { updatedLine, removed : updatedLine !== line , removeLine : false } ;
298+ }
299+
191300 // Parses the content of a file and extracts notes and deck name
192301 private parseNotesFromContent (
193302 content : string ,
@@ -223,11 +332,18 @@ export default class SimpleAnkiSyncPlugin extends Plugin {
223332
224333 let existingId : number | undefined ;
225334 let endLine = i + 2 ;
226- const maybeComment = lines [ i + 3 ] ;
227- const idMatch = maybeComment ?. match ( NOTE_ID_COMMENT ) ;
228- if ( idMatch ) {
229- existingId = parseInt ( idMatch [ 1 ] , 10 ) ;
230- endLine = i + 3 ;
335+
336+ const inlineId = this . extractInlineNoteId ( dataCells [ 0 ] ) ;
337+ dataCells [ 0 ] = inlineId . cleanedBack ;
338+ if ( inlineId . noteId ) {
339+ existingId = inlineId . noteId ;
340+ } else {
341+ const maybeComment = lines [ i + 3 ] ;
342+ const idMatch = maybeComment ?. match ( NOTE_ID_COMMENT ) ;
343+ if ( idMatch ) {
344+ existingId = parseInt ( idMatch [ 1 ] , 10 ) ;
345+ endLine = i + 3 ;
346+ }
231347 }
232348
233349 notes . push ( {
@@ -309,7 +425,6 @@ export default class SimpleAnkiSyncPlugin extends Plugin {
309425 for ( const m of orig . matchAll ( NOTE_ID_COMMENT_GLOBAL ) ) existingIds . push ( + m [ 1 ] ) ;
310426
311427 const lines = orig . split ( '\n' ) ;
312- let offset = 0 ;
313428 const newIds : number [ ] = [ ] ;
314429
315430 for ( const note of notes ) {
@@ -348,8 +463,9 @@ export default class SimpleAnkiSyncPlugin extends Plugin {
348463 const created = await this . anki . addNote ( deckName , DEFAULT_MODEL , fields ) ;
349464 if ( created ) {
350465 newIds . push ( created ) ;
351- lines . splice ( note . endLine + 1 + offset , 0 , `<!--ANKI_NOTE_ID:${ created } -->` ) ;
352- offset ++ ;
466+ const dataRowIndex = note . startLine + 2 ;
467+ const updatedRow = this . appendNoteIdToRow ( lines [ dataRowIndex ] , created ) ;
468+ lines [ dataRowIndex ] = updatedRow ;
353469 }
354470 }
355471 }
@@ -359,11 +475,14 @@ export default class SimpleAnkiSyncPlugin extends Plugin {
359475 if ( toDelete . length ) {
360476 await this . anki . deleteNotes ( toDelete ) ;
361477 for ( const id of toDelete ) {
362- const commentLine = `<!--ANKI_NOTE_ID:${ id } -->` ;
363- const idx = lines . findIndex ( l => l . trim ( ) === commentLine ) ;
364- if ( idx !== - 1 ) {
365- lines . splice ( idx , 1 ) ;
366- if ( idx < notes . length ) offset -- ;
478+ for ( let idx = lines . length - 1 ; idx >= 0 ; idx -- ) {
479+ const result = this . stripNoteIdFromLine ( lines [ idx ] , id ) ;
480+ if ( ! result . removed ) continue ;
481+ if ( result . removeLine ) {
482+ lines . splice ( idx , 1 ) ;
483+ } else {
484+ lines [ idx ] = result . updatedLine ;
485+ }
367486 }
368487 }
369488 }
@@ -412,13 +531,16 @@ export default class SimpleAnkiSyncPlugin extends Plugin {
412531 // Remove all IDs and Anki-Cards
413532 if ( existingIds . length ) {
414533 await this . anki . deleteNotes ( existingIds ) ;
415- for ( const id of existingIds ) {
416- const commentLine = `<!--ANKI_NOTE_ID: ${ id } -->` ;
417- const idx = lines . findIndex ( l => l . trim ( ) === commentLine ) ;
418- if ( idx !== - 1 ) {
419- lines . splice ( idx , 1 ) ;
534+ const cleaned : string [ ] = [ ] ;
535+ for ( const line of lines ) {
536+ const result = this . stripNoteIdFromLine ( line ) ;
537+ if ( result . removeLine ) {
538+ continue ;
420539 }
540+ cleaned . push ( result . updatedLine ) ;
421541 }
542+ lines . length = 0 ;
543+ lines . push ( ...cleaned ) ;
422544 }
423545
424546 const updated = lines . join ( '\n' ) ;
0 commit comments