@mikedown/core 0.2.0 → 0.4.0
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/CHANGELOG.md +19 -0
- package/README.md +121 -2
- package/dist/ai/engines-page.d.ts +137 -0
- package/dist/ai/engines-page.js +11 -0
- package/dist/ai/engines-page.js.map +1 -0
- package/dist/ai/index.d.ts +2722 -0
- package/dist/ai/index.js +684 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai-ui-QFYFN73D.js +6397 -0
- package/dist/ai-ui-QFYFN73D.js.map +1 -0
- package/dist/chunk-APPBNQEY.js +216 -0
- package/dist/chunk-APPBNQEY.js.map +1 -0
- package/dist/chunk-DWXGWVA3.js +5748 -0
- package/dist/chunk-DWXGWVA3.js.map +1 -0
- package/dist/chunk-GWCRPPCX.js +18 -0
- package/dist/chunk-GWCRPPCX.js.map +1 -0
- package/dist/chunk-IWCXWUWW.js +6958 -0
- package/dist/chunk-IWCXWUWW.js.map +1 -0
- package/dist/chunk-L4FO63DF.js +113 -0
- package/dist/chunk-L4FO63DF.js.map +1 -0
- package/dist/chunk-N4N54LK2.js +39 -0
- package/dist/chunk-N4N54LK2.js.map +1 -0
- package/dist/chunk-QWKT57TR.js +62 -0
- package/dist/chunk-QWKT57TR.js.map +1 -0
- package/dist/chunk-TSP7BAV3.js +22 -0
- package/dist/chunk-TSP7BAV3.js.map +1 -0
- package/dist/index.d.ts +19 -463
- package/dist/index.js +7717 -2103
- package/dist/index.js.map +1 -1
- package/dist/schema-DVfssxQX.d.ts +100 -0
- package/dist/shared/anchoring.js +4 -16
- package/dist/shared/anchoring.js.map +1 -1
- package/dist/shared/imagePaste.js +3 -57
- package/dist/shared/imagePaste.js.map +1 -1
- package/dist/shared/sha1.js +3 -57
- package/dist/shared/sha1.js.map +1 -1
- package/dist/shared/wordCount.js +4 -12
- package/dist/shared/wordCount.js.map +1 -1
- package/dist/styles.css +4562 -108
- package/dist/types-2TZErbJr.d.ts +619 -0
- package/dist/types-DO8UBybZ.d.ts +741 -0
- package/package.json +11 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { f as AiFeatureSettings, a6 as AiGhostTextOptions, i as AiEngineKind, A as AiSettings, _ as AiDocBase, m as AiDocsOptions, a2 as AiDocsSettings } from './types-DO8UBybZ.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `mikedown.ai.*` settings: parse (validate, fill defaults, keep unknown
|
|
5
|
+
* keys) and serialize back to a settings patch.
|
|
6
|
+
*
|
|
7
|
+
* `parseAiSettings` never throws. Malformed entries (an engine that is not
|
|
8
|
+
* an object or has no base URL, a model with no id, a route without both
|
|
9
|
+
* ids) are dropped with a warning; everything it does not recognize is kept
|
|
10
|
+
* as is, on every object, so a newer version's settings survive a save.
|
|
11
|
+
* Engines without an id get one, once: the id is written back by
|
|
12
|
+
* `serializeAiSettings`, so it is stable from then on.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
interface ParseAiSettingsOptions {
|
|
16
|
+
/** Id for an engine that has none. Default: a slug of its name plus random characters. */
|
|
17
|
+
generateId?: (name: string) => string;
|
|
18
|
+
/** Called once per dropped or repaired entry. Default `console.warn`. */
|
|
19
|
+
onWarning?: (message: string) => void;
|
|
20
|
+
}
|
|
21
|
+
/** The top-level keys, without the `ai.` prefix. */
|
|
22
|
+
declare const AI_SETTINGS_KEYS: readonly ["enabled", "allowCloud", "engines", "routing", "features", "localNetworks", "showToolbarButton", "activityPanel", "docBases", "docs"];
|
|
23
|
+
|
|
24
|
+
declare function isKnownEngineKind(kind: string): kind is AiEngineKind;
|
|
25
|
+
/** A kind only one host runs, with no address or key (`vscode-lm`); see `AI_HOST_ENGINE_KINDS`. */
|
|
26
|
+
declare function isHostEngineKind(kind: string): boolean;
|
|
27
|
+
/** A new engine id: `omlx-mac-studio-k3f9a2`. Never shown, only has to be unique and stable. */
|
|
28
|
+
declare function generateAiId(name?: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Read `raw` as AI settings. Accepts the nested object VS Code returns for
|
|
31
|
+
* `getConfiguration('mikedown.ai')` (`{ enabled, engines, ... }`) or a flat
|
|
32
|
+
* store with `ai.`-prefixed keys (Desktop, a settings patch). Anything else,
|
|
33
|
+
* including undefined, gives the defaults.
|
|
34
|
+
*/
|
|
35
|
+
declare function parseAiSettings(raw: unknown, opts?: ParseAiSettingsOptions): AiSettings;
|
|
36
|
+
/** Defaults and ranges for `features.ghostText` (decision D12). */
|
|
37
|
+
declare const GHOST_DELAY_MS_DEFAULT = 400;
|
|
38
|
+
declare const GHOST_DELAY_MS_MIN = 100;
|
|
39
|
+
declare const GHOST_DELAY_MS_MAX = 5000;
|
|
40
|
+
declare const GHOST_MAX_TOKENS_DEFAULT = 48;
|
|
41
|
+
declare const GHOST_MAX_TOKENS_MIN = 8;
|
|
42
|
+
declare const GHOST_MAX_TOKENS_MAX = 256;
|
|
43
|
+
/**
|
|
44
|
+
* The Inline suggestions knobs: defaults (400 ms, 48 tokens, `auto`, local
|
|
45
|
+
* engines only) for anything absent or of the wrong type, numbers rounded and
|
|
46
|
+
* clamped (delay 100 to 5000 ms, tokens 8 to 256).
|
|
47
|
+
*/
|
|
48
|
+
declare function ghostTextOptions(features: AiFeatureSettings | undefined): AiGhostTextOptions;
|
|
49
|
+
/** The settings patch keys, `ai.`-prefixed: what the editor sends to `host.saveSettings`. */
|
|
50
|
+
type AiSettingsPatch = Record<`ai.${string}`, unknown>;
|
|
51
|
+
/**
|
|
52
|
+
* Settings to a `saveSettings` patch: one `ai.<key>` per top-level key,
|
|
53
|
+
* including unknown ones, as plain JSON (deep copies). VS Code writes each as
|
|
54
|
+
* `mikedown.ai.<key>`; Desktop stores the flat keys. Secrets are never here.
|
|
55
|
+
*/
|
|
56
|
+
declare function serializeAiSettings(settings: AiSettings): AiSettingsPatch;
|
|
57
|
+
/** What `checkDocRoot` says about one folder path. */
|
|
58
|
+
type DocRootCheck = {
|
|
59
|
+
ok: true;
|
|
60
|
+
path: string;
|
|
61
|
+
windows: boolean;
|
|
62
|
+
} | {
|
|
63
|
+
ok: false;
|
|
64
|
+
reason: string;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Is `raw` an acceptable base root (decision 2)? Absolute, normalized
|
|
68
|
+
* (separators collapsed, `.` and `..` resolved, no trailing separator), no
|
|
69
|
+
* `${...}` token, not a filesystem root, and not a markdown file where the
|
|
70
|
+
* name says so. Pure string work: a host still checks the folder on disk.
|
|
71
|
+
* POSIX (`/a/b`), drive (`C:\a\b`, `C:/a/b`) and UNC (`\\server\share\a`)
|
|
72
|
+
* paths are accepted; the result keeps the style it came in (a drive letter
|
|
73
|
+
* is upper-cased, a Windows path uses `\`).
|
|
74
|
+
*/
|
|
75
|
+
declare function checkDocRoot(raw: unknown): DocRootCheck;
|
|
76
|
+
/** The key two roots are compared by: case-insensitive for Windows paths only. */
|
|
77
|
+
declare function docRootKey(path: string): string;
|
|
78
|
+
/**
|
|
79
|
+
* A base's home override: a markdown file name, or a relative path under the
|
|
80
|
+
* root, with `/` separators. No absolute path, no `..`, no `${...}`. Null
|
|
81
|
+
* when `raw` is not one.
|
|
82
|
+
*/
|
|
83
|
+
declare function normalizeDocHomeFile(raw: unknown): string | null;
|
|
84
|
+
/**
|
|
85
|
+
* Read `ai.docBases`. Drops, each with a warning that says why: an entry that
|
|
86
|
+
* is not an object, one with no valid root, a root that fails `checkDocRoot`
|
|
87
|
+
* or repeats another root (in this base or any earlier one; case-insensitive
|
|
88
|
+
* for Windows paths). A bad `homeFile` is dropped and the base kept. A base
|
|
89
|
+
* with no id gets one, once (written back by `serializeAiSettings`).
|
|
90
|
+
* `allowCloud` is false unless exactly `true`; `enabled` is true unless
|
|
91
|
+
* exactly `false`. Unknown keys survive.
|
|
92
|
+
*/
|
|
93
|
+
declare function parseDocBases(raw: unknown, genId: (name: string) => string, warn: (m: string) => void): AiDocBase[];
|
|
94
|
+
/** Read `ai.docs`: hops clamped to 1 to 5, share to 30 to 80, `navigateBy` to `section` or `page`. Unknown keys survive. */
|
|
95
|
+
declare function parseDocsOptions(raw: unknown, warn: (m: string) => void): AiDocsOptions;
|
|
96
|
+
/** What the navigation loop runs with: the advanced options plus the bases. */
|
|
97
|
+
declare function resolveDocsSettings(settings: Pick<AiSettings, 'docBases' | 'docs'>): AiDocsSettings;
|
|
98
|
+
|
|
99
|
+
export { GHOST_DELAY_MS_DEFAULT as G, AI_SETTINGS_KEYS as a, GHOST_DELAY_MS_MAX as b, GHOST_DELAY_MS_MIN as c, GHOST_MAX_TOKENS_DEFAULT as d, GHOST_MAX_TOKENS_MAX as e, GHOST_MAX_TOKENS_MIN as f, checkDocRoot as g, docRootKey as h, generateAiId as i, ghostTextOptions as j, isHostEngineKind as k, isKnownEngineKind as l, parseDocBases as m, normalizeDocHomeFile as n, parseDocsOptions as o, parseAiSettings as p, resolveDocsSettings as r, serializeAiSettings as s };
|
|
100
|
+
export type { AiSettingsPatch as A, DocRootCheck as D, ParseAiSettingsOptions as P };
|
package/dist/shared/anchoring.js
CHANGED
|
@@ -1,19 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
function dedupedAnchorIdAt(headingTexts, index) {
|
|
6
|
-
const seen = /* @__PURE__ */ new Map();
|
|
7
|
-
for (let i = 0; i < headingTexts.length; i++) {
|
|
8
|
-
const base = githubAnchorId(headingTexts[i]);
|
|
9
|
-
const count = seen.get(base) ?? 0;
|
|
10
|
-
seen.set(base, count + 1);
|
|
11
|
-
if (i === index) {
|
|
12
|
-
return count === 0 ? base : `${base}-${count}`;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
return "";
|
|
16
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
dedupedAnchorIdAt,
|
|
3
|
+
githubAnchorId
|
|
4
|
+
} from "../chunk-TSP7BAV3.js";
|
|
17
5
|
export {
|
|
18
6
|
dedupedAnchorIdAt,
|
|
19
7
|
githubAnchorId
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -1,60 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
function sha1Hex(data) {
|
|
6
|
-
const len = data.length;
|
|
7
|
-
const padded = new Uint8Array((len + 8 >> 6) + 1 << 6);
|
|
8
|
-
padded.set(data);
|
|
9
|
-
padded[len] = 128;
|
|
10
|
-
const view = new DataView(padded.buffer);
|
|
11
|
-
view.setUint32(padded.length - 8, Math.floor(len / 536870912));
|
|
12
|
-
view.setUint32(padded.length - 4, len * 8 >>> 0);
|
|
13
|
-
let h0 = 1732584193;
|
|
14
|
-
let h1 = 4023233417;
|
|
15
|
-
let h2 = 2562383102;
|
|
16
|
-
let h3 = 271733878;
|
|
17
|
-
let h4 = 3285377520;
|
|
18
|
-
const w = new Uint32Array(80);
|
|
19
|
-
for (let off = 0; off < padded.length; off += 64) {
|
|
20
|
-
for (let i = 0; i < 16; i++) {
|
|
21
|
-
w[i] = view.getUint32(off + i * 4);
|
|
22
|
-
}
|
|
23
|
-
for (let i = 16; i < 80; i++) {
|
|
24
|
-
w[i] = rotl(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
|
|
25
|
-
}
|
|
26
|
-
let a = h0, b = h1, c = h2, d = h3, e = h4;
|
|
27
|
-
for (let i = 0; i < 80; i++) {
|
|
28
|
-
let f;
|
|
29
|
-
let k;
|
|
30
|
-
if (i < 20) {
|
|
31
|
-
f = b & c | ~b & d;
|
|
32
|
-
k = 1518500249;
|
|
33
|
-
} else if (i < 40) {
|
|
34
|
-
f = b ^ c ^ d;
|
|
35
|
-
k = 1859775393;
|
|
36
|
-
} else if (i < 60) {
|
|
37
|
-
f = b & c | b & d | c & d;
|
|
38
|
-
k = 2400959708;
|
|
39
|
-
} else {
|
|
40
|
-
f = b ^ c ^ d;
|
|
41
|
-
k = 3395469782;
|
|
42
|
-
}
|
|
43
|
-
const t = rotl(a, 5) + f + e + k + w[i] >>> 0;
|
|
44
|
-
e = d;
|
|
45
|
-
d = c;
|
|
46
|
-
c = rotl(b, 30) >>> 0;
|
|
47
|
-
b = a;
|
|
48
|
-
a = t;
|
|
49
|
-
}
|
|
50
|
-
h0 = h0 + a >>> 0;
|
|
51
|
-
h1 = h1 + b >>> 0;
|
|
52
|
-
h2 = h2 + c >>> 0;
|
|
53
|
-
h3 = h3 + d >>> 0;
|
|
54
|
-
h4 = h4 + e >>> 0;
|
|
55
|
-
}
|
|
56
|
-
return [h0, h1, h2, h3, h4].map((h) => h.toString(16).padStart(8, "0")).join("");
|
|
57
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
sha1Hex
|
|
3
|
+
} from "../chunk-QWKT57TR.js";
|
|
58
4
|
|
|
59
5
|
// src/shared/imagePaste.ts
|
|
60
6
|
var MIME_TO_EXT = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/shared/sha1.ts","../../src/shared/imagePaste.ts"],"sourcesContent":["// Pure, synchronous SHA-1 over bytes. No node:crypto, so it runs in the\n// webview, a Tauri webview, and Node alike. Used only for the image-paste\n// `${hash}` token and hash-dedupe (content fingerprinting, not security);\n// hosts with a native hash (Node `crypto`) can inject theirs instead.\n\nfunction rotl(x: number, n: number): number {\n return (x << n) | (x >>> (32 - n));\n}\n\nexport function sha1Hex(data: Uint8Array): string {\n const len = data.length;\n const padded = new Uint8Array((((len + 8) >> 6) + 1) << 6);\n padded.set(data);\n padded[len] = 0x80;\n const view = new DataView(padded.buffer);\n // 64-bit big-endian bit length.\n view.setUint32(padded.length - 8, Math.floor(len / 0x20000000));\n view.setUint32(padded.length - 4, (len * 8) >>> 0);\n\n let h0 = 0x67452301;\n let h1 = 0xefcdab89;\n let h2 = 0x98badcfe;\n let h3 = 0x10325476;\n let h4 = 0xc3d2e1f0;\n const w = new Uint32Array(80);\n\n for (let off = 0; off < padded.length; off += 64) {\n for (let i = 0; i < 16; i++) {w[i] = view.getUint32(off + i * 4);}\n for (let i = 16; i < 80; i++) {w[i] = rotl(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);}\n let a = h0, b = h1, c = h2, d = h3, e = h4;\n for (let i = 0; i < 80; i++) {\n let f: number;\n let k: number;\n if (i < 20) { f = (b & c) | (~b & d); k = 0x5a827999; }\n else if (i < 40) { f = b ^ c ^ d; k = 0x6ed9eba1; }\n else if (i < 60) { f = (b & c) | (b & d) | (c & d); k = 0x8f1bbcdc; }\n else { f = b ^ c ^ d; k = 0xca62c1d6; }\n const t = (rotl(a, 5) + f + e + k + w[i]) >>> 0;\n e = d; d = c; c = rotl(b, 30) >>> 0; b = a; a = t;\n }\n h0 = (h0 + a) >>> 0;\n h1 = (h1 + b) >>> 0;\n h2 = (h2 + c) >>> 0;\n h3 = (h3 + d) >>> 0;\n h4 = (h4 + e) >>> 0;\n }\n return [h0, h1, h2, h3, h4].map(h => h.toString(16).padStart(8, '0')).join('');\n}\n","// Image-paste helpers shared by the VS Code extension host and the desktop\n// app. Platform-neutral: no Node `path`/`crypto` imports. Path-dependent\n// helpers come from `createImagePasteHelpers(env)`, where the host injects its\n// path implementation (Node `path` in the extension) and optionally a SHA-1\n// function (defaults to the pure `sha1Hex` in `./sha1`).\nimport { sha1Hex as pureSha1Hex } from './sha1';\n\nexport interface ImagePasteSettings {\n enabled: boolean;\n folder: string;\n folderRelativeTo: 'document' | 'workspace';\n filenamePattern: string;\n pathStyle: 'relative' | 'workspace-absolute';\n altText: 'empty' | 'filename' | 'prompt';\n maxSizeMB: number;\n cleanupUnreferenced: boolean;\n}\n\n/** The subset of Node's `path` module these helpers use. */\nexport interface PathOps {\n sep: string;\n join(...parts: string[]): string;\n resolve(...parts: string[]): string;\n dirname(p: string): string;\n basename(p: string, ext?: string): string;\n extname(p: string): string;\n relative(from: string, to: string): string;\n isAbsolute(p: string): boolean;\n normalize(p: string): string;\n}\n\nexport interface ImagePasteEnv {\n path: PathOps;\n /** Full lowercase hex SHA-1 of the bytes. Defaults to the pure implementation. */\n sha1Hex?: (data: Uint8Array) => string;\n}\n\nconst MIME_TO_EXT: Record<string, string> = {\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/jpg': 'jpg',\n 'image/gif': 'gif',\n 'image/webp': 'webp',\n 'image/svg+xml': 'svg',\n 'image/bmp': 'bmp',\n 'image/x-icon': 'ico',\n 'image/tiff': 'tiff',\n 'image/avif': 'avif',\n};\n\nexport function extensionFromMime(mime: string): string | null {\n return MIME_TO_EXT[mime.toLowerCase()] ?? null;\n}\n\nexport interface FilenameContext {\n /** Markdown document base name without extension (e.g. \"my-note\"). */\n docName: string;\n /** Output extension without dot (e.g. \"png\"). */\n extension: string;\n /** Image bytes — used for the `${hash}` token. */\n data: Uint8Array;\n /** Timestamp used for `${date}`/`${time}`/`${timestamp}` tokens. */\n timestamp: Date;\n /** Auto-increment value for the `${index}` token. Defaults to 1. */\n index?: number;\n}\n\n/**\n * Strip characters that don't belong in a filename (Windows-strict set + control chars).\n * Collapses runs of whitespace/separators to a single dash.\n */\nexport function sanitizeForFilename(input: string): string {\n return input\n // eslint-disable-next-line no-control-regex -- intentional: strips ASCII control chars (\\x00-\\x1f) from filenames\n .replace(/[<>:\"/\\\\|?*\\x00-\\x1f]/g, '')\n .replace(/\\s+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^[.-]+|[.-]+$/g, '');\n}\n\nexport interface FilenameResolution {\n /** Final absolute path to the image on disk. */\n absPath: string;\n /** True when an existing identical file was reused (hash-dedupe hit). */\n reused: boolean;\n}\n\n/**\n * Pull all `` matches out of a markdown string and return their\n * `src` values. Skips external (http/https) and data URIs since those don't\n * map to local files we'd ever want to delete.\n */\nexport function extractLocalImageRefs(markdown: string): string[] {\n const out: string[] = [];\n const re = /!\\[([^\\]]*)\\]\\(([^)]+)\\)/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(markdown)) !== null) {\n const src = m[2].trim();\n if (!src) {continue;}\n if (/^(?:[a-z][a-z0-9+.-]*:|data:|\\/\\/)/i.test(src) && !src.startsWith('/')) {continue;}\n // Strip cache-bust query/fragment that the resize popover may have left in.\n const queryStart = src.search(/[?#]/);\n out.push(queryStart >= 0 ? src.slice(0, queryStart) : src);\n }\n return out;\n}\n\n/**\n * Heuristic: does `filename` look like a MikeDown-generated paste filename\n * (rather than a user-curated asset like `logo.png`)?\n *\n * Builds a regex from the configured `filenamePattern` by replacing tokens\n * with shape-anchors (`${timestamp}` → `\\d{8}-\\d{6}`, `${hash}` → 8 hex chars,\n * etc.). Allows an optional `-N` collision suffix and any image extension.\n *\n * Returns false on any pattern parse failure so we never accidentally widen\n * the cleanup set when the user has set a weird custom pattern.\n */\nexport function looksLikeAutoPastedImage(\n filename: string,\n pattern: string,\n docName: string\n): boolean {\n if (!pattern) {return false;}\n const sanitizedDoc = sanitizeForFilename(docName) || 'image';\n const escapeRegex = (s: string): string => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n let regex = '^';\n let i = 0;\n while (i < pattern.length) {\n const tail = pattern.slice(i);\n const tokenMatch = /^\\$\\{(\\w+)\\}/.exec(tail);\n if (tokenMatch) {\n const key = tokenMatch[1];\n switch (key) {\n case 'docName': regex += escapeRegex(sanitizedDoc); break;\n case 'date': regex += '\\\\d{8}'; break;\n case 'time': regex += '\\\\d{6}'; break;\n case 'timestamp': regex += '\\\\d{8}-\\\\d{6}'; break;\n case 'hash': regex += '[0-9a-f]{8}'; break;\n case 'index': regex += '\\\\d+'; break;\n // Unknown token — bail rather than match too broadly.\n default: return false;\n }\n i += tokenMatch[0].length;\n } else {\n regex += escapeRegex(pattern[i]);\n i += 1;\n }\n }\n regex += '(?:-\\\\d+)?\\\\.[a-zA-Z0-9]+$';\n try {\n return new RegExp(regex).test(filename);\n } catch {\n return false;\n }\n}\n\n/**\n * Decide what alt text to use, given the configured strategy and (optionally)\n * a user-provided value from the prompt UI.\n */\nexport function resolveAltText(\n strategy: 'empty' | 'filename' | 'prompt',\n filenameNoExt: string,\n promptedValue: string | undefined\n): string {\n if (strategy === 'filename') {return filenameNoExt;}\n if (strategy === 'prompt') {return promptedValue ?? '';}\n return '';\n}\n\n/**\n * Build the path- and hash-dependent helpers for one host. The extension\n * passes Node's `path` and a `crypto` SHA-1; the desktop app passes its own\n * synchronous path implementation (the pure SHA-1 default works there).\n */\nexport function createImagePasteHelpers(env: ImagePasteEnv) {\n const path = env.path;\n const hashImpl = env.sha1Hex ?? pureSha1Hex;\n\n /**\n * Replace pattern tokens with concrete values. Unknown tokens pass through\n * untouched so user-typed text stays intact.\n *\n * Supported tokens:\n * ${docName} sanitized document base name\n * ${date} YYYYMMDD\n * ${time} HHmmss\n * ${timestamp} YYYYMMDD-HHmmss\n * ${hash} 8-char SHA-1 of the image bytes\n * ${index} auto-increment value (default 1)\n */\n function expandFilenamePattern(pattern: string, ctx: FilenameContext): string {\n const pad = (n: number, w = 2): string => String(n).padStart(w, '0');\n const t = ctx.timestamp;\n const date = `${t.getFullYear()}${pad(t.getMonth() + 1)}${pad(t.getDate())}`;\n const time = `${pad(t.getHours())}${pad(t.getMinutes())}${pad(t.getSeconds())}`;\n const timestamp = `${date}-${time}`;\n\n // Lazy: only compute the hash if the pattern actually uses it.\n let hash = '';\n if (pattern.includes('${hash}')) {\n hash = sha1Hex(ctx.data).slice(0, 8);\n }\n\n const tokens: Record<string, string> = {\n docName: sanitizeForFilename(ctx.docName) || 'image',\n date,\n time,\n timestamp,\n hash,\n index: String(ctx.index ?? 1),\n };\n\n return pattern.replace(/\\$\\{(\\w+)\\}/g, (match: string, key: string) => {\n return Object.prototype.hasOwnProperty.call(tokens, key) ? tokens[key] : match;\n });\n }\n\n function sha1Hex(data: Uint8Array): string {\n return hashImpl(data);\n }\n\n /**\n * Resolve the absolute folder where pasted images should be written.\n * `workspaceRoot` may be undefined when the document is outside any workspace —\n * in that case `folderRelativeTo === 'workspace'` falls back to the document's\n * own directory.\n */\n function resolveTargetFolder(\n config: ImagePasteSettings,\n documentPath: string,\n workspaceRoot: string | undefined\n ): string {\n const folder = expandFolderPattern(config.folder, documentPath);\n if (path.isAbsolute(folder)) {\n return folder;\n }\n const base = config.folderRelativeTo === 'workspace' && workspaceRoot\n ? workspaceRoot\n : path.dirname(documentPath);\n return path.resolve(base, folder);\n }\n\n /**\n * Expand `${docName}` in the configured folder so each document can get its\n * own image folder (e.g. `assets/${docName}`). Only `${docName}` is supported:\n * time-based tokens would scatter one document's images across folders and\n * break orphan-cleanup scoping. Other tokens pass through untouched.\n */\n function expandFolderPattern(folder: string, documentPath: string): string {\n const docName = path.basename(documentPath, path.extname(documentPath));\n const safe = sanitizeForFilename(docName) || 'image';\n return folder.replace(/\\$\\{docName\\}/g, safe);\n }\n\n /**\n * Compute the final on-disk path, handling collisions and hash-dedupe.\n *\n * Behavior:\n * - If the resolved name (with extension) doesn't exist → use it verbatim.\n * - If it exists AND `${hash}` is in the pattern AND content hashes match\n * (per `readExistingHash`) → reuse the existing file (`reused: true`).\n * - Otherwise → append `-1`, `-2`, … until a free slot is found.\n *\n * `readExistingHash` is injected so this function stays pure / testable; pass\n * a real fs-backed implementation from the production caller.\n */\n function resolveFilename(\n pattern: string,\n targetFolder: string,\n ctx: FilenameContext,\n fileExists: (absPath: string) => boolean,\n readExistingHash?: (absPath: string) => string\n ): FilenameResolution {\n const ext = ctx.extension;\n const baseName = expandFilenamePattern(pattern, ctx);\n const initialPath = path.join(targetFolder, `${baseName}.${ext}`);\n\n if (!fileExists(initialPath)) {\n return { absPath: initialPath, reused: false };\n }\n\n // Hash-dedupe: only triggered when the pattern explicitly opts in via ${hash}.\n // Without that opt-in we always create a new file (collision suffix path).\n if (pattern.includes('${hash}') && readExistingHash) {\n const incomingHash = sha1Hex(ctx.data);\n const existingHash = readExistingHash(initialPath);\n if (existingHash === incomingHash) {\n return { absPath: initialPath, reused: true };\n }\n }\n\n let i = 1;\n while (true) {\n const candidate = path.join(targetFolder, `${baseName}-${i}.${ext}`);\n if (!fileExists(candidate)) {\n return { absPath: candidate, reused: false };\n }\n i += 1;\n }\n }\n\n /**\n * Produce the path string to write into the markdown image link.\n *\n * `relative` — POSIX-relative from the document's directory\n * (e.g. `images/foo.png`). Anchors with `./` only when\n * the path would otherwise look like a sibling file\n * starting with a letter — leaving plain `images/foo.png`\n * is the typical convention and what users expect.\n * `workspace-absolute` — leading `/`, POSIX-relative from the workspace root.\n * Falls back to the document-relative form when the\n * document is outside any workspace.\n */\n function formatInsertPath(\n absImagePath: string,\n documentPath: string,\n workspaceRoot: string | undefined,\n pathStyle: 'relative' | 'workspace-absolute'\n ): string {\n const docDir = path.dirname(documentPath);\n if (pathStyle === 'workspace-absolute' && workspaceRoot) {\n return '/' + toPosix(path.relative(workspaceRoot, absImagePath));\n }\n return toPosix(path.relative(docDir, absImagePath));\n }\n\n function toPosix(p: string): string {\n return p.split(path.sep).join('/');\n }\n\n /**\n * Compute a sibling filename for a resized variant (e.g. `foo.png` + 50 →\n * `foo-50pct.png`). If that sibling already exists, append `-1`, `-2`, …\n * until a free slot is found.\n */\n function resizeSiblingPath(\n absPath: string,\n percent: number,\n fileExists: (absPath: string) => boolean\n ): string {\n const dir = path.dirname(absPath);\n const ext = path.extname(absPath);\n const base = path.basename(absPath, ext);\n const stem = `${base}-${Math.round(percent)}pct`;\n const initial = path.join(dir, `${stem}${ext}`);\n if (!fileExists(initial)) {return initial;}\n let i = 1;\n while (true) {\n const candidate = path.join(dir, `${stem}-${i}${ext}`);\n if (!fileExists(candidate)) {return candidate;}\n i += 1;\n }\n }\n\n /**\n * Resolve a list of image-link srcs (as parsed from markdown) to absolute fs\n * paths. `documentDir` is the directory of the markdown file the srcs came\n * from. `workspaceRoot` is required to resolve workspace-absolute paths\n * (those starting with `/`).\n */\n function resolveLocalImagePaths(\n srcs: string[],\n documentDir: string,\n workspaceRoot: string | undefined\n ): string[] {\n const out: string[] = [];\n for (const src of srcs) {\n if (path.isAbsolute(src)) {\n // A leading-slash path is workspace-absolute when we have a workspace,\n // otherwise it's a real absolute path on the host filesystem.\n if (src.startsWith('/') && workspaceRoot) {\n out.push(path.join(workspaceRoot, src));\n } else {\n out.push(src);\n }\n continue;\n }\n out.push(path.resolve(documentDir, src));\n }\n return out;\n }\n\n /**\n * True iff `absImagePath` lives inside the resolved imagePaste folder for\n * this document. Used by orphan-cleanup to scope deletion to MikeDown-managed\n * locations (we never touch images the user keeps elsewhere).\n */\n function isInsideManagedFolder(\n absImagePath: string,\n config: ImagePasteSettings,\n documentPath: string,\n workspaceRoot: string | undefined\n ): boolean {\n const folder = resolveTargetFolder(config, documentPath, workspaceRoot);\n // Use trailing-separator match so `/foo/imagesX/y.png` doesn't count as\n // being inside `/foo/images`.\n const folderWithSep = folder.endsWith(path.sep) ? folder : folder + path.sep;\n return absImagePath === folder || absImagePath.startsWith(folderWithSep);\n }\n\n /**\n * True iff `childPath` is `parentDir` itself or lives somewhere underneath it.\n * Uses `path.relative` so this is correct across separators; a relative\n * result that starts with `..` (or that is itself absolute, which\n * `path.relative` returns for cross-drive paths on Windows) means `childPath`\n * is outside `parentDir`.\n */\n function isPathInside(parentDir: string, childPath: string): boolean {\n const rel = path.relative(parentDir, childPath);\n return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));\n }\n\n /**\n * True iff `absPath` is outside every directory in `roots`. Used by the image\n * file picker (M4) to decide whether a picked file needs to be copied into\n * the image-paste target folder to be servable — VS Code's webview\n * `localResourceRoots` only covers the extension's own directories, the\n * document's folder, and workspace folders, so a file picked from elsewhere\n * (e.g. the user's Desktop) won't render unless it's copied in. Files already\n * inside one of `roots` are referenced in place.\n */\n function isOutsideResourceRoots(absPath: string, roots: string[]): boolean {\n return !roots.some(root => isPathInside(root, absPath));\n }\n\n return {\n expandFilenamePattern,\n resolveTargetFolder,\n expandFolderPattern,\n resolveFilename,\n formatInsertPath,\n resizeSiblingPath,\n resolveLocalImagePaths,\n isInsideManagedFolder,\n isPathInside,\n isOutsideResourceRoots,\n extensionFromMime,\n sanitizeForFilename,\n extractLocalImageRefs,\n looksLikeAutoPastedImage,\n resolveAltText,\n };\n}\n\nexport type ImagePasteHelpers = ReturnType<typeof createImagePasteHelpers>;\n"],"mappings":"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","names":["sha1Hex"]}
|
|
1
|
+
{"version":3,"sources":["../../src/shared/imagePaste.ts"],"sourcesContent":["// Image-paste helpers shared by the VS Code extension host and the desktop\n// app. Platform-neutral: no Node `path`/`crypto` imports. Path-dependent\n// helpers come from `createImagePasteHelpers(env)`, where the host injects its\n// path implementation (Node `path` in the extension) and optionally a SHA-1\n// function (defaults to the pure `sha1Hex` in `./sha1`).\nimport { sha1Hex as pureSha1Hex } from './sha1';\n\nexport interface ImagePasteSettings {\n enabled: boolean;\n folder: string;\n folderRelativeTo: 'document' | 'workspace';\n filenamePattern: string;\n pathStyle: 'relative' | 'workspace-absolute';\n altText: 'empty' | 'filename' | 'prompt';\n maxSizeMB: number;\n cleanupUnreferenced: boolean;\n}\n\n/** The subset of Node's `path` module these helpers use. */\nexport interface PathOps {\n sep: string;\n join(...parts: string[]): string;\n resolve(...parts: string[]): string;\n dirname(p: string): string;\n basename(p: string, ext?: string): string;\n extname(p: string): string;\n relative(from: string, to: string): string;\n isAbsolute(p: string): boolean;\n normalize(p: string): string;\n}\n\nexport interface ImagePasteEnv {\n path: PathOps;\n /** Full lowercase hex SHA-1 of the bytes. Defaults to the pure implementation. */\n sha1Hex?: (data: Uint8Array) => string;\n}\n\nconst MIME_TO_EXT: Record<string, string> = {\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/jpg': 'jpg',\n 'image/gif': 'gif',\n 'image/webp': 'webp',\n 'image/svg+xml': 'svg',\n 'image/bmp': 'bmp',\n 'image/x-icon': 'ico',\n 'image/tiff': 'tiff',\n 'image/avif': 'avif',\n};\n\nexport function extensionFromMime(mime: string): string | null {\n return MIME_TO_EXT[mime.toLowerCase()] ?? null;\n}\n\nexport interface FilenameContext {\n /** Markdown document base name without extension (e.g. \"my-note\"). */\n docName: string;\n /** Output extension without dot (e.g. \"png\"). */\n extension: string;\n /** Image bytes — used for the `${hash}` token. */\n data: Uint8Array;\n /** Timestamp used for `${date}`/`${time}`/`${timestamp}` tokens. */\n timestamp: Date;\n /** Auto-increment value for the `${index}` token. Defaults to 1. */\n index?: number;\n}\n\n/**\n * Strip characters that don't belong in a filename (Windows-strict set + control chars).\n * Collapses runs of whitespace/separators to a single dash.\n */\nexport function sanitizeForFilename(input: string): string {\n return input\n // eslint-disable-next-line no-control-regex -- intentional: strips ASCII control chars (\\x00-\\x1f) from filenames\n .replace(/[<>:\"/\\\\|?*\\x00-\\x1f]/g, '')\n .replace(/\\s+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^[.-]+|[.-]+$/g, '');\n}\n\nexport interface FilenameResolution {\n /** Final absolute path to the image on disk. */\n absPath: string;\n /** True when an existing identical file was reused (hash-dedupe hit). */\n reused: boolean;\n}\n\n/**\n * Pull all `` matches out of a markdown string and return their\n * `src` values. Skips external (http/https) and data URIs since those don't\n * map to local files we'd ever want to delete.\n */\nexport function extractLocalImageRefs(markdown: string): string[] {\n const out: string[] = [];\n const re = /!\\[([^\\]]*)\\]\\(([^)]+)\\)/g;\n let m: RegExpExecArray | null;\n while ((m = re.exec(markdown)) !== null) {\n const src = m[2].trim();\n if (!src) {continue;}\n if (/^(?:[a-z][a-z0-9+.-]*:|data:|\\/\\/)/i.test(src) && !src.startsWith('/')) {continue;}\n // Strip cache-bust query/fragment that the resize popover may have left in.\n const queryStart = src.search(/[?#]/);\n out.push(queryStart >= 0 ? src.slice(0, queryStart) : src);\n }\n return out;\n}\n\n/**\n * Heuristic: does `filename` look like a MikeDown-generated paste filename\n * (rather than a user-curated asset like `logo.png`)?\n *\n * Builds a regex from the configured `filenamePattern` by replacing tokens\n * with shape-anchors (`${timestamp}` → `\\d{8}-\\d{6}`, `${hash}` → 8 hex chars,\n * etc.). Allows an optional `-N` collision suffix and any image extension.\n *\n * Returns false on any pattern parse failure so we never accidentally widen\n * the cleanup set when the user has set a weird custom pattern.\n */\nexport function looksLikeAutoPastedImage(\n filename: string,\n pattern: string,\n docName: string\n): boolean {\n if (!pattern) {return false;}\n const sanitizedDoc = sanitizeForFilename(docName) || 'image';\n const escapeRegex = (s: string): string => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n let regex = '^';\n let i = 0;\n while (i < pattern.length) {\n const tail = pattern.slice(i);\n const tokenMatch = /^\\$\\{(\\w+)\\}/.exec(tail);\n if (tokenMatch) {\n const key = tokenMatch[1];\n switch (key) {\n case 'docName': regex += escapeRegex(sanitizedDoc); break;\n case 'date': regex += '\\\\d{8}'; break;\n case 'time': regex += '\\\\d{6}'; break;\n case 'timestamp': regex += '\\\\d{8}-\\\\d{6}'; break;\n case 'hash': regex += '[0-9a-f]{8}'; break;\n case 'index': regex += '\\\\d+'; break;\n // Unknown token — bail rather than match too broadly.\n default: return false;\n }\n i += tokenMatch[0].length;\n } else {\n regex += escapeRegex(pattern[i]);\n i += 1;\n }\n }\n regex += '(?:-\\\\d+)?\\\\.[a-zA-Z0-9]+$';\n try {\n return new RegExp(regex).test(filename);\n } catch {\n return false;\n }\n}\n\n/**\n * Decide what alt text to use, given the configured strategy and (optionally)\n * a user-provided value from the prompt UI.\n */\nexport function resolveAltText(\n strategy: 'empty' | 'filename' | 'prompt',\n filenameNoExt: string,\n promptedValue: string | undefined\n): string {\n if (strategy === 'filename') {return filenameNoExt;}\n if (strategy === 'prompt') {return promptedValue ?? '';}\n return '';\n}\n\n/**\n * Build the path- and hash-dependent helpers for one host. The extension\n * passes Node's `path` and a `crypto` SHA-1; the desktop app passes its own\n * synchronous path implementation (the pure SHA-1 default works there).\n */\nexport function createImagePasteHelpers(env: ImagePasteEnv) {\n const path = env.path;\n const hashImpl = env.sha1Hex ?? pureSha1Hex;\n\n /**\n * Replace pattern tokens with concrete values. Unknown tokens pass through\n * untouched so user-typed text stays intact.\n *\n * Supported tokens:\n * ${docName} sanitized document base name\n * ${date} YYYYMMDD\n * ${time} HHmmss\n * ${timestamp} YYYYMMDD-HHmmss\n * ${hash} 8-char SHA-1 of the image bytes\n * ${index} auto-increment value (default 1)\n */\n function expandFilenamePattern(pattern: string, ctx: FilenameContext): string {\n const pad = (n: number, w = 2): string => String(n).padStart(w, '0');\n const t = ctx.timestamp;\n const date = `${t.getFullYear()}${pad(t.getMonth() + 1)}${pad(t.getDate())}`;\n const time = `${pad(t.getHours())}${pad(t.getMinutes())}${pad(t.getSeconds())}`;\n const timestamp = `${date}-${time}`;\n\n // Lazy: only compute the hash if the pattern actually uses it.\n let hash = '';\n if (pattern.includes('${hash}')) {\n hash = sha1Hex(ctx.data).slice(0, 8);\n }\n\n const tokens: Record<string, string> = {\n docName: sanitizeForFilename(ctx.docName) || 'image',\n date,\n time,\n timestamp,\n hash,\n index: String(ctx.index ?? 1),\n };\n\n return pattern.replace(/\\$\\{(\\w+)\\}/g, (match: string, key: string) => {\n return Object.prototype.hasOwnProperty.call(tokens, key) ? tokens[key] : match;\n });\n }\n\n function sha1Hex(data: Uint8Array): string {\n return hashImpl(data);\n }\n\n /**\n * Resolve the absolute folder where pasted images should be written.\n * `workspaceRoot` may be undefined when the document is outside any workspace —\n * in that case `folderRelativeTo === 'workspace'` falls back to the document's\n * own directory.\n */\n function resolveTargetFolder(\n config: ImagePasteSettings,\n documentPath: string,\n workspaceRoot: string | undefined\n ): string {\n const folder = expandFolderPattern(config.folder, documentPath);\n if (path.isAbsolute(folder)) {\n return folder;\n }\n const base = config.folderRelativeTo === 'workspace' && workspaceRoot\n ? workspaceRoot\n : path.dirname(documentPath);\n return path.resolve(base, folder);\n }\n\n /**\n * Expand `${docName}` in the configured folder so each document can get its\n * own image folder (e.g. `assets/${docName}`). Only `${docName}` is supported:\n * time-based tokens would scatter one document's images across folders and\n * break orphan-cleanup scoping. Other tokens pass through untouched.\n */\n function expandFolderPattern(folder: string, documentPath: string): string {\n const docName = path.basename(documentPath, path.extname(documentPath));\n const safe = sanitizeForFilename(docName) || 'image';\n return folder.replace(/\\$\\{docName\\}/g, safe);\n }\n\n /**\n * Compute the final on-disk path, handling collisions and hash-dedupe.\n *\n * Behavior:\n * - If the resolved name (with extension) doesn't exist → use it verbatim.\n * - If it exists AND `${hash}` is in the pattern AND content hashes match\n * (per `readExistingHash`) → reuse the existing file (`reused: true`).\n * - Otherwise → append `-1`, `-2`, … until a free slot is found.\n *\n * `readExistingHash` is injected so this function stays pure / testable; pass\n * a real fs-backed implementation from the production caller.\n */\n function resolveFilename(\n pattern: string,\n targetFolder: string,\n ctx: FilenameContext,\n fileExists: (absPath: string) => boolean,\n readExistingHash?: (absPath: string) => string\n ): FilenameResolution {\n const ext = ctx.extension;\n const baseName = expandFilenamePattern(pattern, ctx);\n const initialPath = path.join(targetFolder, `${baseName}.${ext}`);\n\n if (!fileExists(initialPath)) {\n return { absPath: initialPath, reused: false };\n }\n\n // Hash-dedupe: only triggered when the pattern explicitly opts in via ${hash}.\n // Without that opt-in we always create a new file (collision suffix path).\n if (pattern.includes('${hash}') && readExistingHash) {\n const incomingHash = sha1Hex(ctx.data);\n const existingHash = readExistingHash(initialPath);\n if (existingHash === incomingHash) {\n return { absPath: initialPath, reused: true };\n }\n }\n\n let i = 1;\n while (true) {\n const candidate = path.join(targetFolder, `${baseName}-${i}.${ext}`);\n if (!fileExists(candidate)) {\n return { absPath: candidate, reused: false };\n }\n i += 1;\n }\n }\n\n /**\n * Produce the path string to write into the markdown image link.\n *\n * `relative` — POSIX-relative from the document's directory\n * (e.g. `images/foo.png`). Anchors with `./` only when\n * the path would otherwise look like a sibling file\n * starting with a letter — leaving plain `images/foo.png`\n * is the typical convention and what users expect.\n * `workspace-absolute` — leading `/`, POSIX-relative from the workspace root.\n * Falls back to the document-relative form when the\n * document is outside any workspace.\n */\n function formatInsertPath(\n absImagePath: string,\n documentPath: string,\n workspaceRoot: string | undefined,\n pathStyle: 'relative' | 'workspace-absolute'\n ): string {\n const docDir = path.dirname(documentPath);\n if (pathStyle === 'workspace-absolute' && workspaceRoot) {\n return '/' + toPosix(path.relative(workspaceRoot, absImagePath));\n }\n return toPosix(path.relative(docDir, absImagePath));\n }\n\n function toPosix(p: string): string {\n return p.split(path.sep).join('/');\n }\n\n /**\n * Compute a sibling filename for a resized variant (e.g. `foo.png` + 50 →\n * `foo-50pct.png`). If that sibling already exists, append `-1`, `-2`, …\n * until a free slot is found.\n */\n function resizeSiblingPath(\n absPath: string,\n percent: number,\n fileExists: (absPath: string) => boolean\n ): string {\n const dir = path.dirname(absPath);\n const ext = path.extname(absPath);\n const base = path.basename(absPath, ext);\n const stem = `${base}-${Math.round(percent)}pct`;\n const initial = path.join(dir, `${stem}${ext}`);\n if (!fileExists(initial)) {return initial;}\n let i = 1;\n while (true) {\n const candidate = path.join(dir, `${stem}-${i}${ext}`);\n if (!fileExists(candidate)) {return candidate;}\n i += 1;\n }\n }\n\n /**\n * Resolve a list of image-link srcs (as parsed from markdown) to absolute fs\n * paths. `documentDir` is the directory of the markdown file the srcs came\n * from. `workspaceRoot` is required to resolve workspace-absolute paths\n * (those starting with `/`).\n */\n function resolveLocalImagePaths(\n srcs: string[],\n documentDir: string,\n workspaceRoot: string | undefined\n ): string[] {\n const out: string[] = [];\n for (const src of srcs) {\n if (path.isAbsolute(src)) {\n // A leading-slash path is workspace-absolute when we have a workspace,\n // otherwise it's a real absolute path on the host filesystem.\n if (src.startsWith('/') && workspaceRoot) {\n out.push(path.join(workspaceRoot, src));\n } else {\n out.push(src);\n }\n continue;\n }\n out.push(path.resolve(documentDir, src));\n }\n return out;\n }\n\n /**\n * True iff `absImagePath` lives inside the resolved imagePaste folder for\n * this document. Used by orphan-cleanup to scope deletion to MikeDown-managed\n * locations (we never touch images the user keeps elsewhere).\n */\n function isInsideManagedFolder(\n absImagePath: string,\n config: ImagePasteSettings,\n documentPath: string,\n workspaceRoot: string | undefined\n ): boolean {\n const folder = resolveTargetFolder(config, documentPath, workspaceRoot);\n // Use trailing-separator match so `/foo/imagesX/y.png` doesn't count as\n // being inside `/foo/images`.\n const folderWithSep = folder.endsWith(path.sep) ? folder : folder + path.sep;\n return absImagePath === folder || absImagePath.startsWith(folderWithSep);\n }\n\n /**\n * True iff `childPath` is `parentDir` itself or lives somewhere underneath it.\n * Uses `path.relative` so this is correct across separators; a relative\n * result that starts with `..` (or that is itself absolute, which\n * `path.relative` returns for cross-drive paths on Windows) means `childPath`\n * is outside `parentDir`.\n */\n function isPathInside(parentDir: string, childPath: string): boolean {\n const rel = path.relative(parentDir, childPath);\n return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));\n }\n\n /**\n * True iff `absPath` is outside every directory in `roots`. Used by the image\n * file picker (M4) to decide whether a picked file needs to be copied into\n * the image-paste target folder to be servable — VS Code's webview\n * `localResourceRoots` only covers the extension's own directories, the\n * document's folder, and workspace folders, so a file picked from elsewhere\n * (e.g. the user's Desktop) won't render unless it's copied in. Files already\n * inside one of `roots` are referenced in place.\n */\n function isOutsideResourceRoots(absPath: string, roots: string[]): boolean {\n return !roots.some(root => isPathInside(root, absPath));\n }\n\n return {\n expandFilenamePattern,\n resolveTargetFolder,\n expandFolderPattern,\n resolveFilename,\n formatInsertPath,\n resizeSiblingPath,\n resolveLocalImagePaths,\n isInsideManagedFolder,\n isPathInside,\n isOutsideResourceRoots,\n extensionFromMime,\n sanitizeForFilename,\n extractLocalImageRefs,\n looksLikeAutoPastedImage,\n resolveAltText,\n };\n}\n\nexport type ImagePasteHelpers = ReturnType<typeof createImagePasteHelpers>;\n"],"mappings":"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","names":["sha1Hex"]}
|
package/dist/shared/sha1.js
CHANGED
|
@@ -1,60 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
function sha1Hex(data) {
|
|
6
|
-
const len = data.length;
|
|
7
|
-
const padded = new Uint8Array((len + 8 >> 6) + 1 << 6);
|
|
8
|
-
padded.set(data);
|
|
9
|
-
padded[len] = 128;
|
|
10
|
-
const view = new DataView(padded.buffer);
|
|
11
|
-
view.setUint32(padded.length - 8, Math.floor(len / 536870912));
|
|
12
|
-
view.setUint32(padded.length - 4, len * 8 >>> 0);
|
|
13
|
-
let h0 = 1732584193;
|
|
14
|
-
let h1 = 4023233417;
|
|
15
|
-
let h2 = 2562383102;
|
|
16
|
-
let h3 = 271733878;
|
|
17
|
-
let h4 = 3285377520;
|
|
18
|
-
const w = new Uint32Array(80);
|
|
19
|
-
for (let off = 0; off < padded.length; off += 64) {
|
|
20
|
-
for (let i = 0; i < 16; i++) {
|
|
21
|
-
w[i] = view.getUint32(off + i * 4);
|
|
22
|
-
}
|
|
23
|
-
for (let i = 16; i < 80; i++) {
|
|
24
|
-
w[i] = rotl(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
|
|
25
|
-
}
|
|
26
|
-
let a = h0, b = h1, c = h2, d = h3, e = h4;
|
|
27
|
-
for (let i = 0; i < 80; i++) {
|
|
28
|
-
let f;
|
|
29
|
-
let k;
|
|
30
|
-
if (i < 20) {
|
|
31
|
-
f = b & c | ~b & d;
|
|
32
|
-
k = 1518500249;
|
|
33
|
-
} else if (i < 40) {
|
|
34
|
-
f = b ^ c ^ d;
|
|
35
|
-
k = 1859775393;
|
|
36
|
-
} else if (i < 60) {
|
|
37
|
-
f = b & c | b & d | c & d;
|
|
38
|
-
k = 2400959708;
|
|
39
|
-
} else {
|
|
40
|
-
f = b ^ c ^ d;
|
|
41
|
-
k = 3395469782;
|
|
42
|
-
}
|
|
43
|
-
const t = rotl(a, 5) + f + e + k + w[i] >>> 0;
|
|
44
|
-
e = d;
|
|
45
|
-
d = c;
|
|
46
|
-
c = rotl(b, 30) >>> 0;
|
|
47
|
-
b = a;
|
|
48
|
-
a = t;
|
|
49
|
-
}
|
|
50
|
-
h0 = h0 + a >>> 0;
|
|
51
|
-
h1 = h1 + b >>> 0;
|
|
52
|
-
h2 = h2 + c >>> 0;
|
|
53
|
-
h3 = h3 + d >>> 0;
|
|
54
|
-
h4 = h4 + e >>> 0;
|
|
55
|
-
}
|
|
56
|
-
return [h0, h1, h2, h3, h4].map((h) => h.toString(16).padStart(8, "0")).join("");
|
|
57
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
sha1Hex
|
|
3
|
+
} from "../chunk-QWKT57TR.js";
|
|
58
4
|
export {
|
|
59
5
|
sha1Hex
|
|
60
6
|
};
|
package/dist/shared/sha1.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/shared/wordCount.js
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
return 0;
|
|
6
|
-
}
|
|
7
|
-
return stripped.split(/\s+/).filter((w) => w.length > 0).length;
|
|
8
|
-
}
|
|
9
|
-
var WPM = 225;
|
|
10
|
-
function readingMinutes(wordCount) {
|
|
11
|
-
return Math.max(1, Math.ceil(wordCount / WPM));
|
|
12
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
countWords,
|
|
3
|
+
readingMinutes
|
|
4
|
+
} from "../chunk-GWCRPPCX.js";
|
|
13
5
|
export {
|
|
14
6
|
countWords,
|
|
15
7
|
readingMinutes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|