yanki 2.0.6 → 2.0.8
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 +89 -89
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.js +30 -21
- package/dist/standalone/index.d.ts +8 -8
- package/dist/standalone/index.js +89 -89
- package/package.json +3 -2
- package/readme.md +13 -13
package/dist/lib/index.d.ts
CHANGED
|
@@ -99,7 +99,7 @@ type GlobalOptions = {
|
|
|
99
99
|
* Exposed for Obsidian, currently only used for getting URL content hashes
|
|
100
100
|
* and inferring MIME types of URLs without extensions. Note that
|
|
101
101
|
* ankiConnectOptions ALSO has a fetch adapter option specifically for
|
|
102
|
-
* communicating with
|
|
102
|
+
* communicating with AnkiConnect.
|
|
103
103
|
*/
|
|
104
104
|
fetchAdapter: FetchAdapter | undefined;
|
|
105
105
|
fileAdapter: FileAdapter | undefined;
|
package/dist/lib/index.js
CHANGED
|
@@ -39,7 +39,7 @@ import rehypeParse from "rehype-parse";
|
|
|
39
39
|
import rehypeRaw from "rehype-raw";
|
|
40
40
|
import rehypeStringify from "rehype-stringify";
|
|
41
41
|
import remarkRehype from "remark-rehype";
|
|
42
|
-
import {
|
|
42
|
+
import { createHighlighterCoreSync } from "shiki/core";
|
|
43
43
|
import { createJavaScriptRegexEngine } from "shiki/engine/javascript";
|
|
44
44
|
import { unified } from "unified";
|
|
45
45
|
import { u } from "unist-builder";
|
|
@@ -465,7 +465,7 @@ function getSlugifiedNamespace(namespace) {
|
|
|
465
465
|
* Only supports MIMEs for valid Anki media types.
|
|
466
466
|
*/
|
|
467
467
|
function getFileExtensionForMimeType(mimeType) {
|
|
468
|
-
const
|
|
468
|
+
const mimeToExtension = {
|
|
469
469
|
"application/octet-stream": "mp4",
|
|
470
470
|
"application/ogg": "ogx",
|
|
471
471
|
"application/pdf": "pdf",
|
|
@@ -501,9 +501,10 @@ function getFileExtensionForMimeType(mimeType) {
|
|
|
501
501
|
"video/x-flv": "flv",
|
|
502
502
|
"video/x-matroska": "mkv",
|
|
503
503
|
"video/x-msvideo": "avi"
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
return
|
|
504
|
+
};
|
|
505
|
+
const normalizedMimeType = mimeType.split(";")[0].trim().toLowerCase();
|
|
506
|
+
if (normalizedMimeType === "") return;
|
|
507
|
+
return mimeToExtension[normalizedMimeType];
|
|
507
508
|
}
|
|
508
509
|
//#endregion
|
|
509
510
|
//#region src/lib/utilities/path.ts
|
|
@@ -681,7 +682,9 @@ async function getFileExtensionFromUrl(url, fetchAdapter, mode = MEDIA_URL_CONTE
|
|
|
681
682
|
try {
|
|
682
683
|
const contentTypeHeaderValue = getHeadersString((await fetchAdapter(url, { method: "HEAD" }))?.headers, ["content-type"]);
|
|
683
684
|
if (contentTypeHeaderValue === void 0) throw new Error("No content-type header found");
|
|
684
|
-
|
|
685
|
+
const extension = getFileExtensionForMimeType(contentTypeHeaderValue);
|
|
686
|
+
if (extension !== void 0) return extension;
|
|
687
|
+
return await getFileExtensionFromUrl(url, fetchAdapter, "name");
|
|
685
688
|
} catch {
|
|
686
689
|
return getFileExtensionFromUrl(url, fetchAdapter, "name");
|
|
687
690
|
}
|
|
@@ -836,7 +839,7 @@ const plugin$2 = function() {
|
|
|
836
839
|
//#endregion
|
|
837
840
|
//#region src/lib/parse/rehype-utilities.ts
|
|
838
841
|
const DIMENSION_CHARS_REGEX = /^[\dx]+$/;
|
|
839
|
-
const highlighter =
|
|
842
|
+
const highlighter = createHighlighterCoreSync({
|
|
840
843
|
engine: createJavaScriptRegexEngine(),
|
|
841
844
|
langs: [
|
|
842
845
|
bash,
|
|
@@ -1161,6 +1164,14 @@ const yankiModelNames = yankiModels.map((model) => model.modelName);
|
|
|
1161
1164
|
const legacyYankiModelNames = ["Yanki - Basic (and reversed card)"];
|
|
1162
1165
|
//#endregion
|
|
1163
1166
|
//#region src/lib/utilities/anki-connect.ts
|
|
1167
|
+
async function ensureModelExists(client, model) {
|
|
1168
|
+
try {
|
|
1169
|
+
await client.model.createModel(model);
|
|
1170
|
+
} catch (error) {
|
|
1171
|
+
if (error instanceof Error && error.message === "Model name already exists") return;
|
|
1172
|
+
throw error;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1164
1175
|
async function deleteNotes(client, notes, dryRun = false) {
|
|
1165
1176
|
if (dryRun) return;
|
|
1166
1177
|
const noteIds = notes.map((note) => note.noteId).filter((noteId) => noteId !== void 0);
|
|
@@ -1193,7 +1204,7 @@ async function addNote(client, note, dryRun, fileAdapter) {
|
|
|
1193
1204
|
if (error.message === `model was not found: ${note.modelName}`) {
|
|
1194
1205
|
const model = yankiModels.find((model) => model.modelName === note.modelName);
|
|
1195
1206
|
if (model === void 0) throw new Error(`Model not found: ${note.modelName}`);
|
|
1196
|
-
await client
|
|
1207
|
+
await ensureModelExists(client, model);
|
|
1197
1208
|
return addNote(client, note, dryRun, fileAdapter);
|
|
1198
1209
|
}
|
|
1199
1210
|
if (error.message === `deck was not found: ${note.deckName}`) {
|
|
@@ -1242,7 +1253,7 @@ async function updateNote(client, localNote, remoteNote, dryRun, fileAdapter) {
|
|
|
1242
1253
|
if (error.message === `Model '${localNote.modelName}' not found`) {
|
|
1243
1254
|
const model = yankiModels.find((model) => model.modelName === localNote.modelName);
|
|
1244
1255
|
if (model === void 0) throw new Error(`Model not found: ${localNote.modelName}`);
|
|
1245
|
-
await client
|
|
1256
|
+
await ensureModelExists(client, model);
|
|
1246
1257
|
return updateNote(client, localNote, remoteNote, dryRun, fileAdapter);
|
|
1247
1258
|
}
|
|
1248
1259
|
throw error;
|
|
@@ -1304,23 +1315,21 @@ function areTagsEqual(localTags, remoteTags) {
|
|
|
1304
1315
|
* @throws {Error}
|
|
1305
1316
|
*/
|
|
1306
1317
|
async function getRemoteNotes(client, namespace = defaultGlobalOptions.namespace) {
|
|
1307
|
-
return await getRemoteNotesById(client, await client.note.findNotes({ query: `"YankiNamespace:${namespace}"` }));
|
|
1318
|
+
return (await getRemoteNotesById(client, await client.note.findNotes({ query: `"YankiNamespace:${namespace}"` }))).filter((note) => note !== void 0);
|
|
1308
1319
|
}
|
|
1309
1320
|
/**
|
|
1310
1321
|
* Get all data from Anki required to populate the YankiNote type.
|
|
1311
1322
|
*
|
|
1312
1323
|
* Handles some extra footwork to identify the deck name and validate the model
|
|
1313
|
-
* name. There's no way to get everything we need in one shot from
|
|
1314
|
-
* Anki-Connect.
|
|
1315
|
-
*
|
|
1316
|
-
* Undefined elements in the returned array are subsequently used to identify
|
|
1317
|
-
* notes that need to be created.
|
|
1324
|
+
* name. There's no way to get everything we need in one shot from AnkiConnect.
|
|
1318
1325
|
*
|
|
1319
1326
|
* @param client An instance of YankiConnect
|
|
1320
|
-
* @param noteIds An array of
|
|
1327
|
+
* @param noteIds An array of note IDs (from `findNotes`) to fetch
|
|
1321
1328
|
*
|
|
1322
|
-
* @returns Array
|
|
1323
|
-
*
|
|
1329
|
+
* @returns Array positionally aligned with `noteIds`. Entries are `undefined`
|
|
1330
|
+
* when `notesInfo` does not resolve the corresponding ID — `findNotes` can
|
|
1331
|
+
* return phantom IDs (e.g. left behind by `addNote` retries) that no longer
|
|
1332
|
+
* exist as notes.
|
|
1324
1333
|
* @throws {Error} If an unknown model name or multiple decks are found for a
|
|
1325
1334
|
* note, or if no deck is found.
|
|
1326
1335
|
*/
|
|
@@ -1444,7 +1453,7 @@ async function updateModelStyle(client, modelName, css, dryRun) {
|
|
|
1444
1453
|
const model = yankiModels.find((model) => model.modelName === modelName);
|
|
1445
1454
|
if (model === void 0) throw new Error(`Model not found: ${modelName}`);
|
|
1446
1455
|
if (dryRun) return false;
|
|
1447
|
-
await client
|
|
1456
|
+
await ensureModelExists(client, model);
|
|
1448
1457
|
return updateModelStyle(client, model.modelName, css, dryRun);
|
|
1449
1458
|
}
|
|
1450
1459
|
throw error;
|
|
@@ -1536,7 +1545,7 @@ async function reconcileMedia(client, liveNotes, namespace, dryRun, fileAdapter)
|
|
|
1536
1545
|
};
|
|
1537
1546
|
}
|
|
1538
1547
|
/**
|
|
1539
|
-
* Request permission to access Anki through
|
|
1548
|
+
* Request permission to access Anki through AnkiConnect.
|
|
1540
1549
|
*
|
|
1541
1550
|
* @returns 'ankiUnreachable' if Anki is not open, or 'granted' if everything is
|
|
1542
1551
|
* copacetic
|
|
@@ -1545,7 +1554,7 @@ async function reconcileMedia(client, liveNotes, namespace, dryRun, fileAdapter)
|
|
|
1545
1554
|
async function requestPermission(client) {
|
|
1546
1555
|
try {
|
|
1547
1556
|
const { permission } = await client.miscellaneous.requestPermission();
|
|
1548
|
-
if (permission === "denied") throw new Error("Permission denied, please add this source to the \"webCorsOriginList\" in the
|
|
1557
|
+
if (permission === "denied") throw new Error("Permission denied, please add this source to the \"webCorsOriginList\" in the AnkiConnect add-on configuration options.");
|
|
1549
1558
|
else return "granted";
|
|
1550
1559
|
} catch (error) {
|
|
1551
1560
|
if (error instanceof Error && (error.message === "fetch failed" || error.message === "net::ERR_CONNECTION_REFUSED")) return "ankiUnreachable";
|
|
@@ -796,7 +796,7 @@ Same as `PartialDeep`, but accepts only `object`s as inputs. Internal helper for
|
|
|
796
796
|
*/
|
|
797
797
|
type PartialObjectDeep<ObjectType extends object, Options extends Required<PartialDeepOptions>> = { [KeyType in keyof ObjectType]?: _PartialDeep<ObjectType[KeyType], Options> };
|
|
798
798
|
//#endregion
|
|
799
|
-
//#region node_modules/.pnpm/yanki-connect@4.0.
|
|
799
|
+
//#region node_modules/.pnpm/yanki-connect@4.0.4/node_modules/yanki-connect/dist/index.d.ts
|
|
800
800
|
//#region src/types/deck.d.ts
|
|
801
801
|
type DeckStats = {
|
|
802
802
|
deck_id: number;
|
|
@@ -1347,7 +1347,7 @@ type YankiConnectOptions = {
|
|
|
1347
1347
|
* instantiated.
|
|
1348
1348
|
*
|
|
1349
1349
|
* The Anki desktop app must be running for the client and the underlying
|
|
1350
|
-
*
|
|
1350
|
+
* AnkiConnect service to work.
|
|
1351
1351
|
*
|
|
1352
1352
|
* Currently supported on macOS only.
|
|
1353
1353
|
*
|
|
@@ -1358,7 +1358,7 @@ type YankiConnectOptions = {
|
|
|
1358
1358
|
autoLaunch: 'immediately' | boolean;
|
|
1359
1359
|
/**
|
|
1360
1360
|
* Advanced option to customize the resource fetch implementation used to make
|
|
1361
|
-
* requests to
|
|
1361
|
+
* requests to AnkiConnect.
|
|
1362
1362
|
*
|
|
1363
1363
|
* Note that the signature reflects the subset of the built-in Fetch interface
|
|
1364
1364
|
* that's actually used by yanki-connect.
|
|
@@ -1369,25 +1369,25 @@ type YankiConnectOptions = {
|
|
|
1369
1369
|
*/
|
|
1370
1370
|
fetchAdapter: undefined | YankiFetchAdapter;
|
|
1371
1371
|
/**
|
|
1372
|
-
* Host where the
|
|
1372
|
+
* Host where the AnkiConnect service is running.
|
|
1373
1373
|
*
|
|
1374
1374
|
* @default 'http://127.0.0.1'
|
|
1375
1375
|
*/
|
|
1376
1376
|
host: string;
|
|
1377
1377
|
/**
|
|
1378
|
-
*
|
|
1378
|
+
* AnkiConnect security key (optional)
|
|
1379
1379
|
*
|
|
1380
1380
|
* @default undefined
|
|
1381
1381
|
*/
|
|
1382
1382
|
key: string | undefined;
|
|
1383
1383
|
/**
|
|
1384
|
-
* Port where the
|
|
1384
|
+
* Port where the AnkiConnect service is running.
|
|
1385
1385
|
*
|
|
1386
1386
|
* @default 8765
|
|
1387
1387
|
*/
|
|
1388
1388
|
port: number;
|
|
1389
1389
|
/**
|
|
1390
|
-
*
|
|
1390
|
+
* AnkiConnect API version.
|
|
1391
1391
|
*
|
|
1392
1392
|
* Only API version 6 is supported for now.
|
|
1393
1393
|
*
|
|
@@ -1494,7 +1494,7 @@ type GlobalOptions = {
|
|
|
1494
1494
|
* Exposed for Obsidian, currently only used for getting URL content hashes
|
|
1495
1495
|
* and inferring MIME types of URLs without extensions. Note that
|
|
1496
1496
|
* ankiConnectOptions ALSO has a fetch adapter option specifically for
|
|
1497
|
-
* communicating with
|
|
1497
|
+
* communicating with AnkiConnect.
|
|
1498
1498
|
*/
|
|
1499
1499
|
fetchAdapter: FetchAdapter | undefined;
|
|
1500
1500
|
fileAdapter: FileAdapter | undefined;
|