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/bin/cli.js +144 -145
- package/dist/lib/index.d.ts +8 -6
- package/dist/lib/index.js +402 -347
- package/dist/standalone/index.d.ts +103 -75
- package/dist/standalone/index.js +155 -155
- package/package.json +25 -25
- 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
|
-
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" ?
|
|
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,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
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
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
|
-
|
|
730
|
+
if (parsedUrl === void 0) return;
|
|
731
|
+
return {
|
|
740
732
|
host: `${parsedUrl.protocol}//${parsedUrl.hostname}`,
|
|
741
|
-
port: Number
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
978
|
-
return `<!-- This HTML was generated by Yanki, a Markdown to Anki converter. Do not edit directly. -->\n${
|
|
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(
|
|
984
|
-
return hastToPlainText(htmlProcessor.parse(
|
|
979
|
+
function htmlToPlainText(htmlText) {
|
|
980
|
+
return hastToPlainText(htmlProcessor.parse(htmlText));
|
|
985
981
|
}
|
|
986
|
-
function getAllLinesOfHtmlAsPlainText(
|
|
987
|
-
return htmlToPlainText(
|
|
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(
|
|
990
|
-
return htmlToPlainText(
|
|
985
|
+
function getFirstLineOfHtmlAsPlainText(htmlText) {
|
|
986
|
+
return htmlToPlainText(htmlText).split("\n").map((line) => line.trim()).find((line) => line.length > 0) ?? "";
|
|
991
987
|
}
|
|
992
|
-
function extractMediaFromHtml(
|
|
993
|
-
const hast = htmlProcessor.parse(
|
|
988
|
+
function extractMediaFromHtml(htmlText) {
|
|
989
|
+
const hast = htmlProcessor.parse(htmlText);
|
|
994
990
|
const media = [];
|
|
995
991
|
visit(hast, "element", (node) => {
|
|
996
|
-
if (
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
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
|
|
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
|
|
1095
|
-
|
|
1096
|
-
|
|
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
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
}
|
|
1218
|
-
if (newNote ===
|
|
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
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
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
|
-
])
|
|
1279
|
-
|
|
1280
|
-
|
|
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
|
|
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
|
|
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}`);
|
|
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
|
|
1389
|
-
...ankiNote.fields.Extra !== void 0 && { Extra: ankiNote.fields.Extra
|
|
1390
|
-
Front: ankiNote.fields.Front
|
|
1391
|
-
YankiNamespace: ankiNote.fields.YankiNamespace
|
|
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
|
-
|
|
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 =
|
|
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
|
-
}
|
|
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)
|
|
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
|
-
|
|
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 =
|
|
1661
|
-
const FILENAME_INCREMENT_SUFFIX_REGEX =
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
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")
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
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
|
-
|
|
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);
|
|
1822
|
+
if (sourceType === "localFileUrl") {
|
|
1823
|
+
const resolvedUrl = normalize(fileUrlToPath(currentPathOrUrl));
|
|
1824
|
+
if (getSrcType(resolvedUrl) === "localFilePath") {
|
|
1825
|
+
currentPathOrUrl = resolvedUrl;
|
|
1826
|
+
continue;
|
|
1813
1827
|
}
|
|
1814
|
-
|
|
1828
|
+
console.warn(`Failed to convert file URL to path: ${currentPathOrUrl} --> ${resolvedUrl}`);
|
|
1829
|
+
return resolvedUrl;
|
|
1815
1830
|
}
|
|
1816
|
-
|
|
1817
|
-
|
|
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
|
-
|
|
1827
|
-
return
|
|
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
|
-
|
|
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 ===
|
|
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 ===
|
|
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 ===
|
|
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 ===
|
|
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(
|
|
2189
|
+
function closingMarkerLookahead(checkEffects, checkOk, checkNok) {
|
|
2141
2190
|
return start;
|
|
2142
2191
|
function start(code) {
|
|
2143
|
-
if (code !== 93) return
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
return
|
|
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(
|
|
2200
|
+
function backslashPipeLookahead(checkEffects, checkOk, checkNok) {
|
|
2152
2201
|
return start;
|
|
2153
2202
|
function start(code) {
|
|
2154
|
-
if (code !== 92) return
|
|
2155
|
-
|
|
2156
|
-
|
|
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
|
-
|
|
2162
|
-
|
|
2163
|
-
return
|
|
2210
|
+
checkEffects.consume(code);
|
|
2211
|
+
checkEffects.exit("wikiMarkerTemp");
|
|
2212
|
+
return checkOk(code);
|
|
2164
2213
|
}
|
|
2165
|
-
|
|
2166
|
-
return
|
|
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(
|
|
2232
|
+
function closingBracketLookahead(checkEffects, checkOk, checkNok) {
|
|
2184
2233
|
return start;
|
|
2185
2234
|
function start(code) {
|
|
2186
2235
|
if (code === 93) {
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
return
|
|
2236
|
+
checkEffects.enter("wikiMarkerTemp");
|
|
2237
|
+
checkEffects.consume(code);
|
|
2238
|
+
checkEffects.exit("wikiMarkerTemp");
|
|
2239
|
+
return checkOk(code);
|
|
2191
2240
|
}
|
|
2192
|
-
return
|
|
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 =
|
|
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
|
|
2257
|
-
|
|
2258
|
-
|
|
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
|
-
|
|
2293
|
-
|
|
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
|
|
2344
|
+
const possibleClozeIndex = Number(result.at(1));
|
|
2296
2345
|
if (!Number.isNaN(possibleClozeIndex)) {
|
|
2297
2346
|
clozeIndex = possibleClozeIndex;
|
|
2298
|
-
|
|
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 (
|
|
2373
|
-
const parsedYaml = parse(rawYaml);
|
|
2374
|
-
if (
|
|
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
|
|
2430
|
+
let foundThematicBreak = false;
|
|
2379
2431
|
visit(ast, "thematicBreak", () => {
|
|
2380
|
-
|
|
2432
|
+
foundThematicBreak = true;
|
|
2381
2433
|
return EXIT;
|
|
2382
2434
|
});
|
|
2383
|
-
return
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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]
|
|
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("").
|
|
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) =>
|
|
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].
|
|
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]
|
|
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:");
|