yanki 2.0.9 → 2.0.11

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
- const leadingSpace = LEADING_WHITESPACE_REGEX.exec(lines[0])?.[0] ?? "";
114
- const leadingSpaceRegex = new RegExp(`^${leadingSpace}`);
113
+ const leadingSpace = LEADING_WHITESPACE_REGEX.exec(lines[0] ?? "")?.[0] ?? "";
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,29 +706,31 @@ 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
- }
736
- }
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);
720
+ }
721
+ const DEFAULT_PORT_BY_PROTOCOL = {
722
+ "ftp:": 21,
723
+ "http:": 80,
724
+ "https:": 443,
725
+ "ws:": 80,
726
+ "wss:": 443
727
+ };
737
728
  function urlToHostAndPort(url) {
738
729
  const parsedUrl = safeParseUrl(url);
739
- return parsedUrl === void 0 ? void 0 : {
730
+ if (parsedUrl === void 0) return;
731
+ return {
740
732
  host: `${parsedUrl.protocol}//${parsedUrl.hostname}`,
741
- port: Number.parseInt(parsedUrl.port, 10)
733
+ port: parsedUrl.port === "" ? DEFAULT_PORT_BY_PROTOCOL[parsedUrl.protocol] ?? NaN : Number(parsedUrl.port)
742
734
  };
743
735
  }
744
736
  function hostAndPortToUrl(host, port) {
@@ -786,7 +778,7 @@ async function getContentHash(absolutePathOrUrl, fileAdapter, fetchAdapter) {
786
778
  }
787
779
  //#endregion
788
780
  //#region src/lib/parse/rehype-mathjax-anki.ts
789
- const CONSECUTIVE_BRACES_REGEX = /([{}])(?=[{}])/g;
781
+ const CONSECUTIVE_BRACES_REGEX = /* @__PURE__ */ new RegExp("([\\{\\}])(?=[\\{\\}])", "gv");
790
782
  function sanitizeBraces(value) {
791
783
  return value.replaceAll(CONSECUTIVE_BRACES_REGEX, "$1 ");
792
784
  }
@@ -801,9 +793,10 @@ const plugin$3 = function() {
801
793
  let fenced = false;
802
794
  visit(tree, (node, index, parent) => {
803
795
  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")) {
796
+ const [preChild] = node.children;
797
+ 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
798
  fenced = true;
806
- parent.children.splice(index, 1, node.children[0]);
799
+ parent.children[index] = preChild;
807
800
  }
808
801
  if (node.tagName === "code" && Array.isArray(node.properties.className) && node.properties.className.includes("language-math")) {
809
802
  const isBlock = node.properties.className.includes("math-display") || fenced;
@@ -822,6 +815,7 @@ const plugin$3 = function() {
822
815
  }
823
816
  ];
824
817
  }
818
+ return CONTINUE;
825
819
  });
826
820
  };
827
821
  };
@@ -838,7 +832,7 @@ const plugin$2 = function() {
838
832
  };
839
833
  //#endregion
840
834
  //#region src/lib/parse/rehype-utilities.ts
841
- const DIMENSION_CHARS_REGEX = /^[\dx]+$/;
835
+ const DIMENSION_CHARS_REGEX = /* @__PURE__ */ new RegExp("^[\\dx]+$", "v");
842
836
  const highlighter = createHighlighterCoreSync({
843
837
  engine: createJavaScriptRegexEngine(),
844
838
  langs: [
@@ -927,7 +921,7 @@ async function mdastToHtml(mdast, options) {
927
921
  const syncEnabled = yankiSyncMedia && ankiMediaFilename !== void 0 ? "true" : "false";
928
922
  if (!supportedMedia || MEDIA_SUPPORTED_FILE_EXTENSIONS.includes(extension)) {
929
923
  const finalSourceWithQuery = isUrl(finalSrc) ? finalSrc : `${finalSrc}${getQuery(String(node.properties.dataYankiSrcOriginal))}`;
930
- parent.children.splice(index, 1, u("element", {
924
+ parent.children[index] = u("element", {
931
925
  properties: {
932
926
  className: ["yanki-media", `yanki-media-${supportedMedia ? "file" : "unsupported"}`],
933
927
  "data-yanki-alt-text": node.properties.alt,
@@ -940,13 +934,13 @@ async function mdastToHtml(mdast, options) {
940
934
  }, [u("element", {
941
935
  properties: { href: finalSourceWithQuery },
942
936
  tagName: "a"
943
- }, [u("text", decodeURI(String(node.properties.dataYankiSrcOriginal)))])]));
937
+ }, [u("text", decodeURI(String(node.properties.dataYankiSrcOriginal)))])]);
944
938
  } else if (MEDIA_SUPPORTED_IMAGE_EXTENSIONS.includes(extension)) {
945
939
  node.properties.src = finalSrc;
946
940
  node.properties.className = ["yanki-media", "yanki-media-image"];
947
941
  node.properties.dataYankiMediaSrc = absolutePathOrUrl;
948
942
  node.properties.dataYankiMediaSync = syncEnabled;
949
- } else if (MEDIA_SUPPORTED_AUDIO_VIDEO_EXTENSIONS.includes(extension)) parent.children.splice(index, 1, u("element", {
943
+ } else if (MEDIA_SUPPORTED_AUDIO_VIDEO_EXTENSIONS.includes(extension)) parent.children[index] = u("element", {
950
944
  properties: {
951
945
  className: ["yanki-media", "yanki-media-audio-video"],
952
946
  "data-yanki-alt-text": node.properties.alt,
@@ -956,17 +950,19 @@ async function mdastToHtml(mdast, options) {
956
950
  "data-yanki-src-original": node.properties.dataYankiSrcOriginal
957
951
  },
958
952
  tagName: "span"
959
- }, [u("text", `[sound:${finalSrc}]`)]));
953
+ }, [u("text", `[sound:${finalSrc}]`)]);
960
954
  });
955
+ return CONTINUE;
961
956
  });
962
957
  for (const mutationPromise of treeMutationPromises) await mutationPromise();
963
958
  visit(hastWithClass, "element", (node, index, parent) => {
964
959
  if (parent === void 0 || index === void 0 || node.tagName !== "img" || emptyIsUndefined(String(node.properties.alt)) === void 0) return CONTINUE;
965
- const { alt, height, width } = parseDimensionsFromAltText(String(node.properties.alt ?? ""));
960
+ const { alt, height, width } = parseDimensionsFromAltText(node.properties.alt ?? "");
966
961
  if (alt === void 0) delete node.properties.alt;
967
962
  else node.properties.alt = alt;
968
963
  if (height !== void 0) node.properties.height = height;
969
964
  if (width !== void 0) node.properties.width = width;
965
+ return CONTINUE;
970
966
  });
971
967
  const isEmpty = isVisuallyEmpty(hastWithClass);
972
968
  if (isEmpty && !useEmptyPlaceholder) return "";
@@ -974,33 +970,32 @@ async function mdastToHtml(mdast, options) {
974
970
  return addBoilerplateComment(processor.stringify(nonEmptyHast)).trim();
975
971
  }
976
972
  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}`;
973
+ function addBoilerplateComment(htmlText) {
974
+ return `<!-- This HTML was generated by Yanki, a Markdown to Anki converter. Do not edit directly. -->\n${htmlText}`;
979
975
  }
980
976
  function hastToPlainText(hast) {
981
977
  return toText(hast);
982
978
  }
983
- function htmlToPlainText(html) {
984
- return hastToPlainText(htmlProcessor.parse(html));
979
+ function htmlToPlainText(htmlText) {
980
+ return hastToPlainText(htmlProcessor.parse(htmlText));
985
981
  }
986
- function getAllLinesOfHtmlAsPlainText(html) {
987
- return htmlToPlainText(html).split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join(" ");
982
+ function getAllLinesOfHtmlAsPlainText(htmlText) {
983
+ return htmlToPlainText(htmlText).split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join(" ");
988
984
  }
989
- function getFirstLineOfHtmlAsPlainText(html) {
990
- return htmlToPlainText(html).split("\n").map((line) => line.trim()).find((line) => line.length > 0) ?? "";
985
+ function getFirstLineOfHtmlAsPlainText(htmlText) {
986
+ return htmlToPlainText(htmlText).split("\n").map((line) => line.trim()).find((line) => line.length > 0) ?? "";
991
987
  }
992
- function extractMediaFromHtml(html) {
993
- const hast = htmlProcessor.parse(html);
988
+ function extractMediaFromHtml(htmlText) {
989
+ const hast = htmlProcessor.parse(htmlText);
994
990
  const media = [];
995
991
  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
- }
992
+ if (node.tagName !== "img" && node.tagName !== "span" || node.properties?.dataYankiMediaSync !== "true") return;
993
+ const filename = node.properties?.src ?? node.properties?.dataYankiSrc;
994
+ const originalSrc = node.properties?.dataYankiMediaSrc;
995
+ if (filename !== void 0 && originalSrc !== void 0 && typeof filename === "string" && typeof originalSrc === "string") media.push({
996
+ filename,
997
+ originalSrc
998
+ });
1004
999
  });
1005
1000
  return media;
1006
1001
  }
@@ -1028,7 +1023,7 @@ function parseDimensions(dimensions) {
1028
1023
  height: void 0
1029
1024
  };
1030
1025
  if (!dimensions.includes("x")) {
1031
- const widthOnly = Number.parseInt(dimensions, 10);
1026
+ const widthOnly = Number(dimensions);
1032
1027
  if (!Number.isNaN(widthOnly)) return {
1033
1028
  width: widthOnly,
1034
1029
  height: void 0
@@ -1050,7 +1045,7 @@ function parseDimensions(dimensions) {
1050
1045
  function isVisuallyEmpty(tree) {
1051
1046
  let hasVisualContent = false;
1052
1047
  visit(tree, (node) => {
1053
- if (hasVisualContent) return;
1048
+ if (hasVisualContent) return EXIT;
1054
1049
  if (node.type === "element") {
1055
1050
  const element = node;
1056
1051
  const { tagName } = element;
@@ -1068,7 +1063,7 @@ function isVisuallyEmpty(tree) {
1068
1063
  hasVisualContent = true;
1069
1064
  return EXIT;
1070
1065
  }
1071
- if (toText(element).trim()) {
1066
+ if (toText(element).trim() !== "") {
1072
1067
  hasVisualContent = true;
1073
1068
  return EXIT;
1074
1069
  }
@@ -1077,6 +1072,7 @@ function isVisuallyEmpty(tree) {
1077
1072
  hasVisualContent = true;
1078
1073
  return EXIT;
1079
1074
  }
1075
+ return CONTINUE;
1080
1076
  });
1081
1077
  return !hasVisualContent;
1082
1078
  }
@@ -1091,10 +1087,9 @@ function isVisuallyEmpty(tree) {
1091
1087
  */
1092
1088
  function addFirstChildToFirstDiv(tree, newChild) {
1093
1089
  visit(tree, "element", (node) => {
1094
- if (node.tagName === "div") {
1095
- node.children.unshift(newChild);
1096
- return EXIT;
1097
- }
1090
+ if (node.tagName !== "div") return;
1091
+ node.children.unshift(newChild);
1092
+ return EXIT;
1098
1093
  });
1099
1094
  return tree;
1100
1095
  }
@@ -1196,26 +1191,26 @@ async function deleteNotes(client, notes, dryRun = false) {
1196
1191
  async function addNote(client, note, dryRun, fileAdapter) {
1197
1192
  if (note.noteId !== void 0) throw new Error("Note already has an ID");
1198
1193
  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");
1194
+ let newNote;
1195
+ try {
1196
+ newNote = await client.note.addNote({ note: {
1197
+ ...note,
1198
+ options: { allowDuplicate: true }
1199
+ } }) ?? void 0;
1200
+ } catch (error) {
1201
+ if (error instanceof Error) if (error.message === `model was not found: ${note.modelName}`) {
1202
+ const model = yankiModels.find((model) => model.modelName === note.modelName);
1203
+ if (model === void 0) throw new Error(`Model not found: ${note.modelName}`, { cause: error });
1204
+ await ensureModelExists(client, model);
1205
+ newNote = await addNote(client, note, dryRun, fileAdapter);
1206
+ } else if (error.message === `deck was not found: ${note.deckName}`) {
1207
+ if (note.deckName === "") throw new Error("Deck name is empty", { cause: error });
1208
+ await client.deck.createDeck({ deck: note.deckName });
1209
+ newNote = await addNote(client, note, dryRun, fileAdapter);
1210
+ } else throw error;
1211
+ else throw new TypeError("Unknown error", { cause: error });
1212
+ }
1213
+ if (newNote === void 0) throw new Error("Note creation failed");
1219
1214
  await uploadMediaForNote(client, note, dryRun, fileAdapter);
1220
1215
  return newNote;
1221
1216
  }
@@ -1244,21 +1239,21 @@ async function updateNote(client, localNote, remoteNote, dryRun, fileAdapter) {
1244
1239
  }
1245
1240
  if (!areTagsEqual(localNote.tags ?? [], remoteNote.tags ?? []) || !areFieldsEqual(localNote.fields, remoteNote.fields) || localNote.modelName !== remoteNote.modelName) {
1246
1241
  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
- });
1242
+ try {
1243
+ await client.note.updateNoteModel({ note: {
1244
+ ...localNote,
1245
+ id: localNote.noteId,
1246
+ tags: localNote.tags ?? []
1247
+ } });
1248
+ } catch (error) {
1249
+ if (error instanceof Error) if (error.message === `Model '${localNote.modelName}' not found`) {
1250
+ const model = yankiModels.find((model) => model.modelName === localNote.modelName);
1251
+ if (model === void 0) throw new Error(`Model not found: ${localNote.modelName}`, { cause: error });
1252
+ await ensureModelExists(client, model);
1253
+ await updateNote(client, localNote, remoteNote, dryRun, fileAdapter);
1254
+ } else throw error;
1255
+ else throw new TypeError("Unknown error", { cause: error });
1256
+ }
1262
1257
  await uploadMediaForNote(client, localNote, dryRun, fileAdapter);
1263
1258
  }
1264
1259
  updated = true;
@@ -1275,9 +1270,13 @@ function areFieldsEqual(localFields, remoteFields) {
1275
1270
  "Front",
1276
1271
  "Back",
1277
1272
  "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;
1273
+ ]) {
1274
+ const localValue = Object.hasOwn(localFields, key) ? localFields[key] : void 0;
1275
+ const remoteValue = Object.hasOwn(remoteFields, key) ? remoteFields[key] : void 0;
1276
+ if (localValue !== void 0 && remoteValue !== void 0) {
1277
+ if (localValue.normalize("NFC") !== remoteValue.normalize("NFC")) return false;
1278
+ } else if (localValue !== void 0 || remoteValue !== void 0) return false;
1279
+ }
1281
1280
  return true;
1282
1281
  }
1283
1282
  function areNotesEqual(noteA, noteB, includeId = true) {
@@ -1302,7 +1301,7 @@ function areNotesEqual(noteA, noteB, includeId = true) {
1302
1301
  function areTagsEqual(localTags, remoteTags) {
1303
1302
  const localTagsSet = new Set(localTags.map((tag) => tag.normalize("NFC").toLowerCase()));
1304
1303
  const remoteTagsSet = new Set(remoteTags.map((tag) => tag.normalize("NFC").toLowerCase()));
1305
- return new Set([...localTagsSet, ...remoteTagsSet]).size === remoteTagsSet.size;
1304
+ return localTagsSet.union(remoteTagsSet).size === remoteTagsSet.size;
1306
1305
  }
1307
1306
  /**
1308
1307
  * Get all notes from Anki that match the model prefix.
@@ -1317,6 +1316,9 @@ function areTagsEqual(localTags, remoteTags) {
1317
1316
  async function getRemoteNotes(client, namespace = defaultGlobalOptions.namespace) {
1318
1317
  return (await getRemoteNotesById(client, await client.note.findNotes({ query: `"YankiNamespace:${namespace}"` }))).filter((note) => note !== void 0);
1319
1318
  }
1319
+ function getFieldValue(field) {
1320
+ return field?.value ?? "";
1321
+ }
1320
1322
  /**
1321
1323
  * Get all data from Anki required to populate the YankiNote type.
1322
1324
  *
@@ -1335,8 +1337,8 @@ async function getRemoteNotes(client, namespace = defaultGlobalOptions.namespace
1335
1337
  */
1336
1338
  async function getRemoteNotesById(client, noteIds) {
1337
1339
  const ankiNotes = await client.note.notesInfo({ notes: noteIds });
1340
+ if (ankiNotes.every((ankiNote) => ankiNote.noteId === void 0)) return Array.from({ length: ankiNotes.length });
1338
1341
  const yankiNotes = [];
1339
- if (ankiNotes.every((ankiNote) => ankiNote.noteId === void 0)) return Array.from({ length: ankiNotes.length }).fill(void 0);
1340
1342
  const allCardIds = ankiNotes.flatMap((note) => note.cards ?? []);
1341
1343
  const deckToCardMap = await client.deck.getDecks({ cards: allCardIds });
1342
1344
  const cardIdToDeckMap = /* @__PURE__ */ new Map();
@@ -1357,38 +1359,21 @@ async function getRemoteNotesById(client, noteIds) {
1357
1359
  const deckConfig = await client.deck.getDeckConfig({ deck: deckName });
1358
1360
  const isFiltered = Boolean(deckConfig.dyn);
1359
1361
  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
- }
1362
+ if (isFiltered) await populateUnfilteredDeckMaps(client, deckFilteredStatusMap, unfilteredDeckNoteIdMap);
1370
1363
  }
1371
1364
  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}`);
1365
+ const unfilteredDeckName = await findUnfilteredDeckNameForNote(client, unfilteredDeckNoteIdMap, ankiNote.noteId);
1366
+ if (unfilteredDeckName === void 0) throw new Error(`No matching non-filtered deck found for note ${ankiNote.noteId}`);
1367
+ deckName = unfilteredDeckName;
1383
1368
  }
1384
1369
  yankiNotes.push({
1385
1370
  cards: ankiNote.cards,
1386
1371
  deckName,
1387
1372
  fields: {
1388
- Back: ankiNote.fields.Back.value ?? "",
1389
- ...ankiNote.fields.Extra !== void 0 && { Extra: ankiNote.fields.Extra.value ?? "" },
1390
- Front: ankiNote.fields.Front.value ?? "",
1391
- YankiNamespace: ankiNote.fields.YankiNamespace.value ?? ""
1373
+ Back: getFieldValue(ankiNote.fields.Back),
1374
+ ...ankiNote.fields.Extra !== void 0 && { Extra: getFieldValue(ankiNote.fields.Extra) },
1375
+ Front: getFieldValue(ankiNote.fields.Front),
1376
+ YankiNamespace: getFieldValue(ankiNote.fields.YankiNamespace)
1392
1377
  },
1393
1378
  modelName: ankiNote.modelName,
1394
1379
  noteId: ankiNote.noteId,
@@ -1397,21 +1382,42 @@ async function getRemoteNotesById(client, noteIds) {
1397
1382
  }
1398
1383
  return yankiNotes;
1399
1384
  }
1385
+ /**
1386
+ * When a filtered deck is spotted, we have to search all decks to find what
1387
+ * non-filtered deck has a given note... so completely populate the filtered
1388
+ * deck status map now, and reuse it for future notes.
1389
+ */
1390
+ async function populateUnfilteredDeckMaps(client, deckFilteredStatusMap, unfilteredDeckNoteIdMap) {
1391
+ const allDeckNames = await client.deck.deckNames();
1392
+ for (const deckName of allDeckNames) {
1393
+ if (deckFilteredStatusMap.has(deckName)) continue;
1394
+ const deckConfig = await client.deck.getDeckConfig({ deck: deckName });
1395
+ deckFilteredStatusMap.set(deckName, Boolean(deckConfig.dyn));
1396
+ }
1397
+ const sortedUnfilteredDeckNames = [...deckFilteredStatusMap].filter(([deck, isFiltered]) => !isFiltered && deck !== "Default").map(([deck]) => deck).toSorted((a, b) => b.split("::").length - a.split("::").length);
1398
+ sortedUnfilteredDeckNames.push("Default");
1399
+ for (const deckName of sortedUnfilteredDeckNames) unfilteredDeckNoteIdMap.set(deckName, void 0);
1400
+ }
1401
+ /**
1402
+ * Find the "real" non-filtered deck containing a note that's currently in a
1403
+ * filtered deck. Lazily populates and caches the note IDs for each unfiltered
1404
+ * deck as needed.
1405
+ *
1406
+ * @returns The name of the unfiltered deck containing the note, or undefined if
1407
+ * none is found.
1408
+ */
1409
+ async function findUnfilteredDeckNameForNote(client, unfilteredDeckNoteIdMap, noteId) {
1410
+ for (const nameToCheck of unfilteredDeckNoteIdMap.keys()) {
1411
+ if (unfilteredDeckNoteIdMap.get(nameToCheck) === void 0) unfilteredDeckNoteIdMap.set(nameToCheck, await client.note.findNotes({ query: `"deck:${nameToCheck}"` }));
1412
+ if (unfilteredDeckNoteIdMap.get(nameToCheck)?.includes(noteId)) return nameToCheck;
1413
+ }
1414
+ }
1400
1415
  async function deleteOrphanedDecks(client, activeNotes, originalNotes, dryRun) {
1401
1416
  const activeNoteDeckNames = [...new Set(activeNotes.map((note) => note.deckName))].filter(Boolean);
1402
1417
  const orphanedDeckNames = [...new Set(originalNotes.map((note) => note.deckName))].filter(Boolean).filter((deckName) => !activeNoteDeckNames.includes(deckName));
1403
1418
  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();
1419
+ for (const orphanedDeckName of orphanedDeckNames) orphanedParentDeckNames.push(...getOrphanedParentDeckNames(orphanedDeckName, activeNoteDeckNames));
1420
+ const deckDeletionCandidates = [.../* @__PURE__ */ new Set([...orphanedDeckNames, ...orphanedParentDeckNames])].toSorted();
1415
1421
  const decksToDelete = [];
1416
1422
  for (const deckName of deckDeletionCandidates) {
1417
1423
  const deckStatsObject = await client.deck.getDeckStats({ decks: [deckName] });
@@ -1439,25 +1445,41 @@ async function deleteOrphanedDecks(client, activeNotes, originalNotes, dryRun) {
1439
1445
  return decksToDelete;
1440
1446
  }
1441
1447
  /**
1448
+ * Walk up an orphaned deck's ancestry, collecting parent deck names until one
1449
+ * is found that still contains an active note's deck.
1450
+ */
1451
+ function getOrphanedParentDeckNames(orphanedDeckName, activeNoteDeckNames) {
1452
+ const orphanedParentDeckNames = [];
1453
+ const parts = orphanedDeckName.split("::");
1454
+ while (parts.length > 1) {
1455
+ parts.pop();
1456
+ const parentDeckName = parts.join("::");
1457
+ if (activeNoteDeckNames.some((deckName) => deckName.startsWith(`${parentDeckName}::`))) break;
1458
+ orphanedParentDeckNames.push(parentDeckName);
1459
+ }
1460
+ return orphanedParentDeckNames;
1461
+ }
1462
+ /**
1442
1463
  * Global! Does not respect namespace. You can write namespace checks into your
1443
1464
  * css if you want.
1444
1465
  */
1445
1466
  async function updateModelStyle(client, modelName, css, dryRun) {
1446
1467
  let currentCss;
1447
1468
  try {
1448
- const { css } = await client.model.modelStyling({ modelName });
1449
- currentCss = css;
1469
+ const { css: existingCss } = await client.model.modelStyling({ modelName });
1470
+ currentCss = existingCss;
1450
1471
  } catch (error) {
1451
1472
  if (error instanceof Error) {
1452
1473
  if (error.message === `model was not found: ${modelName}`) {
1453
1474
  const model = yankiModels.find((model) => model.modelName === modelName);
1454
- if (model === void 0) throw new Error(`Model not found: ${modelName}`);
1475
+ if (model === void 0) throw new Error(`Model not found: ${modelName}`, { cause: error });
1455
1476
  if (dryRun) return false;
1456
1477
  await ensureModelExists(client, model);
1457
1478
  return updateModelStyle(client, model.modelName, css, dryRun);
1458
1479
  }
1459
1480
  throw error;
1460
- } else throw new TypeError("Unknown error");
1481
+ }
1482
+ throw new TypeError("Unknown error", { cause: error });
1461
1483
  }
1462
1484
  if (currentCss !== void 0 && currentCss === css) return false;
1463
1485
  if (!dryRun) await client.model.updateModelStyling({ model: {
@@ -1514,7 +1536,8 @@ async function reconcileMedia(client, liveNotes, namespace, dryRun, fileAdapter)
1514
1536
  const activeMediaFilenames = new Set(expectedMedia.map(({ filename }) => filename));
1515
1537
  const allMediaInNamespace = await client.media.getMediaFilesNames({ pattern: `${slugifiedNamespace}-*` });
1516
1538
  const deletedMediaFilenames = [];
1517
- for (const remoteMediaFilename of allMediaInNamespace) if (!activeMediaFilenames.has(remoteMediaFilename)) {
1539
+ for (const remoteMediaFilename of allMediaInNamespace) {
1540
+ if (activeMediaFilenames.has(remoteMediaFilename)) continue;
1518
1541
  await client.media.deleteMediaFile({ filename: remoteMediaFilename });
1519
1542
  deletedMediaFilenames.push(remoteMediaFilename);
1520
1543
  }
@@ -1555,7 +1578,7 @@ async function requestPermission(client) {
1555
1578
  try {
1556
1579
  const { permission } = await client.miscellaneous.requestPermission();
1557
1580
  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";
1581
+ return "granted";
1559
1582
  } catch (error) {
1560
1583
  if (error instanceof Error && (error.message === "fetch failed" || error.message === "net::ERR_CONNECTION_REFUSED")) return "ankiUnreachable";
1561
1584
  throw error;
@@ -1657,8 +1680,8 @@ function formatListResult(result) {
1657
1680
  }
1658
1681
  //#endregion
1659
1682
  //#region src/lib/utilities/filenames.ts
1660
- const CLOZE_CONTENT_REGEX = /\{\{\w\d*\s?:{0,2}([^:}]+)/;
1661
- const FILENAME_INCREMENT_SUFFIX_REGEX = /\s\(\d+\)$/;
1683
+ const CLOZE_CONTENT_REGEX = /* @__PURE__ */ new RegExp("\\{\\{\\w\\d*\\s?:{0,2}([^:\\}]+)", "v");
1684
+ const FILENAME_INCREMENT_SUFFIX_REGEX = /* @__PURE__ */ new RegExp("\\s\\(\\d+\\)$", "v");
1662
1685
  function getSafeTitleForNote(note, manageFilenames, maxLength) {
1663
1686
  if (manageFilenames === "off") throw new Error("manageFilenames must not be off");
1664
1687
  switch (note.modelName) {
@@ -1674,9 +1697,9 @@ function getSafeTitleForNote(note, manageFilenames, maxLength) {
1674
1697
  }
1675
1698
  case "Yanki - Cloze": {
1676
1699
  const cleanFront = getAllLinesOfHtmlAsPlainText(note.fields.Front);
1677
- const textBeforeCloze = emptyIsUndefined(cleanFront.split("{{").at(0) ?? "");
1700
+ const textBeforeCloze = emptyIsUndefined(cleanFront.split("{{", 1).at(0) ?? "");
1678
1701
  const firstClozeText = emptyIsUndefined(CLOZE_CONTENT_REGEX.exec(cleanFront)?.at(1));
1679
- const textAfterCloze = emptyIsUndefined(cleanFront.split("}}").at(1)?.split("{{").at(0) ?? "");
1702
+ const textAfterCloze = emptyIsUndefined(cleanFront.split("}}", 2).at(1)?.split("{{", 1).at(0) ?? "");
1680
1703
  switch (manageFilenames) {
1681
1704
  case "prompt": return getSafeFilename(textBeforeCloze ?? textAfterCloze ?? firstClozeText ?? "", maxLength);
1682
1705
  case "response": return getSafeFilename(firstClozeText ?? textBeforeCloze ?? textAfterCloze ?? "", maxLength);
@@ -1697,7 +1720,7 @@ function getSafeFilename(text, maxLength) {
1697
1720
  let basicSafeFilename = filenamify(getFirstLineOfHtmlAsPlainText(text).trim(), {
1698
1721
  maxLength: Number.MAX_SAFE_INTEGER,
1699
1722
  replacement: " "
1700
- }).replaceAll(/\s+/g, " ").trim();
1723
+ }).replaceAll(/* @__PURE__ */ new RegExp("\\s+", "gv"), " ").trim();
1701
1724
  if (basicSafeFilename.length === 0) basicSafeFilename = MEDIA_DEFAULT_EMPTY_FILENAME;
1702
1725
  basicSafeFilename = basicSafeFilename.normalize("NFC");
1703
1726
  if (maxLength === void 0) return basicSafeFilename;
@@ -1740,7 +1763,7 @@ function getTemporarilyUniqueFilePath(filePath) {
1740
1763
  }
1741
1764
  //#endregion
1742
1765
  //#region src/lib/utilities/resolve-link.ts
1743
- const MD_EXTENSION_REGEX = /\.md$/;
1766
+ const MD_EXTENSION_REGEX = /* @__PURE__ */ new RegExp("\\.md$", "v");
1744
1767
  const defaultResolveLinkOptions = {
1745
1768
  allFilePaths: [],
1746
1769
  basePath: void 0,
@@ -1779,43 +1802,34 @@ const defaultResolveLinkOptions = {
1779
1802
  function resolveLink(filePathOrUrl, options) {
1780
1803
  const { allFilePaths, basePath, convertFilePathsToProtocol, cwd, obsidianVaultName, type } = deepmerge(defaultResolveLinkOptions, options ?? {});
1781
1804
  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": {
1805
+ let currentPathOrUrl = filePathOrUrl;
1806
+ for (;;) {
1807
+ const decodedUrl = safeDecodeURI(currentPathOrUrl) ?? currentPathOrUrl;
1808
+ const sourceType = getSrcType(decodedUrl);
1809
+ if (sourceType === "localFileName") {
1785
1810
  let resolvedUrl = addExtensionIfMissing(normalize(decodedUrl), "md");
1786
1811
  resolvedUrl = resolveNameLink(resolvedUrl, cwd, allFilePaths ?? []) ?? resolveWithBasePath(decodedUrl, {
1787
1812
  basePath,
1788
1813
  cwd
1789
1814
  });
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}`);
1815
+ if (getSrcType(resolvedUrl) === "localFilePath") {
1816
+ currentPathOrUrl = resolvedUrl;
1817
+ continue;
1818
+ }
1819
+ console.warn(`Failed to convert local file wiki-style name to path: ${currentPathOrUrl} --> ${resolvedUrl}`);
1799
1820
  return resolvedUrl;
1800
1821
  }
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);
1822
+ if (sourceType === "localFileUrl") {
1823
+ const resolvedUrl = normalize(fileUrlToPath(currentPathOrUrl));
1824
+ if (getSrcType(resolvedUrl) === "localFilePath") {
1825
+ currentPathOrUrl = resolvedUrl;
1826
+ continue;
1813
1827
  }
1814
- return getBase(resolvedUrl);
1828
+ console.warn(`Failed to convert file URL to path: ${currentPathOrUrl} --> ${resolvedUrl}`);
1829
+ return resolvedUrl;
1815
1830
  }
1816
- case "localFileUrl": {
1817
- const resolvedUrl = normalize(fileUrlToPath(filePathOrUrl));
1818
- if (getSrcType(resolvedUrl) === "localFilePath") return resolveLink(resolvedUrl, {
1831
+ switch (sourceType) {
1832
+ case "localFilePath": return resolveLocalFilePath(decodedUrl, {
1819
1833
  allFilePaths,
1820
1834
  basePath,
1821
1835
  convertFilePathsToProtocol,
@@ -1823,15 +1837,34 @@ function resolveLink(filePathOrUrl, options) {
1823
1837
  obsidianVaultName,
1824
1838
  type
1825
1839
  });
1826
- console.warn(`Failed to convert file URL to path: ${filePathOrUrl} --> ${resolvedUrl}`);
1827
- return resolvedUrl;
1840
+ case "obsidianVaultUrl": return currentPathOrUrl;
1841
+ case "remoteHttpUrl": return currentPathOrUrl;
1842
+ case "unsupportedProtocolUrl":
1843
+ console.warn(`Unsupported URL protocol: ${currentPathOrUrl}`);
1844
+ return currentPathOrUrl;
1845
+ }
1846
+ }
1847
+ }
1848
+ /**
1849
+ * Resolve a decoded local file path to an absolute path or protocol URL.
1850
+ * Extracted from `resolveLink` for the benefit of readability (and the
1851
+ * `max-depth` lint rule).
1852
+ */
1853
+ function resolveLocalFilePath(decodedUrl, options) {
1854
+ const { allFilePaths, basePath, convertFilePathsToProtocol, cwd, obsidianVaultName, type } = options;
1855
+ const resolvedUrl = resolveWithBasePath(normalize(decodedUrl), {
1856
+ basePath,
1857
+ cwd
1858
+ });
1859
+ const resolvedUrlWithMatchedExtension = pathExistsInAllFiles(addExtensionIfMissing(resolvedUrl, "md"), allFilePaths ?? []) ?? pathExistsInAllFiles(addExtension(resolvedUrl, "md"), allFilePaths ?? []) ?? void 0;
1860
+ if (resolvedUrlWithMatchedExtension !== void 0) {
1861
+ if (convertFilePathsToProtocol !== "none" && (type === "link" || type === "embed" && [".md", ".pdf"].includes(getExtension(resolvedUrlWithMatchedExtension)))) {
1862
+ if (convertFilePathsToProtocol === "obsidian" && obsidianVaultName !== void 0) return createObsidianVaultLink(resolvedUrlWithMatchedExtension, basePath ?? "", obsidianVaultName);
1863
+ if (convertFilePathsToProtocol === "file") return createFileLink(resolvedUrlWithMatchedExtension);
1828
1864
  }
1829
- case "obsidianVaultUrl": return filePathOrUrl;
1830
- case "remoteHttpUrl": return filePathOrUrl;
1831
- case "unsupportedProtocolUrl":
1832
- console.warn(`Unsupported URL protocol: ${filePathOrUrl}`);
1833
- return filePathOrUrl;
1865
+ return getBase(resolvedUrlWithMatchedExtension);
1834
1866
  }
1867
+ return getBase(resolvedUrl);
1835
1868
  }
1836
1869
  /**
1837
1870
  * Convert from a (usually wiki-style) named link to an absolute path to an
@@ -2047,7 +2080,11 @@ function wikiBasic() {
2047
2080
  return nok(code);
2048
2081
  }
2049
2082
  function startUrl(code) {
2050
- if (code === -5 || code === -4 || code === -3 || code === null) return nok(code);
2083
+ if (code === null || [
2084
+ -5,
2085
+ -4,
2086
+ -3
2087
+ ].includes(code)) return nok(code);
2051
2088
  if (code === 124) return nok(code);
2052
2089
  if (code === 93) return nok(code);
2053
2090
  effects.enter("wikiUrl");
@@ -2057,7 +2094,11 @@ function wikiBasic() {
2057
2094
  return insideUrl;
2058
2095
  }
2059
2096
  function insideUrl(code) {
2060
- if (code === -5 || code === -4 || code === -3 || code === null) return nok(code);
2097
+ if (code === null || [
2098
+ -5,
2099
+ -4,
2100
+ -3
2101
+ ].includes(code)) return nok(code);
2061
2102
  if (code === 124) {
2062
2103
  if (linkLength === 1) return nok(code);
2063
2104
  return transitionToLabel(code);
@@ -2075,7 +2116,11 @@ function wikiBasic() {
2075
2116
  return insideUrl;
2076
2117
  }
2077
2118
  function startLabel(code) {
2078
- if (code === -5 || code === -4 || code === -3 || code === null) return nok(code);
2119
+ if (code === null || [
2120
+ -5,
2121
+ -4,
2122
+ -3
2123
+ ].includes(code)) return nok(code);
2079
2124
  if (code === 93) return lookaheadClosingMarker(code);
2080
2125
  effects.enter("wikiLabel");
2081
2126
  effects.enter("chunkString", { contentType: "string" });
@@ -2084,7 +2129,11 @@ function wikiBasic() {
2084
2129
  return insideLabel;
2085
2130
  }
2086
2131
  function insideLabel(code) {
2087
- if (code === -5 || code === -4 || code === -3 || code === null) return nok(code);
2132
+ if (code === null || [
2133
+ -5,
2134
+ -4,
2135
+ -3
2136
+ ].includes(code)) return nok(code);
2088
2137
  if (code === 93) return lookaheadClosingMarker(code);
2089
2138
  effects.consume(code);
2090
2139
  labelLength++;
@@ -2137,33 +2186,33 @@ function wikiBasic() {
2137
2186
  return insideUrl;
2138
2187
  }
2139
2188
  /** If the next character is also `]`, run `ok`, else `nok`. */
2140
- function closingMarkerLookahead(effects, ok, nok) {
2189
+ function closingMarkerLookahead(checkEffects, checkOk, checkNok) {
2141
2190
  return start;
2142
2191
  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);
2192
+ if (code !== 93) return checkNok(code);
2193
+ checkEffects.enter("wikiMarkerTemp");
2194
+ checkEffects.consume(code);
2195
+ checkEffects.exit("wikiMarkerTemp");
2196
+ return checkOk(code);
2148
2197
  }
2149
2198
  }
2150
2199
  /** Check if backslash is followed by pipe. */
2151
- function backslashPipeLookahead(effects, ok, nok) {
2200
+ function backslashPipeLookahead(checkEffects, checkOk, checkNok) {
2152
2201
  return start;
2153
2202
  function start(code) {
2154
- if (code !== 92) return nok(code);
2155
- effects.enter("wikiMarkerTemp");
2156
- effects.consume(code);
2203
+ if (code !== 92) return checkNok(code);
2204
+ checkEffects.enter("wikiMarkerTemp");
2205
+ checkEffects.consume(code);
2157
2206
  return checkNext;
2158
2207
  }
2159
2208
  function checkNext(code) {
2160
2209
  if (code === 124) {
2161
- effects.consume(code);
2162
- effects.exit("wikiMarkerTemp");
2163
- return ok(code);
2210
+ checkEffects.consume(code);
2211
+ checkEffects.exit("wikiMarkerTemp");
2212
+ return checkOk(code);
2164
2213
  }
2165
- effects.exit("wikiMarkerTemp");
2166
- return nok(code);
2214
+ checkEffects.exit("wikiMarkerTemp");
2215
+ return checkNok(code);
2167
2216
  }
2168
2217
  }
2169
2218
  function transitionToLabel(code) {
@@ -2180,16 +2229,16 @@ function wikiBasic() {
2180
2229
  }, lookaheadClosingMarker, startLabel)(code);
2181
2230
  }
2182
2231
  /** Check if next character is a closing bracket. */
2183
- function closingBracketLookahead(effects, ok, nok) {
2232
+ function closingBracketLookahead(checkEffects, checkOk, checkNok) {
2184
2233
  return start;
2185
2234
  function start(code) {
2186
2235
  if (code === 93) {
2187
- effects.enter("wikiMarkerTemp");
2188
- effects.consume(code);
2189
- effects.exit("wikiMarkerTemp");
2190
- return ok(code);
2236
+ checkEffects.enter("wikiMarkerTemp");
2237
+ checkEffects.consume(code);
2238
+ checkEffects.exit("wikiMarkerTemp");
2239
+ return checkOk(code);
2191
2240
  }
2192
- return nok(code);
2241
+ return checkNok(code);
2193
2242
  }
2194
2243
  }
2195
2244
  function consumePipeAfterBackslash(code) {
@@ -2231,7 +2280,7 @@ const plugin = function() {
2231
2280
  };
2232
2281
  //#endregion
2233
2282
  //#region src/lib/parse/remark-utilities.ts
2234
- const CLOZE_NUMBER_PREFIX_REGEX = /^[(|]?(\d{1,2})(?:[\s).|]|$)(.*)$/;
2283
+ const CLOZE_NUMBER_PREFIX_REGEX = /* @__PURE__ */ new RegExp("^[\\(\\|]?(\\d{1,2})(?:[\\s\\).\\|]|$)(.*)$", "v");
2235
2284
  const defaultAstFromMarkdownOptions = { ...defaultGlobalOptions };
2236
2285
  async function getAstFromMarkdown(markdown, options) {
2237
2286
  const { allFilePaths, basePath, cwd, obsidianVault, resolveUrls } = deepmerge(defaultAstFromMarkdownOptions, options ?? {});
@@ -2253,10 +2302,9 @@ function isText(node) {
2253
2302
  }
2254
2303
  function deleteFirstNodeOfType(tree, nodeType) {
2255
2304
  visit(tree, nodeType, (_, index, parent) => {
2256
- if (parent && index !== void 0) {
2257
- parent.children.splice(index, 1);
2258
- return EXIT;
2259
- }
2305
+ if (!parent || index === void 0) return;
2306
+ parent.children.splice(index, 1);
2307
+ return EXIT;
2260
2308
  });
2261
2309
  return tree;
2262
2310
  }
@@ -2289,13 +2337,14 @@ function replaceDeleteNodesWithClozeMarkup(ast) {
2289
2337
  let clozeIndex = 1;
2290
2338
  visit(ast, "delete", (node, index, parent) => {
2291
2339
  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);
2340
+ const [firstChild] = node.children;
2341
+ if (firstChild !== void 0 && isText(firstChild)) {
2342
+ const result = CLOZE_NUMBER_PREFIX_REGEX.exec(firstChild.value);
2294
2343
  if (result !== null && (node.children.length > 1 || (result.at(2) ?? "").length > 0)) {
2295
- const possibleClozeIndex = Number.parseInt(result.at(1) ?? "", 10);
2344
+ const possibleClozeIndex = Number(result.at(1));
2296
2345
  if (!Number.isNaN(possibleClozeIndex)) {
2297
2346
  clozeIndex = possibleClozeIndex;
2298
- node.children[0].value = (result.at(2)?.trim().length ?? 0) > 0 ? result.at(2) ?? "" : "";
2347
+ firstChild.value = (result.at(2)?.trim().length ?? 0) > 0 ? result.at(2) ?? "" : "";
2299
2348
  }
2300
2349
  }
2301
2350
  }
@@ -2313,6 +2362,7 @@ function replaceDeleteNodesWithClozeMarkup(ast) {
2313
2362
  ];
2314
2363
  parent.children.splice(index, 1, ...clozeNodes);
2315
2364
  clozeIndex += 1;
2365
+ return CONTINUE;
2316
2366
  });
2317
2367
  return ast;
2318
2368
  }
@@ -2345,6 +2395,7 @@ function getYankiModelNameFromTree(ast) {
2345
2395
  probableType = `Yanki - Cloze`;
2346
2396
  return EXIT;
2347
2397
  }
2398
+ return CONTINUE;
2348
2399
  });
2349
2400
  if (probableType !== void 0) return probableType;
2350
2401
  if (!hasThematicBreak(ast) && isLastVisibleNodeEmphasisWithOthers(ast)) return `Yanki - Basic (type in the answer)`;
@@ -2359,6 +2410,7 @@ function getYankiModelNameFromTree(ast) {
2359
2410
  }
2360
2411
  }
2361
2412
  lastNode = node;
2413
+ return CONTINUE;
2362
2414
  });
2363
2415
  return probableType ?? `Yanki - Basic`;
2364
2416
  }
@@ -2369,18 +2421,18 @@ function getFrontmatterFromTree(ast) {
2369
2421
  rawYaml = node.value;
2370
2422
  return EXIT;
2371
2423
  });
2372
- if (!rawYaml) return {};
2373
- const parsedYaml = parse(rawYaml);
2374
- if (!parsedYaml) throw new Error("Could not parse frontmatter");
2424
+ if (rawYaml === void 0 || rawYaml === "") return {};
2425
+ const parsedYaml = parse(rawYaml) ?? void 0;
2426
+ if (parsedYaml === void 0) throw new Error("Could not parse frontmatter");
2375
2427
  return parsedYaml;
2376
2428
  }
2377
2429
  function hasThematicBreak(ast) {
2378
- let hasThematicBreak = false;
2430
+ let foundThematicBreak = false;
2379
2431
  visit(ast, "thematicBreak", () => {
2380
- hasThematicBreak = true;
2432
+ foundThematicBreak = true;
2381
2433
  return EXIT;
2382
2434
  });
2383
- return hasThematicBreak;
2435
+ return foundThematicBreak;
2384
2436
  }
2385
2437
  function isLastVisibleNodeEmphasisWithOthers(ast) {
2386
2438
  let lastVisibleNode;
@@ -2394,6 +2446,7 @@ function isLastVisibleNodeEmphasisWithOthers(ast) {
2394
2446
  visibleCount++;
2395
2447
  return SKIP;
2396
2448
  }
2449
+ return CONTINUE;
2397
2450
  });
2398
2451
  return lastVisibleNode?.type === "emphasis" && visibleCount > 1;
2399
2452
  }
@@ -2406,6 +2459,7 @@ function removeLastEmphasis(ast) {
2406
2459
  lastEmphasisNode = node;
2407
2460
  lastEmphasisParent = parent;
2408
2461
  lastEmphasisIndex = index;
2462
+ return CONTINUE;
2409
2463
  });
2410
2464
  if (lastEmphasisParent && lastEmphasisNode && typeof lastEmphasisIndex === "number") {
2411
2465
  lastEmphasisParent.children.splice(lastEmphasisIndex, 1);
@@ -2610,7 +2664,7 @@ async function loadLocalNotes(allLocalFilePaths, options) {
2610
2664
  strictLineBreaks,
2611
2665
  syncMediaAssets
2612
2666
  });
2613
- if (note.deckName === "") note.deckName = deckNamesFromFilePaths[index];
2667
+ if (note.deckName === "") note.deckName = deckNamesFromFilePaths[index] ?? "";
2614
2668
  return {
2615
2669
  filePath,
2616
2670
  filePathOriginal: filePath,
@@ -2737,9 +2791,10 @@ async function getStyle(options) {
2737
2791
  const css = await getModelStyle(client, modelName);
2738
2792
  cssSet.add(css);
2739
2793
  }
2740
- if (cssSet.size === 0) throw new Error("No CSS found in any Yanki model.");
2794
+ const [firstCss] = cssSet;
2795
+ if (firstCss === void 0) throw new Error("No CSS found in any Yanki model.");
2741
2796
  if (cssSet.size > 1) throw new Error("Expected all Yanki models to have identical CSS.");
2742
- return [...cssSet][0];
2797
+ return firstCss;
2743
2798
  }
2744
2799
  async function setStyle(options) {
2745
2800
  const startTime = performance.now();
@@ -2782,7 +2837,7 @@ function formatSetStyleResult(result, verbose = false) {
2782
2837
  }
2783
2838
  //#endregion
2784
2839
  //#region src/lib/model/frontmatter.ts
2785
- const NEWLINE_REGEX = /\r?\n/;
2840
+ const NEWLINE_REGEX = /* @__PURE__ */ new RegExp("\\r?\\n", "v");
2786
2841
  /**
2787
2842
  * Update the noteId in the frontmatter of a markdown string.
2788
2843
  *
@@ -2815,7 +2870,7 @@ async function setNoteIdInFrontmatter(markdown, noteId) {
2815
2870
  delete parsedFrontmatter.noteId;
2816
2871
  if (Object.keys(parsedFrontmatter).length === 0) {
2817
2872
  const markdownWithoutFrontmatter = lines.slice(frontmatterEnd + 1);
2818
- if (markdownWithoutFrontmatter[0].trim() === "") return markdownWithoutFrontmatter.slice(1).join("\n");
2873
+ if (markdownWithoutFrontmatter[0]?.trim() === "") return markdownWithoutFrontmatter.slice(1).join("\n");
2819
2874
  return markdownWithoutFrontmatter.join("\n");
2820
2875
  }
2821
2876
  } else parsedFrontmatter.noteId = noteId;
@@ -2828,7 +2883,7 @@ async function setNoteIdInFrontmatter(markdown, noteId) {
2828
2883
  }
2829
2884
  function getFrontmatterRange(markdown) {
2830
2885
  const lines = markdown.split(NEWLINE_REGEX);
2831
- if (!lines.join("").trim().startsWith("---")) return [void 0, void 0];
2886
+ if (!lines.join("").trimStart().startsWith("---")) return [void 0, void 0];
2832
2887
  const frontmatterStart = lines.findIndex((line) => line.startsWith("---"));
2833
2888
  const frontmatterEnd = lines.findIndex((line, index) => index > frontmatterStart && line.startsWith("---"));
2834
2889
  if (frontmatterStart === -1 || frontmatterEnd === -1) return [void 0, void 0];
@@ -2910,7 +2965,7 @@ async function syncNotes(allLocalNotes, options) {
2910
2965
  if (localNote.noteId === void 0) throw new Error("Note ID is undefined");
2911
2966
  matchedIds.add(localNote.noteId);
2912
2967
  }
2913
- const orphanedNotes = remoteNotes.filter((remoteNote) => !allLocalNotesCopy.some((localNote) => localNote.noteId === remoteNote.noteId));
2968
+ const orphanedNotes = remoteNotes.filter((remoteNote) => allLocalNotesCopy.every((localNote) => localNote.noteId !== remoteNote.noteId));
2914
2969
  await deleteNotes(client, orphanedNotes, dryRun);
2915
2970
  for (const orphanedNote of orphanedNotes) synced.push({
2916
2971
  action: "deleted",
@@ -3001,7 +3056,7 @@ async function syncFiles(allLocalFilePaths, options) {
3001
3056
  manageFilenames,
3002
3057
  maxFilenameLength
3003
3058
  });
3004
- if (obsidianVault !== void 0) {
3059
+ if (obsidianVault !== void 0 && !dryRun) {
3005
3060
  if (renamedLocalNotes.some((renamedNote) => renamedNote.filePath !== renamedNote.filePathOriginal)) {
3006
3061
  const reloadedLocalNotes = await loadLocalNotes(renamedLocalNotes.map((note) => note.filePath), {
3007
3062
  allFilePaths: allFilePaths.map((filePath) => {
@@ -3043,7 +3098,7 @@ async function syncFiles(allLocalFilePaths, options) {
3043
3098
  filePath: void 0,
3044
3099
  filePathOriginal: void 0,
3045
3100
  note: note.note
3046
- })), ...liveNotes].sort((a, b) => (a.filePath ?? "").localeCompare(b.filePath ?? ""));
3101
+ })), ...liveNotes].toSorted((a, b) => (a.filePath ?? "").localeCompare(b.filePath ?? ""));
3047
3102
  return {
3048
3103
  ankiWeb,
3049
3104
  deletedDecks,
@@ -3060,12 +3115,12 @@ function formatSyncFilesResult(result, verbose = false) {
3060
3115
  const lines = [];
3061
3116
  const { synced } = result;
3062
3117
  const actionCounts = synced.reduce((acc, note) => {
3063
- acc[note.action] = (acc[note.action] || 0) + 1;
3118
+ acc[note.action] = (acc[note.action] ?? 0) + 1;
3064
3119
  return acc;
3065
3120
  }, {});
3066
3121
  const totalSynced = synced.filter((note) => note.action !== "deleted").length;
3067
3122
  const totalRenamed = synced.filter((note) => note.filePath !== note.filePathOriginal).length;
3068
- const ankiUnreachable = actionCounts.ankiUnreachable > 0;
3123
+ const ankiUnreachable = (actionCounts.ankiUnreachable ?? 0) > 0;
3069
3124
  lines.push(`${result.dryRun ? "Will sync" : ankiUnreachable ? "Failed to sync" : "Successfully synced"} ${totalSynced} ${plur("note", totalSynced)} to Anki${result.dryRun ? "" : ` in ${prettyMilliseconds(result.duration)}`}.`);
3070
3125
  if (verbose) {
3071
3126
  lines.push("", result.dryRun ? "Sync Plan Summary:" : "Sync Summary:");