yanki 2.0.9 → 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/bin/cli.js +136 -137
- package/dist/lib/index.js +364 -330
- package/dist/standalone/index.d.ts +46 -17
- package/dist/standalone/index.js +152 -152
- package/package.json +23 -23
- package/readme.md +6 -2
- /package/dist/bin/{open-CC1xuL9n.js → open-PkGTdlsi.js} +0 -0
- /package/dist/standalone/{open-DsJJlGt1.js → open-DnUwqK4E.js} +0 -0
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(
|
|
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 =
|
|
110
|
-
const LEADING_WHITESPACE_REGEX =
|
|
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" ?
|
|
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
|
|
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 =
|
|
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
|
-
[
|
|
327
|
-
[
|
|
328
|
-
[
|
|
329
|
-
[
|
|
330
|
-
[
|
|
331
|
-
[
|
|
332
|
-
[
|
|
333
|
-
[
|
|
334
|
-
[
|
|
335
|
-
[
|
|
336
|
-
[
|
|
337
|
-
[
|
|
338
|
-
[
|
|
339
|
-
[
|
|
340
|
-
[
|
|
341
|
-
[
|
|
342
|
-
[
|
|
343
|
-
[
|
|
344
|
-
[
|
|
345
|
-
[
|
|
346
|
-
[
|
|
347
|
-
[
|
|
348
|
-
[
|
|
349
|
-
[
|
|
350
|
-
[
|
|
351
|
-
[
|
|
352
|
-
[
|
|
353
|
-
[
|
|
354
|
-
[
|
|
355
|
-
[
|
|
356
|
-
[
|
|
357
|
-
[
|
|
358
|
-
[
|
|
359
|
-
[
|
|
360
|
-
[
|
|
361
|
-
[
|
|
362
|
-
[
|
|
363
|
-
[
|
|
364
|
-
[
|
|
365
|
-
[
|
|
366
|
-
[
|
|
367
|
-
[
|
|
368
|
-
[
|
|
369
|
-
[
|
|
370
|
-
[
|
|
371
|
-
[
|
|
372
|
-
[
|
|
373
|
-
[
|
|
374
|
-
[
|
|
375
|
-
[
|
|
376
|
-
[
|
|
377
|
-
[
|
|
378
|
-
[
|
|
379
|
-
[
|
|
380
|
-
[
|
|
381
|
-
[
|
|
382
|
-
[
|
|
383
|
-
[
|
|
384
|
-
[
|
|
385
|
-
[
|
|
386
|
-
[
|
|
387
|
-
[
|
|
388
|
-
[
|
|
389
|
-
[
|
|
390
|
-
[
|
|
391
|
-
[
|
|
392
|
-
[
|
|
393
|
-
[
|
|
394
|
-
[
|
|
395
|
-
[
|
|
396
|
-
[
|
|
397
|
-
[
|
|
398
|
-
[
|
|
399
|
-
[
|
|
400
|
-
[
|
|
401
|
-
[
|
|
402
|
-
[
|
|
403
|
-
[
|
|
404
|
-
[
|
|
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 =
|
|
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 =
|
|
601
|
-
const FILE_PREFIX_REGEX =
|
|
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
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
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
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
978
|
-
return `<!-- This HTML was generated by Yanki, a Markdown to Anki converter. Do not edit directly. -->\n${
|
|
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(
|
|
984
|
-
return hastToPlainText(htmlProcessor.parse(
|
|
968
|
+
function htmlToPlainText(htmlText) {
|
|
969
|
+
return hastToPlainText(htmlProcessor.parse(htmlText));
|
|
985
970
|
}
|
|
986
|
-
function getAllLinesOfHtmlAsPlainText(
|
|
987
|
-
return htmlToPlainText(
|
|
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(
|
|
990
|
-
return htmlToPlainText(
|
|
974
|
+
function getFirstLineOfHtmlAsPlainText(htmlText) {
|
|
975
|
+
return htmlToPlainText(htmlText).split("\n").map((line) => line.trim()).find((line) => line.length > 0) ?? "";
|
|
991
976
|
}
|
|
992
|
-
function extractMediaFromHtml(
|
|
993
|
-
const hast = htmlProcessor.parse(
|
|
977
|
+
function extractMediaFromHtml(htmlText) {
|
|
978
|
+
const hast = htmlProcessor.parse(htmlText);
|
|
994
979
|
const media = [];
|
|
995
980
|
visit(hast, "element", (node) => {
|
|
996
|
-
if (
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
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
|
|
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
|
|
1095
|
-
|
|
1096
|
-
|
|
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
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
}
|
|
1218
|
-
if (newNote ===
|
|
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
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
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
|
-
])
|
|
1279
|
-
|
|
1280
|
-
|
|
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
|
|
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
|
|
1373
|
-
|
|
1374
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
}
|
|
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)
|
|
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
|
-
|
|
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 =
|
|
1661
|
-
const FILENAME_INCREMENT_SUFFIX_REGEX =
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
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")
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
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
|
-
|
|
1802
|
-
const resolvedUrl =
|
|
1803
|
-
|
|
1804
|
-
|
|
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
|
-
|
|
1813
|
+
console.warn(`Failed to convert file URL to path: ${currentPathOrUrl} --> ${resolvedUrl}`);
|
|
1814
|
+
return resolvedUrl;
|
|
1815
1815
|
}
|
|
1816
|
-
|
|
1817
|
-
|
|
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
|
-
|
|
1827
|
-
return
|
|
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 ===
|
|
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 ===
|
|
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 ===
|
|
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 ===
|
|
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(
|
|
2174
|
+
function closingMarkerLookahead(checkEffects, checkOk, checkNok) {
|
|
2141
2175
|
return start;
|
|
2142
2176
|
function start(code) {
|
|
2143
|
-
if (code !== 93) return
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
return
|
|
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(
|
|
2185
|
+
function backslashPipeLookahead(checkEffects, checkOk, checkNok) {
|
|
2152
2186
|
return start;
|
|
2153
2187
|
function start(code) {
|
|
2154
|
-
if (code !== 92) return
|
|
2155
|
-
|
|
2156
|
-
|
|
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
|
-
|
|
2162
|
-
|
|
2163
|
-
return
|
|
2195
|
+
checkEffects.consume(code);
|
|
2196
|
+
checkEffects.exit("wikiMarkerTemp");
|
|
2197
|
+
return checkOk(code);
|
|
2164
2198
|
}
|
|
2165
|
-
|
|
2166
|
-
return
|
|
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(
|
|
2217
|
+
function closingBracketLookahead(checkEffects, checkOk, checkNok) {
|
|
2184
2218
|
return start;
|
|
2185
2219
|
function start(code) {
|
|
2186
2220
|
if (code === 93) {
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
return
|
|
2221
|
+
checkEffects.enter("wikiMarkerTemp");
|
|
2222
|
+
checkEffects.consume(code);
|
|
2223
|
+
checkEffects.exit("wikiMarkerTemp");
|
|
2224
|
+
return checkOk(code);
|
|
2191
2225
|
}
|
|
2192
|
-
return
|
|
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 =
|
|
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
|
|
2257
|
-
|
|
2258
|
-
|
|
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
|
-
|
|
2293
|
-
|
|
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
|
|
2329
|
+
const possibleClozeIndex = Number(result.at(1));
|
|
2296
2330
|
if (!Number.isNaN(possibleClozeIndex)) {
|
|
2297
2331
|
clozeIndex = possibleClozeIndex;
|
|
2298
|
-
|
|
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 (
|
|
2373
|
-
const parsedYaml = parse(rawYaml);
|
|
2374
|
-
if (
|
|
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
|
|
2412
|
+
let foundThematicBreak = false;
|
|
2379
2413
|
visit(ast, "thematicBreak", () => {
|
|
2380
|
-
|
|
2414
|
+
foundThematicBreak = true;
|
|
2381
2415
|
return EXIT;
|
|
2382
2416
|
});
|
|
2383
|
-
return
|
|
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 =
|
|
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) =>
|
|
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].
|
|
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]
|
|
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;
|