@mikedown/core 0.2.0 → 0.3.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 +8 -0
- package/README.md +121 -2
- package/dist/ai/engines-page.d.ts +130 -0
- package/dist/ai/engines-page.js +10 -0
- package/dist/ai/engines-page.js.map +1 -0
- package/dist/ai/index.d.ts +1531 -0
- package/dist/ai/index.js +395 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai-ui-C7ENTWWU.js +3370 -0
- package/dist/ai-ui-C7ENTWWU.js.map +1 -0
- package/dist/chunk-7A5HEPLK.js +4910 -0
- package/dist/chunk-7A5HEPLK.js.map +1 -0
- package/dist/chunk-GWCRPPCX.js +18 -0
- package/dist/chunk-GWCRPPCX.js.map +1 -0
- package/dist/chunk-JZWKEBCI.js +3899 -0
- package/dist/chunk-JZWKEBCI.js.map +1 -0
- package/dist/chunk-L4FO63DF.js +113 -0
- package/dist/chunk-L4FO63DF.js.map +1 -0
- package/dist/chunk-LXZG7IKN.js +39 -0
- package/dist/chunk-LXZG7IKN.js.map +1 -0
- package/dist/chunk-QDVY7WW6.js +165 -0
- package/dist/chunk-QDVY7WW6.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 +6511 -1258
- package/dist/index.js.map +1 -1
- package/dist/schema-DYzxkuSb.d.ts +46 -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 +3642 -127
- package/dist/types-CgEeIUhi.d.ts +432 -0
- package/dist/types-CsVNVSNY.d.ts +597 -0
- package/package.json +11 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// src/portal.ts
|
|
2
|
+
var portalEl = null;
|
|
3
|
+
var source = null;
|
|
4
|
+
function getPortal() {
|
|
5
|
+
if (!portalEl || !portalEl.isConnected) {
|
|
6
|
+
portalEl = document.createElement("div");
|
|
7
|
+
portalEl.className = "mikedown-portal";
|
|
8
|
+
document.body.appendChild(portalEl);
|
|
9
|
+
syncPortal();
|
|
10
|
+
}
|
|
11
|
+
return portalEl;
|
|
12
|
+
}
|
|
13
|
+
function setPortalSource(root) {
|
|
14
|
+
source = root;
|
|
15
|
+
syncPortal();
|
|
16
|
+
}
|
|
17
|
+
function releasePortalSource(root) {
|
|
18
|
+
if (source === root) {
|
|
19
|
+
source = null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function syncPortal() {
|
|
23
|
+
if (!portalEl || !source) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const theme = source.getAttribute("data-mikedown-theme");
|
|
27
|
+
if (theme) {
|
|
28
|
+
portalEl.setAttribute("data-mikedown-theme", theme);
|
|
29
|
+
} else {
|
|
30
|
+
portalEl.removeAttribute("data-mikedown-theme");
|
|
31
|
+
}
|
|
32
|
+
portalEl.style.cssText = source.style.cssText;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/ai-rewrite-highlight.ts
|
|
36
|
+
import { Extension } from "@tiptap/core";
|
|
37
|
+
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
38
|
+
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
39
|
+
import { StateEffect, StateField } from "@codemirror/state";
|
|
40
|
+
import { Decoration as CmDecoration, EditorView as CmEditorView } from "@codemirror/view";
|
|
41
|
+
var aiRewriteHighlightKey = new PluginKey("mikedownAiRewriteHighlight");
|
|
42
|
+
var AiRewriteHighlight = Extension.create({
|
|
43
|
+
name: "mikedownAiRewriteHighlight",
|
|
44
|
+
addProseMirrorPlugins() {
|
|
45
|
+
return [new Plugin({
|
|
46
|
+
key: aiRewriteHighlightKey,
|
|
47
|
+
state: {
|
|
48
|
+
init: () => null,
|
|
49
|
+
apply(tr, value) {
|
|
50
|
+
const meta = tr.getMeta(aiRewriteHighlightKey);
|
|
51
|
+
if (meta) {
|
|
52
|
+
return meta.range;
|
|
53
|
+
}
|
|
54
|
+
if (!value || !tr.docChanged) {
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
if (value.anchor) {
|
|
58
|
+
const p = tr.mapping.map(value.from, -1);
|
|
59
|
+
return { ...value, from: p, to: p };
|
|
60
|
+
}
|
|
61
|
+
const from = tr.mapping.map(value.from, 1);
|
|
62
|
+
const to = tr.mapping.map(value.to, -1);
|
|
63
|
+
return to > from ? { from, to, id: value.id } : null;
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
props: {
|
|
67
|
+
decorations(state) {
|
|
68
|
+
const r = aiRewriteHighlightKey.getState(state);
|
|
69
|
+
if (!r || r.to <= r.from) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return DecorationSet.create(state.doc, [Decoration.inline(r.from, r.to, { class: "mikedown-ai-rewrite-target" })]);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
})];
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
var setCmRewriteRange = StateEffect.define();
|
|
79
|
+
var cmRewriteRange = StateField.define({
|
|
80
|
+
create: () => null,
|
|
81
|
+
update(value, tr) {
|
|
82
|
+
for (const e of tr.effects) {
|
|
83
|
+
if (e.is(setCmRewriteRange)) {
|
|
84
|
+
return e.value;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (!value || !tr.docChanged) {
|
|
88
|
+
return value;
|
|
89
|
+
}
|
|
90
|
+
if (value.anchor) {
|
|
91
|
+
const p = tr.changes.mapPos(value.from, -1);
|
|
92
|
+
return { ...value, from: p, to: p };
|
|
93
|
+
}
|
|
94
|
+
const from = tr.changes.mapPos(value.from, 1);
|
|
95
|
+
const to = tr.changes.mapPos(value.to, -1);
|
|
96
|
+
return to > from ? { from, to, id: value.id } : null;
|
|
97
|
+
},
|
|
98
|
+
provide: (f) => CmEditorView.decorations.from(f, (r) => r && r.to > r.from ? CmDecoration.set([CmDecoration.mark({ class: "mikedown-ai-rewrite-target" }).range(r.from, r.to)]) : CmDecoration.none)
|
|
99
|
+
});
|
|
100
|
+
var cmAiRewriteHighlight = [cmRewriteRange];
|
|
101
|
+
|
|
102
|
+
export {
|
|
103
|
+
getPortal,
|
|
104
|
+
setPortalSource,
|
|
105
|
+
releasePortalSource,
|
|
106
|
+
syncPortal,
|
|
107
|
+
aiRewriteHighlightKey,
|
|
108
|
+
AiRewriteHighlight,
|
|
109
|
+
setCmRewriteRange,
|
|
110
|
+
cmRewriteRange,
|
|
111
|
+
cmAiRewriteHighlight
|
|
112
|
+
};
|
|
113
|
+
//# sourceMappingURL=chunk-L4FO63DF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/portal.ts","../src/ai-rewrite-highlight.ts"],"sourcesContent":["/**\n * Overlay portal.\n *\n * Menus, pickers and dialogs that must escape the editor box (slash menu,\n * context menu, emoji picker, settings dialog, ...) are appended to a single\n * `<div class=\"mikedown-portal\">` on `<body>` instead of to `<body>` itself.\n * The core CSS is scoped to `.mikedown-root` and `.mikedown-portal`, so this is\n * what lets those overlays be styled without the stylesheet touching the host\n * page. The portal is `display: contents` (no box of its own).\n *\n * The portal copies the theme attribute and inline CSS variables of the editor\n * root that last asked for it (the focused or most recently themed editor), so\n * overlays share that editor's tokens and fonts.\n */\n\nlet portalEl: HTMLElement | null = null;\nlet source: HTMLElement | null = null;\n\n/** The portal element, created on first use (and again if something removed it). */\nexport function getPortal(): HTMLElement {\n if (!portalEl || !portalEl.isConnected) {\n portalEl = document.createElement('div');\n portalEl.className = 'mikedown-portal';\n document.body.appendChild(portalEl);\n syncPortal();\n }\n return portalEl;\n}\n\n/** Make `root` the editor whose theme the portal mirrors, and mirror it now. */\nexport function setPortalSource(root: HTMLElement): void {\n source = root;\n syncPortal();\n}\n\n/** Forget `root` as the portal's source (on destroy). */\nexport function releasePortalSource(root: HTMLElement): void {\n if (source === root) {source = null;}\n}\n\n/** Copy the source root's theme attribute and inline variables onto the portal. */\nexport function syncPortal(): void {\n if (!portalEl || !source) {return;}\n const theme = source.getAttribute('data-mikedown-theme');\n if (theme) {portalEl.setAttribute('data-mikedown-theme', theme);}\n else {portalEl.removeAttribute('data-mikedown-theme');}\n portalEl.style.cssText = source.style.cssText;\n}\n","/**\n * The rewrite target highlight (a PM plugin in rich text, a CM state field in\n * source mode). Kept apart from the popover in `ai-rewrite.ts` because the\n * editor adds both extensions when it is built, while the rest of the AI UI\n * loads on demand (`ai-lite.ts`).\n */\n\nimport { Extension } from '@tiptap/core';\nimport { Plugin, PluginKey } from '@tiptap/pm/state';\nimport { Decoration, DecorationSet } from '@tiptap/pm/view';\nimport { StateEffect, StateField } from '@codemirror/state';\nimport { Decoration as CmDecoration, EditorView as CmEditorView } from '@codemirror/view';\n\n/**\n * A target range; `id` tells one open target from the next. An `anchor` is\n * a caret position (from === to) that follows edits and is never dropped.\n */\nexport interface Range { from: number; to: number; id: number; anchor?: boolean }\n\n// ── Target highlight: rich text ─────────────────────────────────────────────\n\nexport const aiRewriteHighlightKey = new PluginKey<Range | null>('mikedownAiRewriteHighlight');\n\n/** Marks the rewrite target while the popover is open; the range follows edits. */\nexport const AiRewriteHighlight = Extension.create({\n name: 'mikedownAiRewriteHighlight',\n addProseMirrorPlugins() {\n return [new Plugin<Range | null>({\n key: aiRewriteHighlightKey,\n state: {\n init: () => null,\n apply(tr, value) {\n const meta = tr.getMeta(aiRewriteHighlightKey) as { range: Range | null } | undefined;\n if (meta) {return meta.range;}\n if (!value || !tr.docChanged) {return value;}\n if (value.anchor) {\n const p = tr.mapping.map(value.from, -1);\n return { ...value, from: p, to: p };\n }\n const from = tr.mapping.map(value.from, 1);\n const to = tr.mapping.map(value.to, -1);\n return to > from ? { from, to, id: value.id } : null;\n },\n },\n props: {\n decorations(state) {\n const r = aiRewriteHighlightKey.getState(state);\n if (!r || r.to <= r.from) {return null;}\n return DecorationSet.create(state.doc, [Decoration.inline(r.from, r.to, { class: 'mikedown-ai-rewrite-target' })]);\n },\n },\n })];\n },\n});\n\n// ── Target highlight: source mode ───────────────────────────────────────────\n\nexport const setCmRewriteRange = StateEffect.define<Range | null>();\n\nexport const cmRewriteRange = StateField.define<Range | null>({\n create: () => null,\n update(value, tr) {\n for (const e of tr.effects) {if (e.is(setCmRewriteRange)) {return e.value;}}\n if (!value || !tr.docChanged) {return value;}\n if (value.anchor) {\n const p = tr.changes.mapPos(value.from, -1);\n return { ...value, from: p, to: p };\n }\n const from = tr.changes.mapPos(value.from, 1);\n const to = tr.changes.mapPos(value.to, -1);\n return to > from ? { from, to, id: value.id } : null;\n },\n provide: (f) => CmEditorView.decorations.from(f, (r) => (r && r.to > r.from\n ? CmDecoration.set([CmDecoration.mark({ class: 'mikedown-ai-rewrite-target' }).range(r.from, r.to)])\n : CmDecoration.none)),\n});\n\n/** Add to the source view's extensions. */\nexport const cmAiRewriteHighlight = [cmRewriteRange];\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/ai-lite.ts
|
|
2
|
+
var aiUi = null;
|
|
3
|
+
var loading = null;
|
|
4
|
+
function loadedAiUi() {
|
|
5
|
+
return aiUi;
|
|
6
|
+
}
|
|
7
|
+
function loadAiUi() {
|
|
8
|
+
if (aiUi) {
|
|
9
|
+
return Promise.resolve(aiUi);
|
|
10
|
+
}
|
|
11
|
+
loading ?? (loading = import("./ai-ui-C7ENTWWU.js").then(
|
|
12
|
+
(m) => {
|
|
13
|
+
aiUi = m;
|
|
14
|
+
return m;
|
|
15
|
+
},
|
|
16
|
+
(err) => {
|
|
17
|
+
loading = null;
|
|
18
|
+
throw err;
|
|
19
|
+
}
|
|
20
|
+
));
|
|
21
|
+
return loading;
|
|
22
|
+
}
|
|
23
|
+
function aiHostTier(ai) {
|
|
24
|
+
return typeof ai.listEngines === "function" ? 2 : 1;
|
|
25
|
+
}
|
|
26
|
+
function aiToolbarButtonMode(hasAiHost, settings) {
|
|
27
|
+
if (!hasAiHost || !settings.showToolbarButton) {
|
|
28
|
+
return "hidden";
|
|
29
|
+
}
|
|
30
|
+
return settings.enabled ? "menu" : "firstRun";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
loadedAiUi,
|
|
35
|
+
loadAiUi,
|
|
36
|
+
aiHostTier,
|
|
37
|
+
aiToolbarButtonMode
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=chunk-LXZG7IKN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ai-lite.ts"],"sourcesContent":["/**\n * The little of the AI layer the editor needs before the AI UI is loaded,\n * and the loader for the rest.\n *\n * Everything behind `host.ai` (the rewrite popover, the AI settings tab and\n * Engines page, the toolbar menu, the prompts and the router) lives in\n * `ai-ui.ts`, which the editor imports with `import()` only when the host has\n * an AI adapter. Bundlers split it into its own chunk, so an editor without\n * AI never downloads it. A host that wants it in its main bundle, loaded\n * synchronously (the VS Code webview), imports `ai-ui-eager.ts` once.\n */\n\nimport type { AiSettings, MikeDownAiHost } from './ai/types';\n\nexport type AiUi = typeof import('./ai-ui');\n\nlet aiUi: AiUi | null = null;\nlet loading: Promise<AiUi> | null = null;\n\n/** The AI UI when it is already loaded (or registered), else null. */\nexport function loadedAiUi(): AiUi | null {\n return aiUi;\n}\n\n/** Load the AI UI chunk once; a failed load is tried again on the next call. */\nexport function loadAiUi(): Promise<AiUi> {\n if (aiUi) {return Promise.resolve(aiUi);}\n loading ??= import('./ai-ui').then(\n (m) => { aiUi = m; return m; },\n (err: unknown) => { loading = null; throw err; },\n );\n return loading;\n}\n\n/** Make the AI UI available synchronously (`ai-ui-eager.ts`); null forgets it (tests). */\nexport function registerAiUi(m: AiUi | null): void {\n aiUi = m;\n loading = null;\n}\n\n// ── Host tier ───────────────────────────────────────────────────────────────\n\n/** 1: `complete` only, the host routes. 2: the host has an engine registry and core routes. */\nexport type AiHostTier = 1 | 2;\n\nexport function aiHostTier(ai: MikeDownAiHost): AiHostTier {\n return typeof ai.listEngines === 'function' ? 2 : 1;\n}\n\n// ── Toolbar button ──────────────────────────────────────────────────────────\n\nexport type AiToolbarButtonMode = 'hidden' | 'firstRun' | 'menu';\n\n/** What the button does: hidden without AI support or when turned off in settings. */\nexport function aiToolbarButtonMode(hasAiHost: boolean, settings: Pick<AiSettings, 'enabled' | 'showToolbarButton'>): AiToolbarButtonMode {\n if (!hasAiHost || !settings.showToolbarButton) {return 'hidden';}\n return settings.enabled ? 'menu' : 'firstRun';\n}\n"],"mappings":";AAgBA,IAAI,OAAoB;AACxB,IAAI,UAAgC;AAG7B,SAAS,aAA0B;AACxC,SAAO;AACT;AAGO,SAAS,WAA0B;AACxC,MAAI,MAAM;AAAC,WAAO,QAAQ,QAAQ,IAAI;AAAA,EAAE;AACxC,wBAAY,OAAO,qBAAS,EAAE;AAAA,IAC5B,CAAC,MAAM;AAAE,aAAO;AAAG,aAAO;AAAA,IAAG;AAAA,IAC7B,CAAC,QAAiB;AAAE,gBAAU;AAAM,YAAM;AAAA,IAAK;AAAA,EACjD;AACA,SAAO;AACT;AAaO,SAAS,WAAW,IAAgC;AACzD,SAAO,OAAO,GAAG,gBAAgB,aAAa,IAAI;AACpD;AAOO,SAAS,oBAAoB,WAAoB,UAAkF;AACxI,MAAI,CAAC,aAAa,CAAC,SAAS,mBAAmB;AAAC,WAAO;AAAA,EAAS;AAChE,SAAO,SAAS,UAAU,SAAS;AACrC;","names":[]}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// src/ai/locality.ts
|
|
2
|
+
var DEFAULT_LOCAL_NETWORKS = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"];
|
|
3
|
+
var ALWAYS_LOCAL_IPV4 = ["127.0.0.0/8", "169.254.0.0/16", "0.0.0.0/32"];
|
|
4
|
+
var LOCAL_SUFFIXES = [".localhost", ".local", ".lan", ".home.arpa", ".internal"];
|
|
5
|
+
function hostOfUrl(url) {
|
|
6
|
+
const trimmed = url.trim();
|
|
7
|
+
if (!trimmed) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
const withoutScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) ? trimmed.replace(/^[a-z][a-z0-9+.-]*:\/\//i, "") : trimmed;
|
|
11
|
+
const authority = withoutScheme.split(/[/?#]/, 1)[0];
|
|
12
|
+
const hostPort = authority.includes("@") ? authority.slice(authority.lastIndexOf("@") + 1) : authority;
|
|
13
|
+
if (!hostPort) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
if (hostPort.startsWith("[")) {
|
|
17
|
+
const end = hostPort.indexOf("]");
|
|
18
|
+
return end > 1 ? hostPort.slice(1, end).toLowerCase() : null;
|
|
19
|
+
}
|
|
20
|
+
if ((hostPort.match(/:/g) ?? []).length > 1) {
|
|
21
|
+
return hostPort.toLowerCase();
|
|
22
|
+
}
|
|
23
|
+
const host = hostPort.split(":", 1)[0].toLowerCase().replace(/\.$/, "");
|
|
24
|
+
return host || null;
|
|
25
|
+
}
|
|
26
|
+
function parseIpv4(host) {
|
|
27
|
+
const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
|
|
28
|
+
if (!m) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const parts = m.slice(1).map(Number);
|
|
32
|
+
if (parts.some((p) => p > 255)) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return (parts[0] << 24 >>> 0) + (parts[1] << 16) + (parts[2] << 8) + parts[3];
|
|
36
|
+
}
|
|
37
|
+
function inIpv4Cidr(ip, cidr) {
|
|
38
|
+
const [base, bitsText] = cidr.split("/");
|
|
39
|
+
const baseIp = parseIpv4(base.trim());
|
|
40
|
+
const bits = bitsText === void 0 ? 32 : Number(bitsText);
|
|
41
|
+
if (baseIp === null || !Number.isInteger(bits) || bits < 0 || bits > 32) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (bits === 0) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
const mask = 4294967295 << 32 - bits >>> 0;
|
|
48
|
+
return (ip & mask) >>> 0 === (baseIp & mask) >>> 0;
|
|
49
|
+
}
|
|
50
|
+
function isLocalIpv6(host) {
|
|
51
|
+
if (!host.includes(":")) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
if (host === "::1" || host === "::") {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
const first = host.split(":")[0];
|
|
58
|
+
if (!first) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
const word = parseInt(first, 16);
|
|
62
|
+
if (Number.isNaN(word)) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
return (word & 65472) === 65152 || (word & 65024) === 64512;
|
|
66
|
+
}
|
|
67
|
+
function detectLocality(baseUrl, localNetworks = DEFAULT_LOCAL_NETWORKS) {
|
|
68
|
+
const host = hostOfUrl(baseUrl);
|
|
69
|
+
if (!host) {
|
|
70
|
+
return "cloud";
|
|
71
|
+
}
|
|
72
|
+
if (host === "localhost" || LOCAL_SUFFIXES.some((s) => host.endsWith(s))) {
|
|
73
|
+
return "local";
|
|
74
|
+
}
|
|
75
|
+
const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(host);
|
|
76
|
+
const ip = parseIpv4(mapped ? mapped[1] : host);
|
|
77
|
+
if (ip !== null) {
|
|
78
|
+
if (ALWAYS_LOCAL_IPV4.some((c) => inIpv4Cidr(ip, c))) {
|
|
79
|
+
return "local";
|
|
80
|
+
}
|
|
81
|
+
return localNetworks.some((entry) => /^\d/.test(entry.trim()) && inIpv4Cidr(ip, entry.trim())) ? "local" : "cloud";
|
|
82
|
+
}
|
|
83
|
+
if (isLocalIpv6(host)) {
|
|
84
|
+
return "local";
|
|
85
|
+
}
|
|
86
|
+
for (const raw of localNetworks) {
|
|
87
|
+
const entry = raw.trim().toLowerCase();
|
|
88
|
+
if (!entry || /^\d/.test(entry)) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (entry.startsWith(".") ? host.endsWith(entry) : host === entry || host.endsWith(`.${entry}`)) {
|
|
92
|
+
return "local";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return "cloud";
|
|
96
|
+
}
|
|
97
|
+
function engineLocality(engine, localNetworks) {
|
|
98
|
+
if (engine.kind === "vscode-lm") {
|
|
99
|
+
return "cloud";
|
|
100
|
+
}
|
|
101
|
+
if (engine.locality === "local" || engine.locality === "cloud") {
|
|
102
|
+
return engine.locality;
|
|
103
|
+
}
|
|
104
|
+
return detectLocality(engine.baseUrl, localNetworks);
|
|
105
|
+
}
|
|
106
|
+
function localityMismatch(engine, localNetworks) {
|
|
107
|
+
if (engine.locality === "auto" || engine.kind === "vscode-lm") {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
return engine.locality !== detectLocality(engine.baseUrl, localNetworks);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/ai/defaults.ts
|
|
114
|
+
function defaultAiSettings() {
|
|
115
|
+
return {
|
|
116
|
+
enabled: false,
|
|
117
|
+
allowCloud: false,
|
|
118
|
+
engines: [],
|
|
119
|
+
routing: { default: null, fallback: "anyLocal", tasks: {} },
|
|
120
|
+
features: {},
|
|
121
|
+
localNetworks: [...DEFAULT_LOCAL_NETWORKS],
|
|
122
|
+
showToolbarButton: true
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/shared/tagSyntax.ts
|
|
127
|
+
var SEGMENT = "[A-Za-z0-9_-]+";
|
|
128
|
+
var TAG_SOURCE = `(?<![\\w/#&])#(${SEGMENT}(?:/${SEGMENT})*)`;
|
|
129
|
+
function inlineTagRegex() {
|
|
130
|
+
return new RegExp(TAG_SOURCE, "g");
|
|
131
|
+
}
|
|
132
|
+
function isValidTag(tag) {
|
|
133
|
+
return /[A-Za-z]/.test(tag);
|
|
134
|
+
}
|
|
135
|
+
function normalizeTag(raw) {
|
|
136
|
+
const t = raw.trim().replace(/^#+/, "").toLowerCase();
|
|
137
|
+
if (!t || !isValidTag(t)) return null;
|
|
138
|
+
return t;
|
|
139
|
+
}
|
|
140
|
+
function looksLikeHexColor(tag) {
|
|
141
|
+
if (!/^[0-9a-f]+$/i.test(tag)) return false;
|
|
142
|
+
return /[0-9]/.test(tag) || tag.length === 6 || tag.length === 8;
|
|
143
|
+
}
|
|
144
|
+
function findInlineTags(text) {
|
|
145
|
+
const re = inlineTagRegex();
|
|
146
|
+
const out = [];
|
|
147
|
+
let m;
|
|
148
|
+
while ((m = re.exec(text)) !== null) {
|
|
149
|
+
if (!isValidTag(m[1]) || looksLikeHexColor(m[1])) continue;
|
|
150
|
+
out.push({ tag: m[1], index: m.index, length: m[0].length });
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export {
|
|
156
|
+
normalizeTag,
|
|
157
|
+
findInlineTags,
|
|
158
|
+
DEFAULT_LOCAL_NETWORKS,
|
|
159
|
+
hostOfUrl,
|
|
160
|
+
detectLocality,
|
|
161
|
+
engineLocality,
|
|
162
|
+
localityMismatch,
|
|
163
|
+
defaultAiSettings
|
|
164
|
+
};
|
|
165
|
+
//# sourceMappingURL=chunk-QDVY7WW6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ai/locality.ts","../src/ai/defaults.ts","../src/shared/tagSyntax.ts"],"sourcesContent":["/**\n * Local or cloud, from an engine's base URL. Loopback, link-local, IPv6\n * unique-local, `localhost`, `.local`, `.lan`, `.home.arpa` and `.internal`\n * are always local; `localNetworks` adds CIDR ranges (RFC 1918 by default, a\n * WireGuard range such as `10.0.98.0/24`) and `.suffix` host names. Anything\n * else, including a URL that does not parse, is cloud.\n */\n\nimport type { AiEngineConfig, AiLocality } from './types';\n\n/** Default `mikedown.ai.localNetworks`: RFC 1918. */\nexport const DEFAULT_LOCAL_NETWORKS: readonly string[] = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'];\n\nconst ALWAYS_LOCAL_IPV4 = ['127.0.0.0/8', '169.254.0.0/16', '0.0.0.0/32'];\nconst LOCAL_SUFFIXES = ['.localhost', '.local', '.lan', '.home.arpa', '.internal'];\n\n/** Lower-cased host of a URL (no port, no brackets), or null. Accepts a bare `host:port`. */\nexport function hostOfUrl(url: string): string | null {\n const trimmed = url.trim();\n if (!trimmed) {return null;}\n const withoutScheme = /^[a-z][a-z0-9+.-]*:\\/\\//i.test(trimmed) ? trimmed.replace(/^[a-z][a-z0-9+.-]*:\\/\\//i, '') : trimmed;\n const authority = withoutScheme.split(/[/?#]/, 1)[0];\n const hostPort = authority.includes('@') ? authority.slice(authority.lastIndexOf('@') + 1) : authority;\n if (!hostPort) {return null;}\n if (hostPort.startsWith('[')) {\n const end = hostPort.indexOf(']');\n return end > 1 ? hostPort.slice(1, end).toLowerCase() : null;\n }\n // More than one colon and no brackets: a bare IPv6 address.\n if ((hostPort.match(/:/g) ?? []).length > 1) {return hostPort.toLowerCase();}\n const host = hostPort.split(':', 1)[0].toLowerCase().replace(/\\.$/, '');\n return host || null;\n}\n\nfunction parseIpv4(host: string): number | null {\n const m = /^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/.exec(host);\n if (!m) {return null;}\n const parts = m.slice(1).map(Number);\n if (parts.some((p) => p > 255)) {return null;}\n return ((parts[0] << 24) >>> 0) + (parts[1] << 16) + (parts[2] << 8) + parts[3];\n}\n\nfunction inIpv4Cidr(ip: number, cidr: string): boolean {\n const [base, bitsText] = cidr.split('/');\n const baseIp = parseIpv4(base.trim());\n const bits = bitsText === undefined ? 32 : Number(bitsText);\n if (baseIp === null || !Number.isInteger(bits) || bits < 0 || bits > 32) {return false;}\n if (bits === 0) {return true;}\n const mask = (0xffffffff << (32 - bits)) >>> 0;\n return ((ip & mask) >>> 0) === ((baseIp & mask) >>> 0);\n}\n\nfunction isLocalIpv6(host: string): boolean {\n if (!host.includes(':')) {return false;}\n if (host === '::1' || host === '::') {return true;}\n // IPv4-mapped (::ffff:192.168.1.2) is judged by its IPv4 part elsewhere.\n const first = host.split(':')[0];\n if (!first) {return false;}\n const word = parseInt(first, 16);\n if (Number.isNaN(word)) {return false;}\n // fe80::/10 link-local, fc00::/7 unique-local.\n return (word & 0xffc0) === 0xfe80 || (word & 0xfe00) === 0xfc00;\n}\n\n/**\n * `local` or `cloud` for a base URL. `localNetworks` entries are IPv4 CIDR\n * ranges (`10.0.98.0/24`, a bare address counts as /32) or host-name\n * suffixes (`.ts.net`, `studio.example.com`).\n */\nexport function detectLocality(baseUrl: string, localNetworks: readonly string[] = DEFAULT_LOCAL_NETWORKS): AiLocality {\n const host = hostOfUrl(baseUrl);\n if (!host) {return 'cloud';}\n if (host === 'localhost' || LOCAL_SUFFIXES.some((s) => host.endsWith(s))) {return 'local';}\n\n const mapped = /^::ffff:(\\d{1,3}(?:\\.\\d{1,3}){3})$/.exec(host);\n const ip = parseIpv4(mapped ? mapped[1] : host);\n if (ip !== null) {\n if (ALWAYS_LOCAL_IPV4.some((c) => inIpv4Cidr(ip, c))) {return 'local';}\n return localNetworks.some((entry) => /^\\d/.test(entry.trim()) && inIpv4Cidr(ip, entry.trim())) ? 'local' : 'cloud';\n }\n if (isLocalIpv6(host)) {return 'local';}\n\n for (const raw of localNetworks) {\n const entry = raw.trim().toLowerCase();\n if (!entry || /^\\d/.test(entry)) {continue;}\n if (entry.startsWith('.') ? host.endsWith(entry) : host === entry || host.endsWith(`.${entry}`)) {return 'local';}\n }\n return 'cloud';\n}\n\n/**\n * The engine's effective locality: its pin, or `detectLocality` for `auto`.\n * A `vscode-lm` engine is always cloud, whatever its pin: it has no address,\n * and the text goes to GitHub's Copilot service.\n */\nexport function engineLocality(engine: Pick<AiEngineConfig, 'baseUrl' | 'locality'> & { kind?: string }, localNetworks?: readonly string[]): AiLocality {\n if (engine.kind === 'vscode-lm') {return 'cloud';}\n if (engine.locality === 'local' || engine.locality === 'cloud') {return engine.locality;}\n return detectLocality(engine.baseUrl, localNetworks);\n}\n\n/**\n * True when a pinned locality disagrees with the URL (cloud pinned on a\n * private address, or local pinned on a public one). The indicator shows a\n * warning glyph for it.\n */\nexport function localityMismatch(engine: Pick<AiEngineConfig, 'baseUrl' | 'locality'> & { kind?: string }, localNetworks?: readonly string[]): boolean {\n if (engine.locality === 'auto' || engine.kind === 'vscode-lm') {return false;}\n return engine.locality !== detectLocality(engine.baseUrl, localNetworks);\n}\n","/**\n * The settings before the host sends any. Its own module so the editor can\n * start from them without loading the rest of the AI layer (see `ai-lite.ts`).\n */\n\nimport { DEFAULT_LOCAL_NETWORKS } from './locality';\nimport type { AiSettings } from './types';\n\nexport function defaultAiSettings(): AiSettings {\n return {\n enabled: false,\n allowCloud: false,\n engines: [],\n routing: { default: null, fallback: 'anyLocal', tasks: {} },\n features: {},\n localNetworks: [...DEFAULT_LOCAL_NETWORKS],\n showToolbarButton: true,\n };\n}\n","// Shared tag syntax — pure, no DOM or vscode imports so both the extension\n// host and the webview bundle can consume it (same cross-bundle pattern as\n// `frontmatterYaml.ts` / `imageDisplayPath.ts`).\n//\n// A tag is `#` followed by one or more `/`-separated segments of\n// [A-Za-z0-9_-]. It must NOT be preceded by a word char, `/`, `#`, or `&`\n// (so it won't fire inside URLs, `##` sequences, or HTML entities) and must\n// contain at least one letter (so pure numbers like `#1234` aren't tags).\n// Inline tags that look like hex colors (`#2563eb`, `#ffffff`, `#f0f`) are\n// skipped too; frontmatter `tags:` entries are explicit, so they aren't.\n// Nested tags use slashes: `#project/active`.\n\nconst SEGMENT = '[A-Za-z0-9_-]+';\nconst TAG_SOURCE = `(?<![\\\\w/#&])#(${SEGMENT}(?:/${SEGMENT})*)`;\n\n/** Fresh global regex each call (global regexes carry mutable lastIndex). */\nexport function inlineTagRegex(): RegExp {\n return new RegExp(TAG_SOURCE, 'g');\n}\n\n/** A tag is only valid if it contains at least one letter. */\nexport function isValidTag(tag: string): boolean {\n return /[A-Za-z]/.test(tag);\n}\n\n/** Normalize a tag for indexing/matching: strip a leading `#`, lowercase. */\nexport function normalizeTag(raw: string): string | null {\n const t = raw.trim().replace(/^#+/, '').toLowerCase();\n if (!t || !isValidTag(t)) return null;\n return t;\n}\n\n/** Hex-color-like tokens: any all-hex run containing a digit (`#0f`,\n * `#2563eb`), or 6/8 hex letters (`#ffffff`). All-letter words such as\n * `#bad`, `#cafe`, `#face` still count as tags. */\nexport function looksLikeHexColor(tag: string): boolean {\n if (!/^[0-9a-f]+$/i.test(tag)) return false;\n return /[0-9]/.test(tag) || tag.length === 6 || tag.length === 8;\n}\n\nexport interface InlineTagMatch {\n /** The tag without its leading `#`, original case. */\n tag: string;\n /** Offset of the `#` within the scanned string. */\n index: number;\n /** Length of the full `#tag` token. */\n length: number;\n}\n\n/** Find every inline `#tag` token in a single string. */\nexport function findInlineTags(text: string): InlineTagMatch[] {\n const re = inlineTagRegex();\n const out: InlineTagMatch[] = [];\n let m: RegExpExecArray | null;\n while ((m = re.exec(text)) !== null) {\n if (!isValidTag(m[1]) || looksLikeHexColor(m[1])) continue;\n out.push({ tag: m[1], index: m.index, length: m[0].length });\n }\n return out;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// src/shared/sha1.ts
|
|
2
|
+
function rotl(x, n) {
|
|
3
|
+
return x << n | x >>> 32 - n;
|
|
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
|
+
}
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
sha1Hex
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=chunk-QWKT57TR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/sha1.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"],"mappings":";AAKA,SAAS,KAAK,GAAW,GAAmB;AAC1C,SAAQ,KAAK,IAAM,MAAO,KAAK;AACjC;AAEO,SAAS,QAAQ,MAA0B;AAChD,QAAM,MAAM,KAAK;AACjB,QAAM,SAAS,IAAI,YAAc,MAAM,KAAM,KAAK,KAAM,CAAC;AACzD,SAAO,IAAI,IAAI;AACf,SAAO,GAAG,IAAI;AACd,QAAM,OAAO,IAAI,SAAS,OAAO,MAAM;AAEvC,OAAK,UAAU,OAAO,SAAS,GAAG,KAAK,MAAM,MAAM,SAAU,CAAC;AAC9D,OAAK,UAAU,OAAO,SAAS,GAAI,MAAM,MAAO,CAAC;AAEjD,MAAI,KAAK;AACT,MAAI,KAAK;AACT,MAAI,KAAK;AACT,MAAI,KAAK;AACT,MAAI,KAAK;AACT,QAAM,IAAI,IAAI,YAAY,EAAE;AAE5B,WAAS,MAAM,GAAG,MAAM,OAAO,QAAQ,OAAO,IAAI;AAChD,aAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAAC,QAAE,CAAC,IAAI,KAAK,UAAU,MAAM,IAAI,CAAC;AAAA,IAAE;AACjE,aAAS,IAAI,IAAI,IAAI,IAAI,KAAK;AAAC,QAAE,CAAC,IAAI,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC;AAAA,IAAE;AAC3F,QAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI;AACxC,aAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,UAAI;AACJ,UAAI;AACJ,UAAI,IAAI,IAAI;AAAE,YAAK,IAAI,IAAM,CAAC,IAAI;AAAI,YAAI;AAAA,MAAY,WAC7C,IAAI,IAAI;AAAE,YAAI,IAAI,IAAI;AAAG,YAAI;AAAA,MAAY,WACzC,IAAI,IAAI;AAAE,YAAK,IAAI,IAAM,IAAI,IAAM,IAAI;AAAI,YAAI;AAAA,MAAY,OAC/D;AAAE,YAAI,IAAI,IAAI;AAAG,YAAI;AAAA,MAAY;AACtC,YAAM,IAAK,KAAK,GAAG,CAAC,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC,MAAO;AAC9C,UAAI;AAAG,UAAI;AAAG,UAAI,KAAK,GAAG,EAAE,MAAM;AAAG,UAAI;AAAG,UAAI;AAAA,IAClD;AACA,SAAM,KAAK,MAAO;AAClB,SAAM,KAAK,MAAO;AAClB,SAAM,KAAK,MAAO;AAClB,SAAM,KAAK,MAAO;AAClB,SAAM,KAAK,MAAO;AAAA,EACpB;AACA,SAAO,CAAC,IAAI,IAAI,IAAI,IAAI,EAAE,EAAE,IAAI,OAAK,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAC/E;","names":[]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// src/shared/anchoring.ts
|
|
2
|
+
function githubAnchorId(text) {
|
|
3
|
+
return text.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/\s/g, "-");
|
|
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
|
+
}
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
githubAnchorId,
|
|
20
|
+
dedupedAnchorIdAt
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=chunk-TSP7BAV3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/anchoring.ts"],"sourcesContent":["// Shared GitHub-style anchor slug generation.\n//\n// IMPORTANT: this module must be importable by BOTH webpack bundles — the\n// extension host (node target, tsconfig.json) and the webview (web target,\n// tsconfig.webview.json). Keep it dependency-free: no `vscode`, no `node:*`,\n// no DOM. (Same rule as imageDisplayPath.ts.) The \"Heading Rename → Fix Links\"\n// feature relies on host and webview computing byte-identical slugs.\n\n/**\n * Convert heading text to a GitHub-style anchor id (the \"base\" slug, before any\n * duplicate-disambiguation suffix).\n *\n * Match GitHub exactly: strip punctuation, then turn each whitespace char into a\n * hyphen. Do NOT collapse consecutive hyphens or trim trailing ones — GitHub\n * keeps them (e.g. \"Memory & Hardware\" → \"memory--hardware\", \"UD-\" → \"ud-\").\n */\nexport function githubAnchorId(text: string): string {\n return text\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, '') // remove punctuation (keep word chars, whitespace, hyphens)\n .replace(/\\s/g, '-'); // each whitespace char → hyphen (preserve consecutive)\n}\n\n/**\n * Given heading texts in document order, return the deduplicated anchor id for\n * the heading at `index`, using GitHub's encounter-order suffixing\n * (`setup`, `setup-1`, `setup-2`, …). The first occurrence keeps the base slug.\n */\nexport function dedupedAnchorIdAt(headingTexts: string[], index: number): string {\n const seen = new Map<string, number>();\n for (let i = 0; i < headingTexts.length; i++) {\n const base = githubAnchorId(headingTexts[i]);\n const count = seen.get(base) ?? 0;\n seen.set(base, count + 1);\n if (i === index) {return count === 0 ? base : `${base}-${count}`;}\n }\n return '';\n}\n"],"mappings":";AAgBO,SAAS,eAAe,MAAsB;AACnD,SAAO,KACJ,YAAY,EACZ,KAAK,EACL,QAAQ,aAAa,EAAE,EACvB,QAAQ,OAAO,GAAG;AACvB;AAOO,SAAS,kBAAkB,cAAwB,OAAuB;AAC/E,QAAM,OAAO,oBAAI,IAAoB;AACrC,WAAS,IAAI,GAAG,IAAI,aAAa,QAAQ,KAAK;AAC5C,UAAM,OAAO,eAAe,aAAa,CAAC,CAAC;AAC3C,UAAM,QAAQ,KAAK,IAAI,IAAI,KAAK;AAChC,SAAK,IAAI,MAAM,QAAQ,CAAC;AACxB,QAAI,MAAM,OAAO;AAAC,aAAO,UAAU,IAAI,OAAO,GAAG,IAAI,IAAI,KAAK;AAAA,IAAG;AAAA,EACnE;AACA,SAAO;AACT;","names":[]}
|