Skip to content

Commit 09b1f02

Browse files
committed
Release 1.4.0
1 parent 5433763 commit 09b1f02

3 files changed

Lines changed: 142 additions & 20 deletions

File tree

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "simple-anki-sync",
33
"name": "Simple Anki Sync",
4-
"version": "1.3.0",
4+
"version": "1.4.0",
55
"minAppVersion": "0.15.0",
66
"description": "The simplest way of syncing simple Flashchards with Anki.",
77
"author": "Lukas Mayr",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "simple-anki-sync",
3-
"version": "1.3.0",
3+
"version": "1.4.0",
44
"description": "Personal Anki Sync Plugin for Obsidian.",
55
"main": "main.js",
66
"scripts": {

src/main.ts

Lines changed: 140 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)