yanki 2.0.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/cli.js +73 -73
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.js +44 -12
- package/dist/standalone/index.d.ts +1 -0
- package/dist/standalone/index.js +95 -95
- package/package.json +4 -4
- package/readme.md +8 -8
- package/dist/.DS_Store +0 -0
package/dist/lib/index.d.ts
CHANGED
package/dist/lib/index.js
CHANGED
|
@@ -534,8 +534,8 @@ function resolveWithBasePath(filePath, options) {
|
|
|
534
534
|
return path.join(cwd, filePath);
|
|
535
535
|
}
|
|
536
536
|
function stripBasePath(filePath, basePath) {
|
|
537
|
-
|
|
538
|
-
return filePath
|
|
537
|
+
if (filePath.toLowerCase().startsWith(basePath.toLowerCase())) return filePath.slice(basePath.length);
|
|
538
|
+
return filePath;
|
|
539
539
|
}
|
|
540
540
|
function getBaseAndQueryParts(filePath) {
|
|
541
541
|
const directoryPath = path.dirname(filePath);
|
|
@@ -1318,7 +1318,7 @@ async function deleteOrphanedDecks(client, activeNotes, originalNotes, dryRun) {
|
|
|
1318
1318
|
while (parts.length > 1) {
|
|
1319
1319
|
parts.pop();
|
|
1320
1320
|
const parentDeckName = parts.join("::");
|
|
1321
|
-
if (activeNoteDeckNames.some((deckName) =>
|
|
1321
|
+
if (activeNoteDeckNames.some((deckName) => deckName.startsWith(`${parentDeckName}::`))) break;
|
|
1322
1322
|
orphanedParentDeckNames.push(parentDeckName);
|
|
1323
1323
|
}
|
|
1324
1324
|
}
|
|
@@ -1409,21 +1409,49 @@ async function uploadMediaForNote(client, note, dryRun, fileAdapter) {
|
|
|
1409
1409
|
}
|
|
1410
1410
|
return uploadedMedia;
|
|
1411
1411
|
}
|
|
1412
|
-
async function
|
|
1413
|
-
if (dryRun) return
|
|
1412
|
+
async function reconcileMedia(client, liveNotes, namespace, dryRun, fileAdapter) {
|
|
1413
|
+
if (dryRun) return {
|
|
1414
|
+
deleted: [],
|
|
1415
|
+
reuploaded: []
|
|
1416
|
+
};
|
|
1414
1417
|
const slugifiedNamespace = getSlugifiedNamespace(namespace);
|
|
1415
|
-
const
|
|
1418
|
+
const expectedMedia = [];
|
|
1416
1419
|
for (const note of liveNotes) {
|
|
1417
1420
|
const mediaPaths = extractMediaFromHtml(`${note.fields.Front}\n${note.fields.Back}\n${note.fields.Extra}`);
|
|
1418
|
-
for (const
|
|
1421
|
+
for (const media of mediaPaths) expectedMedia.push(media);
|
|
1419
1422
|
}
|
|
1423
|
+
const activeMediaFilenames = new Set(expectedMedia.map(({ filename }) => filename));
|
|
1420
1424
|
const allMediaInNamespace = await client.media.getMediaFilesNames({ pattern: `${slugifiedNamespace}-*` });
|
|
1421
1425
|
const deletedMediaFilenames = [];
|
|
1422
|
-
for (const remoteMediaFilename of allMediaInNamespace) if (!activeMediaFilenames.
|
|
1426
|
+
for (const remoteMediaFilename of allMediaInNamespace) if (!activeMediaFilenames.has(remoteMediaFilename)) {
|
|
1423
1427
|
await client.media.deleteMediaFile({ filename: remoteMediaFilename });
|
|
1424
1428
|
deletedMediaFilenames.push(remoteMediaFilename);
|
|
1425
1429
|
}
|
|
1426
|
-
|
|
1430
|
+
const reuploadedMediaFilenames = [];
|
|
1431
|
+
for (const { filename, originalSrc } of expectedMedia) if (!allMediaInNamespace.includes(filename)) try {
|
|
1432
|
+
if (isUrl(originalSrc)) {
|
|
1433
|
+
await client.media.storeMediaFile({
|
|
1434
|
+
deleteExisting: true,
|
|
1435
|
+
filename,
|
|
1436
|
+
url: originalSrc
|
|
1437
|
+
});
|
|
1438
|
+
reuploadedMediaFilenames.push(filename);
|
|
1439
|
+
} else if (fileAdapter === void 0) console.warn(`Could not re-upload local media file "${filename}": no file adapter provided`);
|
|
1440
|
+
else {
|
|
1441
|
+
await client.media.storeMediaFile({
|
|
1442
|
+
data: uint8ArrayToBase64(await fileAdapter.readFileBuffer(originalSrc)),
|
|
1443
|
+
deleteExisting: true,
|
|
1444
|
+
filename
|
|
1445
|
+
});
|
|
1446
|
+
reuploadedMediaFilenames.push(filename);
|
|
1447
|
+
}
|
|
1448
|
+
} catch (error) {
|
|
1449
|
+
console.warn(`Anki could not re-upload media file: "${filename}"\n${String(error)}`);
|
|
1450
|
+
}
|
|
1451
|
+
return {
|
|
1452
|
+
deleted: deletedMediaFilenames,
|
|
1453
|
+
reuploaded: reuploadedMediaFilenames
|
|
1454
|
+
};
|
|
1427
1455
|
}
|
|
1428
1456
|
/**
|
|
1429
1457
|
* Request permission to access Anki through Anki-Connect.
|
|
@@ -1467,7 +1495,7 @@ async function cleanNotes(options) {
|
|
|
1467
1495
|
const remoteNotes = await getRemoteNotes(client, sanitizedNamespace);
|
|
1468
1496
|
await deleteNotes(client, remoteNotes, dryRun);
|
|
1469
1497
|
const deletedDecks = await deleteOrphanedDecks(client, [], remoteNotes, dryRun);
|
|
1470
|
-
const deletedMedia = await
|
|
1498
|
+
const { deleted: deletedMedia } = await reconcileMedia(client, [], sanitizedNamespace, dryRun);
|
|
1471
1499
|
const isChanged = remoteNotes.length > 0 || deletedDecks.length > 0;
|
|
1472
1500
|
if (!dryRun && ankiWeb && (isChanged || SYNC_TO_ANKI_WEB_EVEN_IF_UNCHANGED)) await syncToAnkiWeb(client);
|
|
1473
1501
|
return {
|
|
@@ -2712,6 +2740,7 @@ async function syncNotes(allLocalNotes, options) {
|
|
|
2712
2740
|
duration: performance.now() - startTime,
|
|
2713
2741
|
fixedDatabase: false,
|
|
2714
2742
|
namespace: sanitizedNamespace,
|
|
2743
|
+
reuploadedMedia: [],
|
|
2715
2744
|
synced: allLocalNotesCopy.map((note) => ({
|
|
2716
2745
|
action: "ankiUnreachable",
|
|
2717
2746
|
note
|
|
@@ -2784,7 +2813,7 @@ async function syncNotes(allLocalNotes, options) {
|
|
|
2784
2813
|
}
|
|
2785
2814
|
}
|
|
2786
2815
|
}
|
|
2787
|
-
const deletedMedia = await
|
|
2816
|
+
const { deleted: deletedMedia, reuploaded: reuploadedMedia } = await reconcileMedia(client, liveNotes, sanitizedNamespace, dryRun, fileAdapter ?? void 0);
|
|
2788
2817
|
const isChanged = deletedDecks.length > 0 || synced.some((note) => note.action !== "unchanged");
|
|
2789
2818
|
if (!dryRun && ankiWeb && (isChanged || SYNC_TO_ANKI_WEB_EVEN_IF_UNCHANGED)) await syncToAnkiWeb(client);
|
|
2790
2819
|
return {
|
|
@@ -2795,6 +2824,7 @@ async function syncNotes(allLocalNotes, options) {
|
|
|
2795
2824
|
duration: performance.now() - startTime,
|
|
2796
2825
|
fixedDatabase,
|
|
2797
2826
|
namespace: sanitizedNamespace,
|
|
2827
|
+
reuploadedMedia,
|
|
2798
2828
|
synced
|
|
2799
2829
|
};
|
|
2800
2830
|
}
|
|
@@ -2866,7 +2896,7 @@ async function syncFiles(allLocalFilePaths, options) {
|
|
|
2866
2896
|
for (const [index, renamedNote] of renamedLocalNotes.entries()) renamedNote.note = reloadedLocalNotes[index].note;
|
|
2867
2897
|
}
|
|
2868
2898
|
}
|
|
2869
|
-
const { deletedDecks, deletedMedia, fixedDatabase, synced } = await syncNotes(renamedLocalNotes.map((note) => note.note), {
|
|
2899
|
+
const { deletedDecks, deletedMedia, fixedDatabase, reuploadedMedia, synced } = await syncNotes(renamedLocalNotes.map((note) => note.note), {
|
|
2870
2900
|
ankiConnectOptions,
|
|
2871
2901
|
ankiWeb,
|
|
2872
2902
|
checkDatabase,
|
|
@@ -2899,6 +2929,7 @@ async function syncFiles(allLocalFilePaths, options) {
|
|
|
2899
2929
|
duration: performance.now() - startTime,
|
|
2900
2930
|
fixedDatabase,
|
|
2901
2931
|
namespace,
|
|
2932
|
+
reuploadedMedia,
|
|
2902
2933
|
synced: syncedAndSorted
|
|
2903
2934
|
};
|
|
2904
2935
|
}
|
|
@@ -2919,6 +2950,7 @@ function formatSyncFilesResult(result, verbose = false) {
|
|
|
2919
2950
|
if (totalRenamed > 0) lines.push("", `Local notes renamed: ${totalRenamed}`);
|
|
2920
2951
|
if (result.deletedDecks.length > 0) lines.push("", `Decks pruned: ${result.deletedDecks.length}`);
|
|
2921
2952
|
if (result.deletedMedia.length > 0) lines.push("", `Media assets deleted: ${result.deletedMedia.length}`);
|
|
2953
|
+
if (result.reuploadedMedia.length > 0) lines.push("", `Media assets re-uploaded: ${result.reuploadedMedia.length}`);
|
|
2922
2954
|
if (!result.dryRun) lines.push("", `Database automatically fixed: ${result.fixedDatabase ? "Yes" : "No"}`);
|
|
2923
2955
|
lines.push("", result.dryRun ? "Sync Plan Details:" : "Sync Details:");
|
|
2924
2956
|
for (const { action, filePath, note } of synced) if (filePath === void 0) lines.push(` Note ID ${note.noteId} ${capitalize(action)} (From Anki)`);
|