yanki 2.0.6 → 2.0.7

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.
@@ -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 Anki-Connect.
102
+ * communicating with AnkiConnect.
103
103
  */
104
104
  fetchAdapter: FetchAdapter | undefined;
105
105
  fileAdapter: FileAdapter | undefined;
package/dist/lib/index.js CHANGED
@@ -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 result = {
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
- }[mimeType];
505
- if (result === void 0) return;
506
- return result;
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
- return getFileExtensionForMimeType(contentTypeHeaderValue);
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
  }
@@ -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.model.createModel(model);
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.model.createModel(model);
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 local note IDs to (attempt) to fetch
1327
+ * @param noteIds An array of note IDs (from `findNotes`) to fetch
1321
1328
  *
1322
- * @returns Array of YankiNote objects, with undefined for notes that could not
1323
- * be found.
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.model.createModel(model);
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 Anki-Connect.
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 Anki-Connect add-on configuration options.");
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.3/node_modules/yanki-connect/dist/index.d.ts
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
- * Anki-Connect service to work.
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 Anki-Connect.
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 Anki-Connect service is running.
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
- * Anki-Connect security key (optional)
1378
+ * AnkiConnect security key (optional)
1379
1379
  *
1380
1380
  * @default undefined
1381
1381
  */
1382
1382
  key: string | undefined;
1383
1383
  /**
1384
- * Port where the Anki-Connect service is running.
1384
+ * Port where the AnkiConnect service is running.
1385
1385
  *
1386
1386
  * @default 8765
1387
1387
  */
1388
1388
  port: number;
1389
1389
  /**
1390
- * Anki-Connect API version.
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 Anki-Connect.
1497
+ * communicating with AnkiConnect.
1498
1498
  */
1499
1499
  fetchAdapter: FetchAdapter | undefined;
1500
1500
  fileAdapter: FileAdapter | undefined;