yanki 2.0.8 → 2.0.10

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/lib/index.js CHANGED
@@ -91,7 +91,7 @@ function truncateOnWordBoundary(text, maxLength, truncationString = "...", wordB
91
91
  return `${words.join(wordBoundary).slice(0, maxLengthSafe)}${truncationString}`;
92
92
  }
93
93
  function cleanClassName(className) {
94
- return className.toLowerCase().replaceAll(/[^\da-z]/gi, " ").trim().replaceAll(/ +/g, "-");
94
+ return className.toLowerCase().replaceAll(/* @__PURE__ */ new RegExp("[^\\dA-Za-z]", "gv"), " ").trim().replaceAll(/* @__PURE__ */ new RegExp(" +", "gv"), "-");
95
95
  }
96
96
  function emptyIsUndefined(text) {
97
97
  if (text === void 0) return;
@@ -106,12 +106,12 @@ function emptyIsUndefined(text) {
106
106
  function css$1(strings, ...values) {
107
107
  return trimLeadingIndentation(strings, ...values);
108
108
  }
109
- const NEWLINE_REGEX$1 = /\r?\n/;
110
- const LEADING_WHITESPACE_REGEX = /^(\s+)/;
109
+ const NEWLINE_REGEX$1 = /* @__PURE__ */ new RegExp("\\r?\\n", "v");
110
+ const LEADING_WHITESPACE_REGEX = /* @__PURE__ */ new RegExp("^(\\s+)", "v");
111
111
  function trimLeadingIndentation(strings, ...values) {
112
112
  const lines = strings.reduce((result, text, i) => `${result}${text}${String(values[i] ?? "")}`, "").split(NEWLINE_REGEX$1).filter((line) => line.trim() !== "");
113
113
  const leadingSpace = LEADING_WHITESPACE_REGEX.exec(lines[0])?.[0] ?? "";
114
- const leadingSpaceRegex = new RegExp(`^${leadingSpace}`);
114
+ const leadingSpaceRegex = new RegExp(`^${leadingSpace}`, "v");
115
115
  return lines.map((line) => line.replace(leadingSpaceRegex, "").trimEnd()).join("\n");
116
116
  }
117
117
  function splitAtFirstMatch(text, regex) {
@@ -247,7 +247,7 @@ const MEDIA_SUPPORTED_EXTENSIONS = [
247
247
  //#endregion
248
248
  //#region src/lib/utilities/platform.ts
249
249
  const ENVIRONMENT = typeof window === "undefined" ? typeof process === "undefined" ? "other" : "node" : "browser";
250
- ENVIRONMENT === "browser" ? /windows/i.test(navigator.userAgent) || /mac/i.test(navigator.userAgent) || /linux/i.test(navigator.userAgent) || /ubuntu/i.test(navigator.userAgent) : ENVIRONMENT === "node" && (process.platform === "win32" || process.platform === "darwin" || process.platform);
250
+ ENVIRONMENT === "browser" ? (/* @__PURE__ */ new RegExp("windows", "iv")).test(navigator.userAgent) || (/* @__PURE__ */ new RegExp("mac", "iv")).test(navigator.userAgent) || (/* @__PURE__ */ new RegExp("linux", "iv")).test(navigator.userAgent) || (/* @__PURE__ */ new RegExp("ubuntu", "iv")).test(navigator.userAgent) : ENVIRONMENT === "node" && (process.platform === "win32" || process.platform === "darwin" || process.platform);
251
251
  //#endregion
252
252
  //#region src/lib/shared/types.ts
253
253
  const defaultGlobalOptions = {
@@ -312,7 +312,7 @@ async function getFileContentHash(absoluteFilePath, fileAdapter, mode = MEDIA_DE
312
312
  case "metadata": {
313
313
  const { mtimeMs, size } = await fileAdapter.stat(absoluteFilePath);
314
314
  const stringToHash = `${mtimeMs ?? ""}${size ?? ""}`;
315
- if (stringToHash === "") return getFileContentHash(absoluteFilePath, fileAdapter, "name");
315
+ if (stringToHash === "") return getHash(absoluteFilePath, 16);
316
316
  return getHash(stringToHash, 16);
317
317
  }
318
318
  case "name": return getHash(absoluteFilePath, 16);
@@ -320,88 +320,88 @@ async function getFileContentHash(absoluteFilePath, fileAdapter, mode = MEDIA_DE
320
320
  }
321
321
  //#endregion
322
322
  //#region src/lib/utilities/namespace.ts
323
- const CONSECUTIVE_HYPHENS_REGEX = /-+/g;
324
- const ASTERISK_REGEX = /\*/;
323
+ const CONSECUTIVE_HYPHENS_REGEX = /* @__PURE__ */ new RegExp("-+", "gv");
324
+ const ASTERISK_REGEX = /* @__PURE__ */ new RegExp("\\*", "v");
325
325
  const FORBIDDEN_CHARACTERS = [
326
- [/:/, "Colon"],
327
- [/\u0000/, "Null"],
328
- [/\u0001/, "Start of Heading"],
329
- [/\u0002/, "Start of Text"],
330
- [/\u0003/, "End of Text"],
331
- [/\u0004/, "End of Transmission"],
332
- [/\u0005/, "Enquiry"],
333
- [/\u0006/, "Acknowledge"],
334
- [/\u0007/, "Bell"],
335
- [/\u0008/, "Backspace"],
336
- [/\u0009/, "Horizontal Tab"],
337
- [/\u000A/, "Line Feed"],
338
- [/\u000B/, "Vertical Tab"],
339
- [/\u000C/, "Form Feed"],
340
- [/\u000D/, "Carriage Return"],
341
- [/\u000E/, "Shift Out"],
342
- [/\u000F/, "Shift In"],
343
- [/\u0010/, "Data Link Escape"],
344
- [/\u0011/, "Device Control 1"],
345
- [/\u0012/, "Device Control 2"],
346
- [/\u0013/, "Device Control 3"],
347
- [/\u0014/, "Device Control 4"],
348
- [/\u0015/, "Negative Acknowledge"],
349
- [/\u0016/, "Synchronous Idle"],
350
- [/\u0017/, "End of Transmission Block"],
351
- [/\u0018/, "Cancel"],
352
- [/\u0019/, "End of Medium"],
353
- [/\u001A/, "Substitute"],
354
- [/\u001B/, "Escape"],
355
- [/\u001C/, "File Separator"],
356
- [/\u001D/, "Group Separator"],
357
- [/\u001E/, "Record Separator"],
358
- [/\u001F/, "Unit Separator"],
359
- [/\u007F/, "Delete"],
360
- [/\u0080/, "Padding Character"],
361
- [/\u0081/, "High Octet Preset"],
362
- [/\u0082/, "Break Permitted Here"],
363
- [/\u0083/, "No Break Here"],
364
- [/\u0084/, "Index"],
365
- [/\u0085/, "Next Line"],
366
- [/\u0086/, "Start of Selected Area"],
367
- [/\u0087/, "End of Selected Area"],
368
- [/\u0088/, "Character Tabulation Set"],
369
- [/\u0089/, "Character Tabulation with Justification"],
370
- [/\u008A/, "Line Tabulation Set"],
371
- [/\u008B/, "Partial Line Forward"],
372
- [/\u008C/, "Partial Line Backward"],
373
- [/\u008D/, "Reverse Line Feed"],
374
- [/\u008E/, "Single Shift Two"],
375
- [/\u008F/, "Single Shift Three"],
376
- [/\u0090/, "Device Control String"],
377
- [/\u0091/, "Private Use One"],
378
- [/\u0092/, "Private Use Two"],
379
- [/\u0093/, "Set Transmit State"],
380
- [/\u0094/, "Cancel Character"],
381
- [/\u0095/, "Message Waiting"],
382
- [/\u0096/, "Start of Protected Area"],
383
- [/\u0097/, "End of Protected Area"],
384
- [/\u0098/, "Start of String"],
385
- [/\u0099/, "Single Graphic Character Introducer"],
386
- [/\u009A/, "Single Character Introducer"],
387
- [/\u009B/, "Control Sequence Introducer"],
388
- [/\u009C/, "String Terminator"],
389
- [/\u009D/, "Operating System Command"],
390
- [/\u009E/, "Privacy Message"],
391
- [/\u009F/, "Application Program Command"],
392
- [/\u00A0/, "Non-breaking Space"],
393
- [/\u00AD/, "Soft Hyphen"],
394
- [/\u200B/, "Zero-width Space"],
395
- [/\u200C/, "Zero-width Non-joiner"],
396
- [/\u200D/, "Zero-width Joiner"],
397
- [/\u200E/, "Left-to-right Mark"],
398
- [/\u200F/, "Right-to-left Mark"],
399
- [/\u202A/, "Left-to-right Embedding"],
400
- [/\u202B/, "Right-to-left Embedding"],
401
- [/\u202C/, "Pop Directional Formatting"],
402
- [/\u202D/, "Left-to-right Override"],
403
- [/\u202E/, "Right-to-left Override"],
404
- [/\uFEFF/, "Byte Order Mark (BOM)"]
326
+ [/* @__PURE__ */ new RegExp(":", "v"), "Colon"],
327
+ [/* @__PURE__ */ new RegExp("\\u{0000}", "v"), "Null"],
328
+ [/* @__PURE__ */ new RegExp("\\u{0001}", "v"), "Start of Heading"],
329
+ [/* @__PURE__ */ new RegExp("\\u{0002}", "v"), "Start of Text"],
330
+ [/* @__PURE__ */ new RegExp("\\u{0003}", "v"), "End of Text"],
331
+ [/* @__PURE__ */ new RegExp("\\u{0004}", "v"), "End of Transmission"],
332
+ [/* @__PURE__ */ new RegExp("\\u{0005}", "v"), "Enquiry"],
333
+ [/* @__PURE__ */ new RegExp("\\u{0006}", "v"), "Acknowledge"],
334
+ [/* @__PURE__ */ new RegExp("\\u{0007}", "v"), "Bell"],
335
+ [/* @__PURE__ */ new RegExp("\\u{0008}", "v"), "Backspace"],
336
+ [/* @__PURE__ */ new RegExp("\\u{0009}", "v"), "Horizontal Tab"],
337
+ [/* @__PURE__ */ new RegExp("\\u{000A}", "v"), "Line Feed"],
338
+ [/* @__PURE__ */ new RegExp("\\u{000B}", "v"), "Vertical Tab"],
339
+ [/* @__PURE__ */ new RegExp("\\u{000C}", "v"), "Form Feed"],
340
+ [/* @__PURE__ */ new RegExp("\\u{000D}", "v"), "Carriage Return"],
341
+ [/* @__PURE__ */ new RegExp("\\u{000E}", "v"), "Shift Out"],
342
+ [/* @__PURE__ */ new RegExp("\\u{000F}", "v"), "Shift In"],
343
+ [/* @__PURE__ */ new RegExp("\\u{0010}", "v"), "Data Link Escape"],
344
+ [/* @__PURE__ */ new RegExp("\\u{0011}", "v"), "Device Control 1"],
345
+ [/* @__PURE__ */ new RegExp("\\u{0012}", "v"), "Device Control 2"],
346
+ [/* @__PURE__ */ new RegExp("\\u{0013}", "v"), "Device Control 3"],
347
+ [/* @__PURE__ */ new RegExp("\\u{0014}", "v"), "Device Control 4"],
348
+ [/* @__PURE__ */ new RegExp("\\u{0015}", "v"), "Negative Acknowledge"],
349
+ [/* @__PURE__ */ new RegExp("\\u{0016}", "v"), "Synchronous Idle"],
350
+ [/* @__PURE__ */ new RegExp("\\u{0017}", "v"), "End of Transmission Block"],
351
+ [/* @__PURE__ */ new RegExp("\\u{0018}", "v"), "Cancel"],
352
+ [/* @__PURE__ */ new RegExp("\\u{0019}", "v"), "End of Medium"],
353
+ [/* @__PURE__ */ new RegExp("\\u{001A}", "v"), "Substitute"],
354
+ [/* @__PURE__ */ new RegExp("\\u{001B}", "v"), "Escape"],
355
+ [/* @__PURE__ */ new RegExp("\\u{001C}", "v"), "File Separator"],
356
+ [/* @__PURE__ */ new RegExp("\\u{001D}", "v"), "Group Separator"],
357
+ [/* @__PURE__ */ new RegExp("\\u{001E}", "v"), "Record Separator"],
358
+ [/* @__PURE__ */ new RegExp("\\u{001F}", "v"), "Unit Separator"],
359
+ [/* @__PURE__ */ new RegExp("\\u{007F}", "v"), "Delete"],
360
+ [/* @__PURE__ */ new RegExp("\\u{0080}", "v"), "Padding Character"],
361
+ [/* @__PURE__ */ new RegExp("\\u{0081}", "v"), "High Octet Preset"],
362
+ [/* @__PURE__ */ new RegExp("\\u{0082}", "v"), "Break Permitted Here"],
363
+ [/* @__PURE__ */ new RegExp("\\u{0083}", "v"), "No Break Here"],
364
+ [/* @__PURE__ */ new RegExp("\\u{0084}", "v"), "Index"],
365
+ [/* @__PURE__ */ new RegExp("\\u{0085}", "v"), "Next Line"],
366
+ [/* @__PURE__ */ new RegExp("\\u{0086}", "v"), "Start of Selected Area"],
367
+ [/* @__PURE__ */ new RegExp("\\u{0087}", "v"), "End of Selected Area"],
368
+ [/* @__PURE__ */ new RegExp("\\u{0088}", "v"), "Character Tabulation Set"],
369
+ [/* @__PURE__ */ new RegExp("\\u{0089}", "v"), "Character Tabulation with Justification"],
370
+ [/* @__PURE__ */ new RegExp("\\u{008A}", "v"), "Line Tabulation Set"],
371
+ [/* @__PURE__ */ new RegExp("\\u{008B}", "v"), "Partial Line Forward"],
372
+ [/* @__PURE__ */ new RegExp("\\u{008C}", "v"), "Partial Line Backward"],
373
+ [/* @__PURE__ */ new RegExp("\\u{008D}", "v"), "Reverse Line Feed"],
374
+ [/* @__PURE__ */ new RegExp("\\u{008E}", "v"), "Single Shift Two"],
375
+ [/* @__PURE__ */ new RegExp("\\u{008F}", "v"), "Single Shift Three"],
376
+ [/* @__PURE__ */ new RegExp("\\u{0090}", "v"), "Device Control String"],
377
+ [/* @__PURE__ */ new RegExp("\\u{0091}", "v"), "Private Use One"],
378
+ [/* @__PURE__ */ new RegExp("\\u{0092}", "v"), "Private Use Two"],
379
+ [/* @__PURE__ */ new RegExp("\\u{0093}", "v"), "Set Transmit State"],
380
+ [/* @__PURE__ */ new RegExp("\\u{0094}", "v"), "Cancel Character"],
381
+ [/* @__PURE__ */ new RegExp("\\u{0095}", "v"), "Message Waiting"],
382
+ [/* @__PURE__ */ new RegExp("\\u{0096}", "v"), "Start of Protected Area"],
383
+ [/* @__PURE__ */ new RegExp("\\u{0097}", "v"), "End of Protected Area"],
384
+ [/* @__PURE__ */ new RegExp("\\u{0098}", "v"), "Start of String"],
385
+ [/* @__PURE__ */ new RegExp("\\u{0099}", "v"), "Single Graphic Character Introducer"],
386
+ [/* @__PURE__ */ new RegExp("\\u{009A}", "v"), "Single Character Introducer"],
387
+ [/* @__PURE__ */ new RegExp("\\u{009B}", "v"), "Control Sequence Introducer"],
388
+ [/* @__PURE__ */ new RegExp("\\u{009C}", "v"), "String Terminator"],
389
+ [/* @__PURE__ */ new RegExp("\\u{009D}", "v"), "Operating System Command"],
390
+ [/* @__PURE__ */ new RegExp("\\u{009E}", "v"), "Privacy Message"],
391
+ [/* @__PURE__ */ new RegExp("\\u{009F}", "v"), "Application Program Command"],
392
+ [/* @__PURE__ */ new RegExp("\\u{00A0}", "v"), "Non-breaking Space"],
393
+ [/* @__PURE__ */ new RegExp("\\u{00AD}", "v"), "Soft Hyphen"],
394
+ [/* @__PURE__ */ new RegExp("\\u{200B}", "v"), "Zero-width Space"],
395
+ [/* @__PURE__ */ new RegExp("\\u{200C}", "v"), "Zero-width Non-joiner"],
396
+ [/* @__PURE__ */ new RegExp("\\u{200D}", "v"), "Zero-width Joiner"],
397
+ [/* @__PURE__ */ new RegExp("\\u{200E}", "v"), "Left-to-right Mark"],
398
+ [/* @__PURE__ */ new RegExp("\\u{200F}", "v"), "Right-to-left Mark"],
399
+ [/* @__PURE__ */ new RegExp("\\u{202A}", "v"), "Left-to-right Embedding"],
400
+ [/* @__PURE__ */ new RegExp("\\u{202B}", "v"), "Right-to-left Embedding"],
401
+ [/* @__PURE__ */ new RegExp("\\u{202C}", "v"), "Pop Directional Formatting"],
402
+ [/* @__PURE__ */ new RegExp("\\u{202D}", "v"), "Left-to-right Override"],
403
+ [/* @__PURE__ */ new RegExp("\\u{202E}", "v"), "Right-to-left Override"],
404
+ [/* @__PURE__ */ new RegExp("\\u{FEFF}", "v"), "Byte Order Mark (BOM)"]
405
405
  ];
406
406
  /**
407
407
  * Convenience
@@ -502,14 +502,14 @@ function getFileExtensionForMimeType(mimeType) {
502
502
  "video/x-matroska": "mkv",
503
503
  "video/x-msvideo": "avi"
504
504
  };
505
- const normalizedMimeType = mimeType.split(";")[0].trim().toLowerCase();
505
+ const normalizedMimeType = mimeType.split(";", 1)[0].trim().toLowerCase();
506
506
  if (normalizedMimeType === "") return;
507
507
  return mimeToExtension[normalizedMimeType];
508
508
  }
509
509
  //#endregion
510
510
  //#region src/lib/utilities/path.ts
511
- const WINDOWS_DRIVE_LETTER_REGEX = /^[A-Z]:/i;
512
- const QUERY_FRAGMENT_START_REGEX = /[#?^]/;
511
+ const WINDOWS_DRIVE_LETTER_REGEX = /* @__PURE__ */ new RegExp("^[A-Z]:", "iv");
512
+ const QUERY_FRAGMENT_START_REGEX = /* @__PURE__ */ new RegExp("[#?^]", "v");
513
513
  /**
514
514
  * The browserify polyfill doesn't implement win32 absolute path detection...
515
515
  *
@@ -520,7 +520,7 @@ const QUERY_FRAGMENT_START_REGEX = /[#?^]/;
520
520
  function isAbsolute(filePath) {
521
521
  return isAbsolutePath.posix(filePath) || isAbsolutePath.win32(filePath);
522
522
  }
523
- const RE_WINDOWS_EXTENDED_LENGTH_PATH = /^\\\\\?\\.+/;
523
+ const RE_WINDOWS_EXTENDED_LENGTH_PATH = /* @__PURE__ */ new RegExp("^\\\\\\\\\\?\\\\.+", "v");
524
524
  /**
525
525
  * Converts all paths to cross-platform 'mixed' style with forward slashes.
526
526
  * Warns on unsupported Windows extended length paths.
@@ -597,8 +597,8 @@ function addExtension(filePath, extension) {
597
597
  }
598
598
  //#endregion
599
599
  //#region src/lib/utilities/url.ts
600
- const DRIVE_LETTER_REGEX = /^[a-z]:/i;
601
- const FILE_PREFIX_REGEX = /^file:/i;
600
+ const DRIVE_LETTER_REGEX = /* @__PURE__ */ new RegExp("^[a-z]:", "iv");
601
+ const FILE_PREFIX_REGEX = /* @__PURE__ */ new RegExp("^file:", "iv");
602
602
  function safeDecodeURI(text) {
603
603
  try {
604
604
  return decodeURI(text);
@@ -676,32 +676,22 @@ async function urlExists(url, fetchAdapter) {
676
676
  }
677
677
  }
678
678
  async function getFileExtensionFromUrl(url, fetchAdapter, mode = MEDIA_URL_CONTENT_TYPE_MODE) {
679
- switch (mode) {
680
- case "metadata":
681
- if (fetchAdapter === void 0) return getFileExtensionFromUrl(url, fetchAdapter, "name");
682
- try {
683
- const contentTypeHeaderValue = getHeadersString((await fetchAdapter(url, { method: "HEAD" }))?.headers, ["content-type"]);
684
- if (contentTypeHeaderValue === void 0) throw new Error("No content-type header found");
685
- const extension = getFileExtensionForMimeType(contentTypeHeaderValue);
686
- if (extension !== void 0) return extension;
687
- return await getFileExtensionFromUrl(url, fetchAdapter, "name");
688
- } catch {
689
- return getFileExtensionFromUrl(url, fetchAdapter, "name");
690
- }
691
- case "name": {
692
- let extensionInUrl;
693
- const parsedUrl = safeParseUrl(url);
694
- if (parsedUrl === void 0) {
695
- console.warn(`Could not parse URL: ${url}`);
696
- return;
697
- }
698
- const pathnameParts = parsedUrl.pathname.split(".");
699
- if (pathnameParts.length > 1) extensionInUrl = pathnameParts.at(-1);
700
- else extensionInUrl = parsedUrl.search.split(".").at(-1);
701
- if (MEDIA_SUPPORTED_EXTENSIONS.includes(extensionInUrl ?? "")) return extensionInUrl;
702
- return;
703
- }
679
+ if (mode === "metadata" && fetchAdapter !== void 0) try {
680
+ const contentTypeHeaderValue = getHeadersString((await fetchAdapter(url, { method: "HEAD" }))?.headers, ["content-type"]);
681
+ if (contentTypeHeaderValue === void 0) throw new Error("No content-type header found");
682
+ const extension = getFileExtensionForMimeType(contentTypeHeaderValue);
683
+ if (extension !== void 0) return extension;
684
+ } catch {}
685
+ const parsedUrl = safeParseUrl(url);
686
+ if (parsedUrl === void 0) {
687
+ console.warn(`Could not parse URL: ${url}`);
688
+ return;
704
689
  }
690
+ let extensionInUrl;
691
+ const pathnameParts = parsedUrl.pathname.split(".");
692
+ if (pathnameParts.length > 1) extensionInUrl = pathnameParts.at(-1);
693
+ else extensionInUrl = parsedUrl.search.split(".").at(-1);
694
+ if (MEDIA_SUPPORTED_EXTENSIONS.includes(extensionInUrl ?? "")) return extensionInUrl;
705
695
  }
706
696
  /**
707
697
  * Tradeoffs between content change sensitivity and sync speed / efficiency,
@@ -716,23 +706,17 @@ async function getFileExtensionFromUrl(url, fetchAdapter, mode = MEDIA_URL_CONTE
716
706
  * if not available.
717
707
  */
718
708
  async function getUrlContentHash(url, fetchAdapter, mode = MEDIA_DEFAULT_HASH_MODE_URL) {
719
- switch (mode) {
720
- case "content":
721
- console.warn("`content` hash mode is not yet implemented for URLs");
722
- return getUrlContentHash(url, fetchAdapter, "metadata");
723
- case "metadata": try {
724
- const stringToHash = getHeadersString((await fetchAdapter(url, { method: "HEAD" }))?.headers, [
725
- "etag",
726
- "last-modified",
727
- "content-length"
728
- ]);
729
- if (stringToHash === void 0) throw new Error("No headers found");
730
- return getHash(stringToHash, 16);
731
- } catch {
732
- return getUrlContentHash(url, fetchAdapter, "name");
733
- }
734
- case "name": return getHash(url, 16);
735
- }
709
+ if (mode === "content") console.warn("`content` hash mode is not yet implemented for URLs");
710
+ if (mode !== "name") try {
711
+ const stringToHash = getHeadersString((await fetchAdapter(url, { method: "HEAD" }))?.headers, [
712
+ "etag",
713
+ "last-modified",
714
+ "content-length"
715
+ ]);
716
+ if (stringToHash === void 0) throw new Error("No headers found");
717
+ return getHash(stringToHash, 16);
718
+ } catch {}
719
+ return getHash(url, 16);
736
720
  }
737
721
  function urlToHostAndPort(url) {
738
722
  const parsedUrl = safeParseUrl(url);
@@ -786,7 +770,7 @@ async function getContentHash(absolutePathOrUrl, fileAdapter, fetchAdapter) {
786
770
  }
787
771
  //#endregion
788
772
  //#region src/lib/parse/rehype-mathjax-anki.ts
789
- const CONSECUTIVE_BRACES_REGEX = /([{}])(?=[{}])/g;
773
+ const CONSECUTIVE_BRACES_REGEX = /* @__PURE__ */ new RegExp("([\\{\\}])(?=[\\{\\}])", "gv");
790
774
  function sanitizeBraces(value) {
791
775
  return value.replaceAll(CONSECUTIVE_BRACES_REGEX, "$1 ");
792
776
  }
@@ -801,9 +785,10 @@ const plugin$3 = function() {
801
785
  let fenced = false;
802
786
  visit(tree, (node, index, parent) => {
803
787
  if (parent === void 0 || index === void 0 || node.type !== "element") return CONTINUE;
804
- if (node.tagName === "pre" && node.children.length === 1 && node.children[0].type === "element" && node.children[0].tagName === "code" && Array.isArray(node.children[0].properties.className) && node.children[0].properties.className.includes("language-math")) {
788
+ const [preChild] = node.children;
789
+ if (node.tagName === "pre" && node.children.length === 1 && preChild.type === "element" && preChild.tagName === "code" && Array.isArray(preChild.properties.className) && preChild.properties.className.includes("language-math")) {
805
790
  fenced = true;
806
- parent.children.splice(index, 1, node.children[0]);
791
+ parent.children[index] = preChild;
807
792
  }
808
793
  if (node.tagName === "code" && Array.isArray(node.properties.className) && node.properties.className.includes("language-math")) {
809
794
  const isBlock = node.properties.className.includes("math-display") || fenced;
@@ -838,7 +823,7 @@ const plugin$2 = function() {
838
823
  };
839
824
  //#endregion
840
825
  //#region src/lib/parse/rehype-utilities.ts
841
- const DIMENSION_CHARS_REGEX = /^[\dx]+$/;
826
+ const DIMENSION_CHARS_REGEX = /* @__PURE__ */ new RegExp("^[\\dx]+$", "v");
842
827
  const highlighter = createHighlighterCoreSync({
843
828
  engine: createJavaScriptRegexEngine(),
844
829
  langs: [
@@ -927,7 +912,7 @@ async function mdastToHtml(mdast, options) {
927
912
  const syncEnabled = yankiSyncMedia && ankiMediaFilename !== void 0 ? "true" : "false";
928
913
  if (!supportedMedia || MEDIA_SUPPORTED_FILE_EXTENSIONS.includes(extension)) {
929
914
  const finalSourceWithQuery = isUrl(finalSrc) ? finalSrc : `${finalSrc}${getQuery(String(node.properties.dataYankiSrcOriginal))}`;
930
- parent.children.splice(index, 1, u("element", {
915
+ parent.children[index] = u("element", {
931
916
  properties: {
932
917
  className: ["yanki-media", `yanki-media-${supportedMedia ? "file" : "unsupported"}`],
933
918
  "data-yanki-alt-text": node.properties.alt,
@@ -940,13 +925,13 @@ async function mdastToHtml(mdast, options) {
940
925
  }, [u("element", {
941
926
  properties: { href: finalSourceWithQuery },
942
927
  tagName: "a"
943
- }, [u("text", decodeURI(String(node.properties.dataYankiSrcOriginal)))])]));
928
+ }, [u("text", decodeURI(String(node.properties.dataYankiSrcOriginal)))])]);
944
929
  } else if (MEDIA_SUPPORTED_IMAGE_EXTENSIONS.includes(extension)) {
945
930
  node.properties.src = finalSrc;
946
931
  node.properties.className = ["yanki-media", "yanki-media-image"];
947
932
  node.properties.dataYankiMediaSrc = absolutePathOrUrl;
948
933
  node.properties.dataYankiMediaSync = syncEnabled;
949
- } else if (MEDIA_SUPPORTED_AUDIO_VIDEO_EXTENSIONS.includes(extension)) parent.children.splice(index, 1, u("element", {
934
+ } else if (MEDIA_SUPPORTED_AUDIO_VIDEO_EXTENSIONS.includes(extension)) parent.children[index] = u("element", {
950
935
  properties: {
951
936
  className: ["yanki-media", "yanki-media-audio-video"],
952
937
  "data-yanki-alt-text": node.properties.alt,
@@ -956,7 +941,7 @@ async function mdastToHtml(mdast, options) {
956
941
  "data-yanki-src-original": node.properties.dataYankiSrcOriginal
957
942
  },
958
943
  tagName: "span"
959
- }, [u("text", `[sound:${finalSrc}]`)]));
944
+ }, [u("text", `[sound:${finalSrc}]`)]);
960
945
  });
961
946
  });
962
947
  for (const mutationPromise of treeMutationPromises) await mutationPromise();
@@ -974,33 +959,32 @@ async function mdastToHtml(mdast, options) {
974
959
  return addBoilerplateComment(processor.stringify(nonEmptyHast)).trim();
975
960
  }
976
961
  const htmlProcessor = unified().use(rehypeParse, { fragment: true });
977
- function addBoilerplateComment(html) {
978
- return `<!-- This HTML was generated by Yanki, a Markdown to Anki converter. Do not edit directly. -->\n${html}`;
962
+ function addBoilerplateComment(htmlText) {
963
+ return `<!-- This HTML was generated by Yanki, a Markdown to Anki converter. Do not edit directly. -->\n${htmlText}`;
979
964
  }
980
965
  function hastToPlainText(hast) {
981
966
  return toText(hast);
982
967
  }
983
- function htmlToPlainText(html) {
984
- return hastToPlainText(htmlProcessor.parse(html));
968
+ function htmlToPlainText(htmlText) {
969
+ return hastToPlainText(htmlProcessor.parse(htmlText));
985
970
  }
986
- function getAllLinesOfHtmlAsPlainText(html) {
987
- return htmlToPlainText(html).split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join(" ");
971
+ function getAllLinesOfHtmlAsPlainText(htmlText) {
972
+ return htmlToPlainText(htmlText).split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join(" ");
988
973
  }
989
- function getFirstLineOfHtmlAsPlainText(html) {
990
- return htmlToPlainText(html).split("\n").map((line) => line.trim()).find((line) => line.length > 0) ?? "";
974
+ function getFirstLineOfHtmlAsPlainText(htmlText) {
975
+ return htmlToPlainText(htmlText).split("\n").map((line) => line.trim()).find((line) => line.length > 0) ?? "";
991
976
  }
992
- function extractMediaFromHtml(html) {
993
- const hast = htmlProcessor.parse(html);
977
+ function extractMediaFromHtml(htmlText) {
978
+ const hast = htmlProcessor.parse(htmlText);
994
979
  const media = [];
995
980
  visit(hast, "element", (node) => {
996
- if ((node.tagName === "img" || node.tagName === "span") && node.properties?.dataYankiMediaSync === "true") {
997
- const filename = node.properties?.src ?? node.properties?.dataYankiSrc;
998
- const originalSrc = node.properties?.dataYankiMediaSrc;
999
- if (filename !== void 0 && originalSrc !== void 0 && typeof filename === "string" && typeof originalSrc === "string") media.push({
1000
- filename,
1001
- originalSrc
1002
- });
1003
- }
981
+ if (node.tagName !== "img" && node.tagName !== "span" || node.properties?.dataYankiMediaSync !== "true") return;
982
+ const filename = node.properties?.src ?? node.properties?.dataYankiSrc;
983
+ const originalSrc = node.properties?.dataYankiMediaSrc;
984
+ if (filename !== void 0 && originalSrc !== void 0 && typeof filename === "string" && typeof originalSrc === "string") media.push({
985
+ filename,
986
+ originalSrc
987
+ });
1004
988
  });
1005
989
  return media;
1006
990
  }
@@ -1028,7 +1012,7 @@ function parseDimensions(dimensions) {
1028
1012
  height: void 0
1029
1013
  };
1030
1014
  if (!dimensions.includes("x")) {
1031
- const widthOnly = Number.parseInt(dimensions, 10);
1015
+ const widthOnly = Number(dimensions);
1032
1016
  if (!Number.isNaN(widthOnly)) return {
1033
1017
  width: widthOnly,
1034
1018
  height: void 0
@@ -1068,7 +1052,7 @@ function isVisuallyEmpty(tree) {
1068
1052
  hasVisualContent = true;
1069
1053
  return EXIT;
1070
1054
  }
1071
- if (toText(element).trim()) {
1055
+ if (toText(element).trim() !== "") {
1072
1056
  hasVisualContent = true;
1073
1057
  return EXIT;
1074
1058
  }
@@ -1091,10 +1075,9 @@ function isVisuallyEmpty(tree) {
1091
1075
  */
1092
1076
  function addFirstChildToFirstDiv(tree, newChild) {
1093
1077
  visit(tree, "element", (node) => {
1094
- if (node.tagName === "div") {
1095
- node.children.unshift(newChild);
1096
- return EXIT;
1097
- }
1078
+ if (node.tagName !== "div") return;
1079
+ node.children.unshift(newChild);
1080
+ return EXIT;
1098
1081
  });
1099
1082
  return tree;
1100
1083
  }
@@ -1196,26 +1179,26 @@ async function deleteNotes(client, notes, dryRun = false) {
1196
1179
  async function addNote(client, note, dryRun, fileAdapter) {
1197
1180
  if (note.noteId !== void 0) throw new Error("Note already has an ID");
1198
1181
  if (dryRun) return 0;
1199
- const newNote = await client.note.addNote({ note: {
1200
- ...note,
1201
- options: { allowDuplicate: true }
1202
- } }).catch(async (error) => {
1203
- if (error instanceof Error) {
1204
- if (error.message === `model was not found: ${note.modelName}`) {
1205
- const model = yankiModels.find((model) => model.modelName === note.modelName);
1206
- if (model === void 0) throw new Error(`Model not found: ${note.modelName}`);
1207
- await ensureModelExists(client, model);
1208
- return addNote(client, note, dryRun, fileAdapter);
1209
- }
1210
- if (error.message === `deck was not found: ${note.deckName}`) {
1211
- if (note.deckName === "") throw new Error("Deck name is empty");
1212
- await client.deck.createDeck({ deck: note.deckName });
1213
- return addNote(client, note, dryRun, fileAdapter);
1214
- }
1215
- throw error;
1216
- } else throw new TypeError("Unknown error");
1217
- });
1218
- if (newNote === null) throw new Error("Note creation failed");
1182
+ let newNote;
1183
+ try {
1184
+ newNote = await client.note.addNote({ note: {
1185
+ ...note,
1186
+ options: { allowDuplicate: true }
1187
+ } }) ?? void 0;
1188
+ } catch (error) {
1189
+ if (error instanceof Error) if (error.message === `model was not found: ${note.modelName}`) {
1190
+ const model = yankiModels.find((model) => model.modelName === note.modelName);
1191
+ if (model === void 0) throw new Error(`Model not found: ${note.modelName}`, { cause: error });
1192
+ await ensureModelExists(client, model);
1193
+ newNote = await addNote(client, note, dryRun, fileAdapter);
1194
+ } else if (error.message === `deck was not found: ${note.deckName}`) {
1195
+ if (note.deckName === "") throw new Error("Deck name is empty", { cause: error });
1196
+ await client.deck.createDeck({ deck: note.deckName });
1197
+ newNote = await addNote(client, note, dryRun, fileAdapter);
1198
+ } else throw error;
1199
+ else throw new TypeError("Unknown error", { cause: error });
1200
+ }
1201
+ if (newNote === void 0) throw new Error("Note creation failed");
1219
1202
  await uploadMediaForNote(client, note, dryRun, fileAdapter);
1220
1203
  return newNote;
1221
1204
  }
@@ -1244,21 +1227,21 @@ async function updateNote(client, localNote, remoteNote, dryRun, fileAdapter) {
1244
1227
  }
1245
1228
  if (!areTagsEqual(localNote.tags ?? [], remoteNote.tags ?? []) || !areFieldsEqual(localNote.fields, remoteNote.fields) || localNote.modelName !== remoteNote.modelName) {
1246
1229
  if (!dryRun) {
1247
- await client.note.updateNoteModel({ note: {
1248
- ...localNote,
1249
- id: localNote.noteId,
1250
- tags: localNote.tags ?? []
1251
- } }).catch(async (error) => {
1252
- if (error instanceof Error) {
1253
- if (error.message === `Model '${localNote.modelName}' not found`) {
1254
- const model = yankiModels.find((model) => model.modelName === localNote.modelName);
1255
- if (model === void 0) throw new Error(`Model not found: ${localNote.modelName}`);
1256
- await ensureModelExists(client, model);
1257
- return updateNote(client, localNote, remoteNote, dryRun, fileAdapter);
1258
- }
1259
- throw error;
1260
- } else throw new TypeError("Unknown error");
1261
- });
1230
+ try {
1231
+ await client.note.updateNoteModel({ note: {
1232
+ ...localNote,
1233
+ id: localNote.noteId,
1234
+ tags: localNote.tags ?? []
1235
+ } });
1236
+ } catch (error) {
1237
+ if (error instanceof Error) if (error.message === `Model '${localNote.modelName}' not found`) {
1238
+ const model = yankiModels.find((model) => model.modelName === localNote.modelName);
1239
+ if (model === void 0) throw new Error(`Model not found: ${localNote.modelName}`, { cause: error });
1240
+ await ensureModelExists(client, model);
1241
+ await updateNote(client, localNote, remoteNote, dryRun, fileAdapter);
1242
+ } else throw error;
1243
+ else throw new TypeError("Unknown error", { cause: error });
1244
+ }
1262
1245
  await uploadMediaForNote(client, localNote, dryRun, fileAdapter);
1263
1246
  }
1264
1247
  updated = true;
@@ -1275,9 +1258,13 @@ function areFieldsEqual(localFields, remoteFields) {
1275
1258
  "Front",
1276
1259
  "Back",
1277
1260
  "Extra"
1278
- ]) if (key in localFields && key in remoteFields) {
1279
- if (localFields[key].normalize("NFC") !== remoteFields[key].normalize("NFC")) return false;
1280
- } else if (key in localFields || key in remoteFields) return false;
1261
+ ]) {
1262
+ const localValue = Object.hasOwn(localFields, key) ? localFields[key] : void 0;
1263
+ const remoteValue = Object.hasOwn(remoteFields, key) ? remoteFields[key] : void 0;
1264
+ if (localValue !== void 0 && remoteValue !== void 0) {
1265
+ if (localValue.normalize("NFC") !== remoteValue.normalize("NFC")) return false;
1266
+ } else if (localValue !== void 0 || remoteValue !== void 0) return false;
1267
+ }
1281
1268
  return true;
1282
1269
  }
1283
1270
  function areNotesEqual(noteA, noteB, includeId = true) {
@@ -1302,7 +1289,7 @@ function areNotesEqual(noteA, noteB, includeId = true) {
1302
1289
  function areTagsEqual(localTags, remoteTags) {
1303
1290
  const localTagsSet = new Set(localTags.map((tag) => tag.normalize("NFC").toLowerCase()));
1304
1291
  const remoteTagsSet = new Set(remoteTags.map((tag) => tag.normalize("NFC").toLowerCase()));
1305
- return new Set([...localTagsSet, ...remoteTagsSet]).size === remoteTagsSet.size;
1292
+ return localTagsSet.union(remoteTagsSet).size === remoteTagsSet.size;
1306
1293
  }
1307
1294
  /**
1308
1295
  * Get all notes from Anki that match the model prefix.
@@ -1335,8 +1322,8 @@ async function getRemoteNotes(client, namespace = defaultGlobalOptions.namespace
1335
1322
  */
1336
1323
  async function getRemoteNotesById(client, noteIds) {
1337
1324
  const ankiNotes = await client.note.notesInfo({ notes: noteIds });
1325
+ if (ankiNotes.every((ankiNote) => ankiNote.noteId === void 0)) return Array.from({ length: ankiNotes.length });
1338
1326
  const yankiNotes = [];
1339
- if (ankiNotes.every((ankiNote) => ankiNote.noteId === void 0)) return Array.from({ length: ankiNotes.length }).fill(void 0);
1340
1327
  const allCardIds = ankiNotes.flatMap((note) => note.cards ?? []);
1341
1328
  const deckToCardMap = await client.deck.getDecks({ cards: allCardIds });
1342
1329
  const cardIdToDeckMap = /* @__PURE__ */ new Map();
@@ -1357,29 +1344,12 @@ async function getRemoteNotesById(client, noteIds) {
1357
1344
  const deckConfig = await client.deck.getDeckConfig({ deck: deckName });
1358
1345
  const isFiltered = Boolean(deckConfig.dyn);
1359
1346
  deckFilteredStatusMap.set(deckName, isFiltered);
1360
- if (isFiltered) {
1361
- const allDeckNames = await client.deck.deckNames();
1362
- for (const localName of allDeckNames) if (!deckFilteredStatusMap.has(localName)) {
1363
- const deckConfig = await client.deck.getDeckConfig({ deck: localName });
1364
- deckFilteredStatusMap.set(localName, Boolean(deckConfig.dyn));
1365
- }
1366
- const sortedUnfilteredDeckNames = [...deckFilteredStatusMap.entries()].filter(([deck, isFiltered]) => !isFiltered && deck !== "Default").map(([deck]) => deck).sort((a, b) => b.split("::").length - a.split("::").length);
1367
- sortedUnfilteredDeckNames.push("Default");
1368
- for (const localDeckName of sortedUnfilteredDeckNames) unfilteredDeckNoteIdMap.set(localDeckName, void 0);
1369
- }
1347
+ if (isFiltered) await populateUnfilteredDeckMaps(client, deckFilteredStatusMap, unfilteredDeckNoteIdMap);
1370
1348
  }
1371
1349
  if (deckFilteredStatusMap.get(deckName)) {
1372
- const namesToCheck = unfilteredDeckNoteIdMap.keys();
1373
- let found = false;
1374
- for (const nameToCheck of namesToCheck) {
1375
- if (unfilteredDeckNoteIdMap.get(nameToCheck) === void 0) unfilteredDeckNoteIdMap.set(nameToCheck, await client.note.findNotes({ query: `"deck:${nameToCheck}"` }));
1376
- if (unfilteredDeckNoteIdMap.get(nameToCheck)?.includes(ankiNote.noteId)) {
1377
- deckName = nameToCheck;
1378
- found = true;
1379
- break;
1380
- }
1381
- }
1382
- if (!found) throw new Error(`No matching non-filtered deck found for note ${ankiNote.noteId}`);
1350
+ const unfilteredDeckName = await findUnfilteredDeckNameForNote(client, unfilteredDeckNoteIdMap, ankiNote.noteId);
1351
+ if (unfilteredDeckName === void 0) throw new Error(`No matching non-filtered deck found for note ${ankiNote.noteId}`);
1352
+ deckName = unfilteredDeckName;
1383
1353
  }
1384
1354
  yankiNotes.push({
1385
1355
  cards: ankiNote.cards,
@@ -1397,21 +1367,42 @@ async function getRemoteNotesById(client, noteIds) {
1397
1367
  }
1398
1368
  return yankiNotes;
1399
1369
  }
1370
+ /**
1371
+ * When a filtered deck is spotted, we have to search all decks to find what
1372
+ * non-filtered deck has a given note... so completely populate the filtered
1373
+ * deck status map now, and reuse it for future notes.
1374
+ */
1375
+ async function populateUnfilteredDeckMaps(client, deckFilteredStatusMap, unfilteredDeckNoteIdMap) {
1376
+ const allDeckNames = await client.deck.deckNames();
1377
+ for (const deckName of allDeckNames) {
1378
+ if (deckFilteredStatusMap.has(deckName)) continue;
1379
+ const deckConfig = await client.deck.getDeckConfig({ deck: deckName });
1380
+ deckFilteredStatusMap.set(deckName, Boolean(deckConfig.dyn));
1381
+ }
1382
+ const sortedUnfilteredDeckNames = [...deckFilteredStatusMap].filter(([deck, isFiltered]) => !isFiltered && deck !== "Default").map(([deck]) => deck).toSorted((a, b) => b.split("::").length - a.split("::").length);
1383
+ sortedUnfilteredDeckNames.push("Default");
1384
+ for (const deckName of sortedUnfilteredDeckNames) unfilteredDeckNoteIdMap.set(deckName, void 0);
1385
+ }
1386
+ /**
1387
+ * Find the "real" non-filtered deck containing a note that's currently in a
1388
+ * filtered deck. Lazily populates and caches the note IDs for each unfiltered
1389
+ * deck as needed.
1390
+ *
1391
+ * @returns The name of the unfiltered deck containing the note, or undefined if
1392
+ * none is found.
1393
+ */
1394
+ async function findUnfilteredDeckNameForNote(client, unfilteredDeckNoteIdMap, noteId) {
1395
+ for (const nameToCheck of unfilteredDeckNoteIdMap.keys()) {
1396
+ if (unfilteredDeckNoteIdMap.get(nameToCheck) === void 0) unfilteredDeckNoteIdMap.set(nameToCheck, await client.note.findNotes({ query: `"deck:${nameToCheck}"` }));
1397
+ if (unfilteredDeckNoteIdMap.get(nameToCheck)?.includes(noteId)) return nameToCheck;
1398
+ }
1399
+ }
1400
1400
  async function deleteOrphanedDecks(client, activeNotes, originalNotes, dryRun) {
1401
1401
  const activeNoteDeckNames = [...new Set(activeNotes.map((note) => note.deckName))].filter(Boolean);
1402
1402
  const orphanedDeckNames = [...new Set(originalNotes.map((note) => note.deckName))].filter(Boolean).filter((deckName) => !activeNoteDeckNames.includes(deckName));
1403
1403
  const orphanedParentDeckNames = [];
1404
- for (const orphanedDeckName of orphanedDeckNames) {
1405
- const parts = orphanedDeckName.split("::");
1406
- if (parts.length === 1) continue;
1407
- while (parts.length > 1) {
1408
- parts.pop();
1409
- const parentDeckName = parts.join("::");
1410
- if (activeNoteDeckNames.some((deckName) => deckName.startsWith(`${parentDeckName}::`))) break;
1411
- orphanedParentDeckNames.push(parentDeckName);
1412
- }
1413
- }
1414
- const deckDeletionCandidates = [...new Set([...orphanedDeckNames, ...orphanedParentDeckNames])].sort();
1404
+ for (const orphanedDeckName of orphanedDeckNames) orphanedParentDeckNames.push(...getOrphanedParentDeckNames(orphanedDeckName, activeNoteDeckNames));
1405
+ const deckDeletionCandidates = [.../* @__PURE__ */ new Set([...orphanedDeckNames, ...orphanedParentDeckNames])].toSorted();
1415
1406
  const decksToDelete = [];
1416
1407
  for (const deckName of deckDeletionCandidates) {
1417
1408
  const deckStatsObject = await client.deck.getDeckStats({ decks: [deckName] });
@@ -1439,25 +1430,41 @@ async function deleteOrphanedDecks(client, activeNotes, originalNotes, dryRun) {
1439
1430
  return decksToDelete;
1440
1431
  }
1441
1432
  /**
1433
+ * Walk up an orphaned deck's ancestry, collecting parent deck names until one
1434
+ * is found that still contains an active note's deck.
1435
+ */
1436
+ function getOrphanedParentDeckNames(orphanedDeckName, activeNoteDeckNames) {
1437
+ const orphanedParentDeckNames = [];
1438
+ const parts = orphanedDeckName.split("::");
1439
+ while (parts.length > 1) {
1440
+ parts.pop();
1441
+ const parentDeckName = parts.join("::");
1442
+ if (activeNoteDeckNames.some((deckName) => deckName.startsWith(`${parentDeckName}::`))) break;
1443
+ orphanedParentDeckNames.push(parentDeckName);
1444
+ }
1445
+ return orphanedParentDeckNames;
1446
+ }
1447
+ /**
1442
1448
  * Global! Does not respect namespace. You can write namespace checks into your
1443
1449
  * css if you want.
1444
1450
  */
1445
1451
  async function updateModelStyle(client, modelName, css, dryRun) {
1446
1452
  let currentCss;
1447
1453
  try {
1448
- const { css } = await client.model.modelStyling({ modelName });
1449
- currentCss = css;
1454
+ const { css: existingCss } = await client.model.modelStyling({ modelName });
1455
+ currentCss = existingCss;
1450
1456
  } catch (error) {
1451
1457
  if (error instanceof Error) {
1452
1458
  if (error.message === `model was not found: ${modelName}`) {
1453
1459
  const model = yankiModels.find((model) => model.modelName === modelName);
1454
- if (model === void 0) throw new Error(`Model not found: ${modelName}`);
1460
+ if (model === void 0) throw new Error(`Model not found: ${modelName}`, { cause: error });
1455
1461
  if (dryRun) return false;
1456
1462
  await ensureModelExists(client, model);
1457
1463
  return updateModelStyle(client, model.modelName, css, dryRun);
1458
1464
  }
1459
1465
  throw error;
1460
- } else throw new TypeError("Unknown error");
1466
+ }
1467
+ throw new TypeError("Unknown error", { cause: error });
1461
1468
  }
1462
1469
  if (currentCss !== void 0 && currentCss === css) return false;
1463
1470
  if (!dryRun) await client.model.updateModelStyling({ model: {
@@ -1514,7 +1521,8 @@ async function reconcileMedia(client, liveNotes, namespace, dryRun, fileAdapter)
1514
1521
  const activeMediaFilenames = new Set(expectedMedia.map(({ filename }) => filename));
1515
1522
  const allMediaInNamespace = await client.media.getMediaFilesNames({ pattern: `${slugifiedNamespace}-*` });
1516
1523
  const deletedMediaFilenames = [];
1517
- for (const remoteMediaFilename of allMediaInNamespace) if (!activeMediaFilenames.has(remoteMediaFilename)) {
1524
+ for (const remoteMediaFilename of allMediaInNamespace) {
1525
+ if (activeMediaFilenames.has(remoteMediaFilename)) continue;
1518
1526
  await client.media.deleteMediaFile({ filename: remoteMediaFilename });
1519
1527
  deletedMediaFilenames.push(remoteMediaFilename);
1520
1528
  }
@@ -1555,7 +1563,7 @@ async function requestPermission(client) {
1555
1563
  try {
1556
1564
  const { permission } = await client.miscellaneous.requestPermission();
1557
1565
  if (permission === "denied") throw new Error("Permission denied, please add this source to the \"webCorsOriginList\" in the AnkiConnect add-on configuration options.");
1558
- else return "granted";
1566
+ return "granted";
1559
1567
  } catch (error) {
1560
1568
  if (error instanceof Error && (error.message === "fetch failed" || error.message === "net::ERR_CONNECTION_REFUSED")) return "ankiUnreachable";
1561
1569
  throw error;
@@ -1657,8 +1665,8 @@ function formatListResult(result) {
1657
1665
  }
1658
1666
  //#endregion
1659
1667
  //#region src/lib/utilities/filenames.ts
1660
- const CLOZE_CONTENT_REGEX = /\{\{\w\d*\s?:{0,2}([^:}]+)/;
1661
- const FILENAME_INCREMENT_SUFFIX_REGEX = /\s\(\d+\)$/;
1668
+ const CLOZE_CONTENT_REGEX = /* @__PURE__ */ new RegExp("\\{\\{\\w\\d*\\s?:{0,2}([^:\\}]+)", "v");
1669
+ const FILENAME_INCREMENT_SUFFIX_REGEX = /* @__PURE__ */ new RegExp("\\s\\(\\d+\\)$", "v");
1662
1670
  function getSafeTitleForNote(note, manageFilenames, maxLength) {
1663
1671
  if (manageFilenames === "off") throw new Error("manageFilenames must not be off");
1664
1672
  switch (note.modelName) {
@@ -1674,9 +1682,9 @@ function getSafeTitleForNote(note, manageFilenames, maxLength) {
1674
1682
  }
1675
1683
  case "Yanki - Cloze": {
1676
1684
  const cleanFront = getAllLinesOfHtmlAsPlainText(note.fields.Front);
1677
- const textBeforeCloze = emptyIsUndefined(cleanFront.split("{{").at(0) ?? "");
1685
+ const textBeforeCloze = emptyIsUndefined(cleanFront.split("{{", 1).at(0) ?? "");
1678
1686
  const firstClozeText = emptyIsUndefined(CLOZE_CONTENT_REGEX.exec(cleanFront)?.at(1));
1679
- const textAfterCloze = emptyIsUndefined(cleanFront.split("}}").at(1)?.split("{{").at(0) ?? "");
1687
+ const textAfterCloze = emptyIsUndefined(cleanFront.split("}}", 2).at(1)?.split("{{", 1).at(0) ?? "");
1680
1688
  switch (manageFilenames) {
1681
1689
  case "prompt": return getSafeFilename(textBeforeCloze ?? textAfterCloze ?? firstClozeText ?? "", maxLength);
1682
1690
  case "response": return getSafeFilename(firstClozeText ?? textBeforeCloze ?? textAfterCloze ?? "", maxLength);
@@ -1697,7 +1705,7 @@ function getSafeFilename(text, maxLength) {
1697
1705
  let basicSafeFilename = filenamify(getFirstLineOfHtmlAsPlainText(text).trim(), {
1698
1706
  maxLength: Number.MAX_SAFE_INTEGER,
1699
1707
  replacement: " "
1700
- }).replaceAll(/\s+/g, " ").trim();
1708
+ }).replaceAll(/* @__PURE__ */ new RegExp("\\s+", "gv"), " ").trim();
1701
1709
  if (basicSafeFilename.length === 0) basicSafeFilename = MEDIA_DEFAULT_EMPTY_FILENAME;
1702
1710
  basicSafeFilename = basicSafeFilename.normalize("NFC");
1703
1711
  if (maxLength === void 0) return basicSafeFilename;
@@ -1740,7 +1748,7 @@ function getTemporarilyUniqueFilePath(filePath) {
1740
1748
  }
1741
1749
  //#endregion
1742
1750
  //#region src/lib/utilities/resolve-link.ts
1743
- const MD_EXTENSION_REGEX = /\.md$/;
1751
+ const MD_EXTENSION_REGEX = /* @__PURE__ */ new RegExp("\\.md$", "v");
1744
1752
  const defaultResolveLinkOptions = {
1745
1753
  allFilePaths: [],
1746
1754
  basePath: void 0,
@@ -1779,43 +1787,34 @@ const defaultResolveLinkOptions = {
1779
1787
  function resolveLink(filePathOrUrl, options) {
1780
1788
  const { allFilePaths, basePath, convertFilePathsToProtocol, cwd, obsidianVaultName, type } = deepmerge(defaultResolveLinkOptions, options ?? {});
1781
1789
  if (convertFilePathsToProtocol === "obsidian" && obsidianVaultName === void 0) console.warn(`convertFilePathsToProtocol is 'obsidian', but no obsidianVaultName provided`);
1782
- const decodedUrl = safeDecodeURI(filePathOrUrl) ?? filePathOrUrl;
1783
- switch (getSrcType(decodedUrl)) {
1784
- case "localFileName": {
1790
+ let currentPathOrUrl = filePathOrUrl;
1791
+ for (;;) {
1792
+ const decodedUrl = safeDecodeURI(currentPathOrUrl) ?? currentPathOrUrl;
1793
+ const sourceType = getSrcType(decodedUrl);
1794
+ if (sourceType === "localFileName") {
1785
1795
  let resolvedUrl = addExtensionIfMissing(normalize(decodedUrl), "md");
1786
1796
  resolvedUrl = resolveNameLink(resolvedUrl, cwd, allFilePaths ?? []) ?? resolveWithBasePath(decodedUrl, {
1787
1797
  basePath,
1788
1798
  cwd
1789
1799
  });
1790
- if (getSrcType(resolvedUrl) === "localFilePath") return resolveLink(resolvedUrl, {
1791
- allFilePaths,
1792
- basePath,
1793
- convertFilePathsToProtocol,
1794
- cwd,
1795
- obsidianVaultName,
1796
- type
1797
- });
1798
- console.warn(`Failed to convert local file wiki-style name to path: ${filePathOrUrl} --> ${resolvedUrl}`);
1800
+ if (getSrcType(resolvedUrl) === "localFilePath") {
1801
+ currentPathOrUrl = resolvedUrl;
1802
+ continue;
1803
+ }
1804
+ console.warn(`Failed to convert local file wiki-style name to path: ${currentPathOrUrl} --> ${resolvedUrl}`);
1799
1805
  return resolvedUrl;
1800
1806
  }
1801
- case "localFilePath": {
1802
- const resolvedUrl = resolveWithBasePath(normalize(decodedUrl), {
1803
- basePath,
1804
- cwd
1805
- });
1806
- const resolvedUrlWithMatchedExtension = pathExistsInAllFiles(addExtensionIfMissing(resolvedUrl, "md"), allFilePaths ?? []) ?? pathExistsInAllFiles(addExtension(resolvedUrl, "md"), allFilePaths ?? []) ?? void 0;
1807
- if (resolvedUrlWithMatchedExtension !== void 0) {
1808
- if (convertFilePathsToProtocol !== "none" && (type === "link" || type === "embed" && [".md", ".pdf"].includes(getExtension(resolvedUrlWithMatchedExtension)))) {
1809
- if (convertFilePathsToProtocol === "obsidian" && obsidianVaultName !== void 0) return createObsidianVaultLink(resolvedUrlWithMatchedExtension, basePath ?? "", obsidianVaultName);
1810
- if (convertFilePathsToProtocol === "file") return createFileLink(resolvedUrlWithMatchedExtension);
1811
- }
1812
- return getBase(resolvedUrlWithMatchedExtension);
1807
+ if (sourceType === "localFileUrl") {
1808
+ const resolvedUrl = normalize(fileUrlToPath(currentPathOrUrl));
1809
+ if (getSrcType(resolvedUrl) === "localFilePath") {
1810
+ currentPathOrUrl = resolvedUrl;
1811
+ continue;
1813
1812
  }
1814
- return getBase(resolvedUrl);
1813
+ console.warn(`Failed to convert file URL to path: ${currentPathOrUrl} --> ${resolvedUrl}`);
1814
+ return resolvedUrl;
1815
1815
  }
1816
- case "localFileUrl": {
1817
- const resolvedUrl = normalize(fileUrlToPath(filePathOrUrl));
1818
- if (getSrcType(resolvedUrl) === "localFilePath") return resolveLink(resolvedUrl, {
1816
+ switch (sourceType) {
1817
+ case "localFilePath": return resolveLocalFilePath(decodedUrl, {
1819
1818
  allFilePaths,
1820
1819
  basePath,
1821
1820
  convertFilePathsToProtocol,
@@ -1823,17 +1822,36 @@ function resolveLink(filePathOrUrl, options) {
1823
1822
  obsidianVaultName,
1824
1823
  type
1825
1824
  });
1826
- console.warn(`Failed to convert file URL to path: ${filePathOrUrl} --> ${resolvedUrl}`);
1827
- return resolvedUrl;
1825
+ case "obsidianVaultUrl": return currentPathOrUrl;
1826
+ case "remoteHttpUrl": return currentPathOrUrl;
1827
+ case "unsupportedProtocolUrl":
1828
+ console.warn(`Unsupported URL protocol: ${currentPathOrUrl}`);
1829
+ return currentPathOrUrl;
1828
1830
  }
1829
- case "obsidianVaultUrl": return filePathOrUrl;
1830
- case "remoteHttpUrl": return filePathOrUrl;
1831
- case "unsupportedProtocolUrl":
1832
- console.warn(`Unsupported URL protocol: ${filePathOrUrl}`);
1833
- return filePathOrUrl;
1834
1831
  }
1835
1832
  }
1836
1833
  /**
1834
+ * Resolve a decoded local file path to an absolute path or protocol URL.
1835
+ * Extracted from `resolveLink` for the benefit of readability (and the
1836
+ * `max-depth` lint rule).
1837
+ */
1838
+ function resolveLocalFilePath(decodedUrl, options) {
1839
+ const { allFilePaths, basePath, convertFilePathsToProtocol, cwd, obsidianVaultName, type } = options;
1840
+ const resolvedUrl = resolveWithBasePath(normalize(decodedUrl), {
1841
+ basePath,
1842
+ cwd
1843
+ });
1844
+ const resolvedUrlWithMatchedExtension = pathExistsInAllFiles(addExtensionIfMissing(resolvedUrl, "md"), allFilePaths ?? []) ?? pathExistsInAllFiles(addExtension(resolvedUrl, "md"), allFilePaths ?? []) ?? void 0;
1845
+ if (resolvedUrlWithMatchedExtension !== void 0) {
1846
+ if (convertFilePathsToProtocol !== "none" && (type === "link" || type === "embed" && [".md", ".pdf"].includes(getExtension(resolvedUrlWithMatchedExtension)))) {
1847
+ if (convertFilePathsToProtocol === "obsidian" && obsidianVaultName !== void 0) return createObsidianVaultLink(resolvedUrlWithMatchedExtension, basePath ?? "", obsidianVaultName);
1848
+ if (convertFilePathsToProtocol === "file") return createFileLink(resolvedUrlWithMatchedExtension);
1849
+ }
1850
+ return getBase(resolvedUrlWithMatchedExtension);
1851
+ }
1852
+ return getBase(resolvedUrl);
1853
+ }
1854
+ /**
1837
1855
  * Convert from a (usually wiki-style) named link to an absolute path to an
1838
1856
  * actual file to match Obsidian's undocumented link resolution algorithm.
1839
1857
  *
@@ -2047,7 +2065,11 @@ function wikiBasic() {
2047
2065
  return nok(code);
2048
2066
  }
2049
2067
  function startUrl(code) {
2050
- if (code === -5 || code === -4 || code === -3 || code === null) return nok(code);
2068
+ if (code === null || [
2069
+ -5,
2070
+ -4,
2071
+ -3
2072
+ ].includes(code)) return nok(code);
2051
2073
  if (code === 124) return nok(code);
2052
2074
  if (code === 93) return nok(code);
2053
2075
  effects.enter("wikiUrl");
@@ -2057,7 +2079,11 @@ function wikiBasic() {
2057
2079
  return insideUrl;
2058
2080
  }
2059
2081
  function insideUrl(code) {
2060
- if (code === -5 || code === -4 || code === -3 || code === null) return nok(code);
2082
+ if (code === null || [
2083
+ -5,
2084
+ -4,
2085
+ -3
2086
+ ].includes(code)) return nok(code);
2061
2087
  if (code === 124) {
2062
2088
  if (linkLength === 1) return nok(code);
2063
2089
  return transitionToLabel(code);
@@ -2075,7 +2101,11 @@ function wikiBasic() {
2075
2101
  return insideUrl;
2076
2102
  }
2077
2103
  function startLabel(code) {
2078
- if (code === -5 || code === -4 || code === -3 || code === null) return nok(code);
2104
+ if (code === null || [
2105
+ -5,
2106
+ -4,
2107
+ -3
2108
+ ].includes(code)) return nok(code);
2079
2109
  if (code === 93) return lookaheadClosingMarker(code);
2080
2110
  effects.enter("wikiLabel");
2081
2111
  effects.enter("chunkString", { contentType: "string" });
@@ -2084,7 +2114,11 @@ function wikiBasic() {
2084
2114
  return insideLabel;
2085
2115
  }
2086
2116
  function insideLabel(code) {
2087
- if (code === -5 || code === -4 || code === -3 || code === null) return nok(code);
2117
+ if (code === null || [
2118
+ -5,
2119
+ -4,
2120
+ -3
2121
+ ].includes(code)) return nok(code);
2088
2122
  if (code === 93) return lookaheadClosingMarker(code);
2089
2123
  effects.consume(code);
2090
2124
  labelLength++;
@@ -2137,33 +2171,33 @@ function wikiBasic() {
2137
2171
  return insideUrl;
2138
2172
  }
2139
2173
  /** If the next character is also `]`, run `ok`, else `nok`. */
2140
- function closingMarkerLookahead(effects, ok, nok) {
2174
+ function closingMarkerLookahead(checkEffects, checkOk, checkNok) {
2141
2175
  return start;
2142
2176
  function start(code) {
2143
- if (code !== 93) return nok(code);
2144
- effects.enter("wikiMarkerTemp");
2145
- effects.consume(code);
2146
- effects.exit("wikiMarkerTemp");
2147
- return ok(code);
2177
+ if (code !== 93) return checkNok(code);
2178
+ checkEffects.enter("wikiMarkerTemp");
2179
+ checkEffects.consume(code);
2180
+ checkEffects.exit("wikiMarkerTemp");
2181
+ return checkOk(code);
2148
2182
  }
2149
2183
  }
2150
2184
  /** Check if backslash is followed by pipe. */
2151
- function backslashPipeLookahead(effects, ok, nok) {
2185
+ function backslashPipeLookahead(checkEffects, checkOk, checkNok) {
2152
2186
  return start;
2153
2187
  function start(code) {
2154
- if (code !== 92) return nok(code);
2155
- effects.enter("wikiMarkerTemp");
2156
- effects.consume(code);
2188
+ if (code !== 92) return checkNok(code);
2189
+ checkEffects.enter("wikiMarkerTemp");
2190
+ checkEffects.consume(code);
2157
2191
  return checkNext;
2158
2192
  }
2159
2193
  function checkNext(code) {
2160
2194
  if (code === 124) {
2161
- effects.consume(code);
2162
- effects.exit("wikiMarkerTemp");
2163
- return ok(code);
2195
+ checkEffects.consume(code);
2196
+ checkEffects.exit("wikiMarkerTemp");
2197
+ return checkOk(code);
2164
2198
  }
2165
- effects.exit("wikiMarkerTemp");
2166
- return nok(code);
2199
+ checkEffects.exit("wikiMarkerTemp");
2200
+ return checkNok(code);
2167
2201
  }
2168
2202
  }
2169
2203
  function transitionToLabel(code) {
@@ -2180,16 +2214,16 @@ function wikiBasic() {
2180
2214
  }, lookaheadClosingMarker, startLabel)(code);
2181
2215
  }
2182
2216
  /** Check if next character is a closing bracket. */
2183
- function closingBracketLookahead(effects, ok, nok) {
2217
+ function closingBracketLookahead(checkEffects, checkOk, checkNok) {
2184
2218
  return start;
2185
2219
  function start(code) {
2186
2220
  if (code === 93) {
2187
- effects.enter("wikiMarkerTemp");
2188
- effects.consume(code);
2189
- effects.exit("wikiMarkerTemp");
2190
- return ok(code);
2221
+ checkEffects.enter("wikiMarkerTemp");
2222
+ checkEffects.consume(code);
2223
+ checkEffects.exit("wikiMarkerTemp");
2224
+ return checkOk(code);
2191
2225
  }
2192
- return nok(code);
2226
+ return checkNok(code);
2193
2227
  }
2194
2228
  }
2195
2229
  function consumePipeAfterBackslash(code) {
@@ -2231,7 +2265,7 @@ const plugin = function() {
2231
2265
  };
2232
2266
  //#endregion
2233
2267
  //#region src/lib/parse/remark-utilities.ts
2234
- const CLOZE_NUMBER_PREFIX_REGEX = /^[(|]?(\d{1,2})(?:[\s).|]|$)(.*)$/;
2268
+ const CLOZE_NUMBER_PREFIX_REGEX = /* @__PURE__ */ new RegExp("^[\\(\\|]?(\\d{1,2})(?:[\\s\\).\\|]|$)(.*)$", "v");
2235
2269
  const defaultAstFromMarkdownOptions = { ...defaultGlobalOptions };
2236
2270
  async function getAstFromMarkdown(markdown, options) {
2237
2271
  const { allFilePaths, basePath, cwd, obsidianVault, resolveUrls } = deepmerge(defaultAstFromMarkdownOptions, options ?? {});
@@ -2253,10 +2287,9 @@ function isText(node) {
2253
2287
  }
2254
2288
  function deleteFirstNodeOfType(tree, nodeType) {
2255
2289
  visit(tree, nodeType, (_, index, parent) => {
2256
- if (parent && index !== void 0) {
2257
- parent.children.splice(index, 1);
2258
- return EXIT;
2259
- }
2290
+ if (!parent || index === void 0) return;
2291
+ parent.children.splice(index, 1);
2292
+ return EXIT;
2260
2293
  });
2261
2294
  return tree;
2262
2295
  }
@@ -2289,13 +2322,14 @@ function replaceDeleteNodesWithClozeMarkup(ast) {
2289
2322
  let clozeIndex = 1;
2290
2323
  visit(ast, "delete", (node, index, parent) => {
2291
2324
  if (parent === void 0 || index === void 0 || !("children" in node) || node.children.length === 0) return CONTINUE;
2292
- if (node.children.length > 0 && isText(node.children[0])) {
2293
- const result = CLOZE_NUMBER_PREFIX_REGEX.exec(node.children[0].value);
2325
+ const [firstChild] = node.children;
2326
+ if (node.children.length > 0 && isText(firstChild)) {
2327
+ const result = CLOZE_NUMBER_PREFIX_REGEX.exec(firstChild.value);
2294
2328
  if (result !== null && (node.children.length > 1 || (result.at(2) ?? "").length > 0)) {
2295
- const possibleClozeIndex = Number.parseInt(result.at(1) ?? "", 10);
2329
+ const possibleClozeIndex = Number(result.at(1));
2296
2330
  if (!Number.isNaN(possibleClozeIndex)) {
2297
2331
  clozeIndex = possibleClozeIndex;
2298
- node.children[0].value = (result.at(2)?.trim().length ?? 0) > 0 ? result.at(2) ?? "" : "";
2332
+ firstChild.value = (result.at(2)?.trim().length ?? 0) > 0 ? result.at(2) ?? "" : "";
2299
2333
  }
2300
2334
  }
2301
2335
  }
@@ -2369,18 +2403,18 @@ function getFrontmatterFromTree(ast) {
2369
2403
  rawYaml = node.value;
2370
2404
  return EXIT;
2371
2405
  });
2372
- if (!rawYaml) return {};
2373
- const parsedYaml = parse(rawYaml);
2374
- if (!parsedYaml) throw new Error("Could not parse frontmatter");
2406
+ if (rawYaml === void 0 || rawYaml === "") return {};
2407
+ const parsedYaml = parse(rawYaml) ?? void 0;
2408
+ if (parsedYaml === void 0) throw new Error("Could not parse frontmatter");
2375
2409
  return parsedYaml;
2376
2410
  }
2377
2411
  function hasThematicBreak(ast) {
2378
- let hasThematicBreak = false;
2412
+ let foundThematicBreak = false;
2379
2413
  visit(ast, "thematicBreak", () => {
2380
- hasThematicBreak = true;
2414
+ foundThematicBreak = true;
2381
2415
  return EXIT;
2382
2416
  });
2383
- return hasThematicBreak;
2417
+ return foundThematicBreak;
2384
2418
  }
2385
2419
  function isLastVisibleNodeEmphasisWithOthers(ast) {
2386
2420
  let lastVisibleNode;
@@ -2782,7 +2816,7 @@ function formatSetStyleResult(result, verbose = false) {
2782
2816
  }
2783
2817
  //#endregion
2784
2818
  //#region src/lib/model/frontmatter.ts
2785
- const NEWLINE_REGEX = /\r?\n/;
2819
+ const NEWLINE_REGEX = /* @__PURE__ */ new RegExp("\\r?\\n", "v");
2786
2820
  /**
2787
2821
  * Update the noteId in the frontmatter of a markdown string.
2788
2822
  *
@@ -2910,7 +2944,7 @@ async function syncNotes(allLocalNotes, options) {
2910
2944
  if (localNote.noteId === void 0) throw new Error("Note ID is undefined");
2911
2945
  matchedIds.add(localNote.noteId);
2912
2946
  }
2913
- const orphanedNotes = remoteNotes.filter((remoteNote) => !allLocalNotesCopy.some((localNote) => localNote.noteId === remoteNote.noteId));
2947
+ const orphanedNotes = remoteNotes.filter((remoteNote) => allLocalNotesCopy.every((localNote) => localNote.noteId !== remoteNote.noteId));
2914
2948
  await deleteNotes(client, orphanedNotes, dryRun);
2915
2949
  for (const orphanedNote of orphanedNotes) synced.push({
2916
2950
  action: "deleted",
@@ -3043,7 +3077,7 @@ async function syncFiles(allLocalFilePaths, options) {
3043
3077
  filePath: void 0,
3044
3078
  filePathOriginal: void 0,
3045
3079
  note: note.note
3046
- })), ...liveNotes].sort((a, b) => (a.filePath ?? "").localeCompare(b.filePath ?? ""));
3080
+ })), ...liveNotes].toSorted((a, b) => (a.filePath ?? "").localeCompare(b.filePath ?? ""));
3047
3081
  return {
3048
3082
  ankiWeb,
3049
3083
  deletedDecks,
@@ -3060,7 +3094,7 @@ function formatSyncFilesResult(result, verbose = false) {
3060
3094
  const lines = [];
3061
3095
  const { synced } = result;
3062
3096
  const actionCounts = synced.reduce((acc, note) => {
3063
- acc[note.action] = (acc[note.action] || 0) + 1;
3097
+ acc[note.action] = (acc[note.action] ?? 0) + 1;
3064
3098
  return acc;
3065
3099
  }, {});
3066
3100
  const totalSynced = synced.filter((note) => note.action !== "deleted").length;