@mdzip/editor 1.4.0 → 1.4.5
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/README.md +35 -0
- package/dist/asset-cache.d.ts +2 -0
- package/dist/asset-cache.d.ts.map +1 -1
- package/dist/asset-cache.js +12 -0
- package/dist/asset-cache.js.map +1 -1
- package/dist/front-matter-extension.d.ts +45 -0
- package/dist/front-matter-extension.d.ts.map +1 -0
- package/dist/front-matter-extension.js +114 -0
- package/dist/front-matter-extension.js.map +1 -0
- package/dist/front-matter.d.ts +18 -0
- package/dist/front-matter.d.ts.map +1 -0
- package/dist/front-matter.js +48 -0
- package/dist/front-matter.js.map +1 -0
- package/dist/highlight-core.d.ts +3 -0
- package/dist/highlight-core.d.ts.map +1 -0
- package/dist/highlight-core.js +53 -0
- package/dist/highlight-core.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/library-info.d.ts +1 -1
- package/dist/library-info.js +1 -1
- package/dist/mermaid.d.ts.map +1 -1
- package/dist/mermaid.js +60 -41
- package/dist/mermaid.js.map +1 -1
- package/dist/metadata.d.ts +1 -1
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +12 -2
- package/dist/metadata.js.map +1 -1
- package/dist/preview.d.ts +1 -0
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +1 -0
- package/dist/preview.js.map +1 -1
- package/dist/rendering.d.ts +94 -1
- package/dist/rendering.d.ts.map +1 -1
- package/dist/rendering.js +173 -5
- package/dist/rendering.js.map +1 -1
- package/dist/view-css.d.ts.map +1 -1
- package/dist/view-css.js +67 -0
- package/dist/view-css.js.map +1 -1
- package/dist/view.d.ts +151 -10
- package/dist/view.d.ts.map +1 -1
- package/dist/view.js +745 -78
- package/dist/view.js.map +1 -1
- package/dist/workspace-view.d.ts +1 -0
- package/dist/workspace-view.d.ts.map +1 -1
- package/dist/workspace-view.js +14 -3
- package/dist/workspace-view.js.map +1 -1
- package/package.json +3 -1
package/dist/mermaid.js
CHANGED
|
@@ -74,10 +74,68 @@ async function withBodyCleanup(doc, run) {
|
|
|
74
74
|
export function mdzipMermaidExtension(options = {}) {
|
|
75
75
|
const load = options.loadMermaid ?? loadDefaultMermaid;
|
|
76
76
|
let counter = 0;
|
|
77
|
+
// Split so a chunk with no mermaid blocks returns synchronously instead of
|
|
78
|
+
// through a Promise. Every markdown render extension's transformHtml runs
|
|
79
|
+
// for every chunk regardless of content — declaring this whole thing async
|
|
80
|
+
// (as an earlier version did) meant calling it always returned a Promise,
|
|
81
|
+
// even on this trivial no-op path, forcing the *entire* chunk-render
|
|
82
|
+
// pipeline (marked's `chainRenderStage`) onto a microtask chain for every
|
|
83
|
+
// chunk in every document once mermaid is registered, mermaid or not. That
|
|
84
|
+
// extra always-async hop was implicated in a real, visible per-keystroke
|
|
85
|
+
// flash in mdzip-studio: it delayed a reconciled chunk's replacement DOM
|
|
86
|
+
// landing just long enough, on top of everything else already sharing the
|
|
87
|
+
// event loop, to widen the window where the old chunk's DOM was already
|
|
88
|
+
// torn down but the new one hadn't landed yet.
|
|
89
|
+
async function renderMermaidBlocks(template, blocks, context) {
|
|
90
|
+
const doc = requireDocument();
|
|
91
|
+
const mermaid = await load();
|
|
92
|
+
if (context.signal.aborted) {
|
|
93
|
+
return template.innerHTML;
|
|
94
|
+
}
|
|
95
|
+
mermaid.initialize({
|
|
96
|
+
startOnLoad: false,
|
|
97
|
+
securityLevel: 'strict',
|
|
98
|
+
// Render labels as SVG <text> rather than HTML inside <foreignObject>.
|
|
99
|
+
// The bundled sanitizer keeps the SVG but strips foreignObject HTML, so
|
|
100
|
+
// html labels would otherwise vanish — this keeps the emitted SVG
|
|
101
|
+
// self-consistent with the policy without re-allowing HTML in labels.
|
|
102
|
+
htmlLabels: false,
|
|
103
|
+
flowchart: { htmlLabels: false },
|
|
104
|
+
theme: resolveTheme(options.theme ?? 'auto', context.colorScheme),
|
|
105
|
+
// Stops mermaid from injecting its own error diagram into the DOM on
|
|
106
|
+
// failure; this extension renders its own error block instead.
|
|
107
|
+
suppressErrorRendering: true
|
|
108
|
+
});
|
|
109
|
+
for (const code of blocks) {
|
|
110
|
+
const pre = code.parentElement;
|
|
111
|
+
if (!pre) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const source = code.textContent ?? '';
|
|
115
|
+
const container = doc.createElement('div');
|
|
116
|
+
try {
|
|
117
|
+
const { svg } = await withBodyCleanup(doc, () => mermaid.render(`mdzip-mermaid-${(counter += 1)}`, source));
|
|
118
|
+
container.className = 'mdzip-mermaid';
|
|
119
|
+
container.innerHTML = sanitizeMdzipHtml(svg, [MERMAID_SANITIZE]);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
container.className = 'mdzip-mermaid-error';
|
|
123
|
+
container.textContent = mermaidErrorMessage(error);
|
|
124
|
+
}
|
|
125
|
+
pre.replaceWith(container);
|
|
126
|
+
if (context.signal.aborted) {
|
|
127
|
+
return template.innerHTML;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return template.innerHTML;
|
|
131
|
+
}
|
|
77
132
|
return {
|
|
78
133
|
name: 'mermaid',
|
|
79
134
|
sanitize: MERMAID_SANITIZE,
|
|
80
|
-
|
|
135
|
+
shouldIsolateChunk(token) {
|
|
136
|
+
return token.type === 'code' && token.lang?.trim().toLowerCase() === 'mermaid';
|
|
137
|
+
},
|
|
138
|
+
transformHtml(html, context) {
|
|
81
139
|
const doc = requireDocument();
|
|
82
140
|
const template = doc.createElement('template');
|
|
83
141
|
template.innerHTML = html;
|
|
@@ -85,46 +143,7 @@ export function mdzipMermaidExtension(options = {}) {
|
|
|
85
143
|
if (blocks.length === 0) {
|
|
86
144
|
return html;
|
|
87
145
|
}
|
|
88
|
-
|
|
89
|
-
if (context.signal.aborted) {
|
|
90
|
-
return html;
|
|
91
|
-
}
|
|
92
|
-
mermaid.initialize({
|
|
93
|
-
startOnLoad: false,
|
|
94
|
-
securityLevel: 'strict',
|
|
95
|
-
// Render labels as SVG <text> rather than HTML inside <foreignObject>.
|
|
96
|
-
// The bundled sanitizer keeps the SVG but strips foreignObject HTML, so
|
|
97
|
-
// html labels would otherwise vanish — this keeps the emitted SVG
|
|
98
|
-
// self-consistent with the policy without re-allowing HTML in labels.
|
|
99
|
-
htmlLabels: false,
|
|
100
|
-
flowchart: { htmlLabels: false },
|
|
101
|
-
theme: resolveTheme(options.theme ?? 'auto', context.colorScheme),
|
|
102
|
-
// Stops mermaid from injecting its own error diagram into the DOM on
|
|
103
|
-
// failure; this extension renders its own error block instead.
|
|
104
|
-
suppressErrorRendering: true
|
|
105
|
-
});
|
|
106
|
-
for (const code of blocks) {
|
|
107
|
-
const pre = code.parentElement;
|
|
108
|
-
if (!pre) {
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
const source = code.textContent ?? '';
|
|
112
|
-
const container = doc.createElement('div');
|
|
113
|
-
try {
|
|
114
|
-
const { svg } = await withBodyCleanup(doc, () => mermaid.render(`mdzip-mermaid-${(counter += 1)}`, source));
|
|
115
|
-
container.className = 'mdzip-mermaid';
|
|
116
|
-
container.innerHTML = sanitizeMdzipHtml(svg, [MERMAID_SANITIZE]);
|
|
117
|
-
}
|
|
118
|
-
catch (error) {
|
|
119
|
-
container.className = 'mdzip-mermaid-error';
|
|
120
|
-
container.textContent = mermaidErrorMessage(error);
|
|
121
|
-
}
|
|
122
|
-
pre.replaceWith(container);
|
|
123
|
-
if (context.signal.aborted) {
|
|
124
|
-
return html;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return template.innerHTML;
|
|
146
|
+
return renderMermaidBlocks(template, blocks, context);
|
|
128
147
|
}
|
|
129
148
|
};
|
|
130
149
|
}
|
package/dist/mermaid.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mermaid.js","sourceRoot":"","sources":["../src/mermaid.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mermaid.js","sourceRoot":"","sources":["../src/mermaid.ts"],"names":[],"mappings":"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"}
|
package/dist/metadata.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export declare function firstMarkdownHeading(markdown: string): string | undefined;
|
|
3
3
|
/** Derive the file base name, without extension, from a path string. */
|
|
4
4
|
export declare function fileBaseNameFromPath(path: string): string;
|
|
5
|
-
/** Resolve a stable title fallback from markdown heading or filename. */
|
|
5
|
+
/** Resolve a stable title fallback from front matter `title:`, markdown heading, or filename. */
|
|
6
6
|
export declare function suggestedTitleFromMarkdown(markdown: string, fileBaseName: string): string;
|
|
7
7
|
/** Resolve display title from manifest title with filename fallback. */
|
|
8
8
|
export declare function displayTitleFromManifest(manifestTitle: string | undefined, fileBaseName: string): string;
|
package/dist/metadata.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAEA,gFAAgF;AAChF,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGzE;AAYD,wEAAwE;AACxE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIzD;AAED,iGAAiG;AACjG,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAEzF;AAED,wEAAwE;AACxE,wBAAgB,wBAAwB,CACtC,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,YAAY,EAAE,MAAM,GACnB,MAAM,CAGR"}
|
package/dist/metadata.js
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
|
+
import { parseFrontMatter } from './front-matter.js';
|
|
1
2
|
/** Extract the first ATX heading, for example `# Title`, from markdown text. */
|
|
2
3
|
export function firstMarkdownHeading(markdown) {
|
|
3
4
|
const match = markdown.match(/^\s{0,3}#{1,6}\s+(.+?)\s*#*\s*$/m);
|
|
4
5
|
return match?.[1]?.trim();
|
|
5
6
|
}
|
|
7
|
+
/** Extract a non-empty string `title:` from a leading front matter block, if any. */
|
|
8
|
+
function frontMatterTitle(markdown) {
|
|
9
|
+
const title = parseFrontMatter(markdown)?.data.title;
|
|
10
|
+
if (typeof title !== 'string') {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
const trimmed = title.trim();
|
|
14
|
+
return trimmed || undefined;
|
|
15
|
+
}
|
|
6
16
|
/** Derive the file base name, without extension, from a path string. */
|
|
7
17
|
export function fileBaseNameFromPath(path) {
|
|
8
18
|
const slashIndex = path.lastIndexOf('/');
|
|
9
19
|
const fileName = slashIndex >= 0 ? path.slice(slashIndex + 1) : path;
|
|
10
20
|
return fileName.replace(/\.[^.]+$/, '') || 'document';
|
|
11
21
|
}
|
|
12
|
-
/** Resolve a stable title fallback from markdown heading or filename. */
|
|
22
|
+
/** Resolve a stable title fallback from front matter `title:`, markdown heading, or filename. */
|
|
13
23
|
export function suggestedTitleFromMarkdown(markdown, fileBaseName) {
|
|
14
|
-
return firstMarkdownHeading(markdown) || fileBaseName;
|
|
24
|
+
return frontMatterTitle(markdown) || firstMarkdownHeading(markdown) || fileBaseName;
|
|
15
25
|
}
|
|
16
26
|
/** Resolve display title from manifest title with filename fallback. */
|
|
17
27
|
export function displayTitleFromManifest(manifestTitle, fileBaseName) {
|
package/dist/metadata.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata.js","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,MAAM,UAAU,oBAAoB,CAAC,QAAgB;IACnD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACjE,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;AAC5B,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACrE,OAAO,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC;AACxD,CAAC;AAED,
|
|
1
|
+
{"version":3,"file":"metadata.js","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,gFAAgF;AAChF,MAAM,UAAU,oBAAoB,CAAC,QAAgB;IACnD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACjE,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;AAC5B,CAAC;AAED,qFAAqF;AACrF,SAAS,gBAAgB,CAAC,QAAgB;IACxC,MAAM,KAAK,GAAG,gBAAgB,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC;IACrD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,OAAO,OAAO,IAAI,SAAS,CAAC;AAC9B,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACrE,OAAO,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC;AACxD,CAAC;AAED,iGAAiG;AACjG,MAAM,UAAU,0BAA0B,CAAC,QAAgB,EAAE,YAAoB;IAC/E,OAAO,gBAAgB,CAAC,QAAQ,CAAC,IAAI,oBAAoB,CAAC,QAAQ,CAAC,IAAI,YAAY,CAAC;AACtF,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,wBAAwB,CACtC,aAAiC,EACjC,YAAoB;IAEpB,MAAM,OAAO,GAAG,aAAa,EAAE,IAAI,EAAE,CAAC;IACtC,OAAO,OAAO,IAAI,YAAY,CAAC;AACjC,CAAC"}
|
package/dist/preview.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export type { MdzWorkspace, MdzWorkspaceAsset } from '@mdzip/core-js';
|
|
|
2
2
|
export * from './archive-utils.js';
|
|
3
3
|
export * from './browser.js';
|
|
4
4
|
export * from './diff.js';
|
|
5
|
+
export * from './front-matter.js';
|
|
5
6
|
export * from './metadata.js';
|
|
6
7
|
export * from './rendering.js';
|
|
7
8
|
export * from './theme.js';
|
package/dist/preview.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACtE,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACtE,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC"}
|
package/dist/preview.js
CHANGED
package/dist/preview.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.js","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"AACA,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"preview.js","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"AACA,cAAc,oBAAoB,CAAC;AACnC,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC"}
|
package/dist/rendering.d.ts
CHANGED
|
@@ -110,6 +110,19 @@ export interface MdzipMarkdownRenderExtension {
|
|
|
110
110
|
transformMarkdown?(markdown: string, context: MdzipMarkdownRenderContext): string | Promise<string>;
|
|
111
111
|
transformHtml?(html: string, context: MdzipMarkdownRenderContext): string | Promise<string>;
|
|
112
112
|
mount?(container: HTMLElement, context: MdzipMarkdownRenderContext): void | MdzipRenderHandle | Promise<void | MdzipRenderHandle>;
|
|
113
|
+
/**
|
|
114
|
+
* Marks a top-level block token as expensive enough to isolate into its own
|
|
115
|
+
* chunk for {@link groupTokensIntoChunks}, regardless of the char/token
|
|
116
|
+
* budget — e.g. a mermaid extension claiming its own ` ```mermaid ` fence.
|
|
117
|
+
* Without this, a token this extension will re-render on every mount
|
|
118
|
+
* (diagram layout, not just syntax highlighting) shares a chunk with
|
|
119
|
+
* whatever nearby text the budget happened to group it with; editing any
|
|
120
|
+
* of that unrelated text still invalidates the whole chunk's source key,
|
|
121
|
+
* forcing an expensive re-render the edit had nothing to do with — most
|
|
122
|
+
* visible on a short document that fits within one or two chunks anyway,
|
|
123
|
+
* where *every* edit ends up re-rendering the diagram.
|
|
124
|
+
*/
|
|
125
|
+
shouldIsolateChunk?(token: Token): boolean;
|
|
113
126
|
}
|
|
114
127
|
/**
|
|
115
128
|
* Context handed to entry renderers for the currently selected archive entry.
|
|
@@ -167,6 +180,29 @@ export interface MdzipRenderRequest {
|
|
|
167
180
|
export interface MdzipRenderResult {
|
|
168
181
|
html: string;
|
|
169
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Rendered heading ids carry this prefix (GitHub does the same) so a heading
|
|
185
|
+
* like "## Images" or "## Title" can never collide with a document/form
|
|
186
|
+
* property, which DOMPurify would otherwise strip the id for. Links written
|
|
187
|
+
* against the bare slug (`#images`) still resolve — see the preview click handler.
|
|
188
|
+
*/
|
|
189
|
+
export declare const MDZIP_HEADING_ID_PREFIX = "user-content-";
|
|
190
|
+
/**
|
|
191
|
+
* GitHub-style heading slug: lowercased, everything but letters, marks,
|
|
192
|
+
* numbers, `_`, `-` and spaces removed, spaces turned into hyphens.
|
|
193
|
+
*/
|
|
194
|
+
export declare function slugifyMdzipHeading(text: string): string;
|
|
195
|
+
/**
|
|
196
|
+
* Gives every heading token (including ones nested in blockquotes and lists)
|
|
197
|
+
* the id its rendered `<hN>` will carry, in document order, disambiguating
|
|
198
|
+
* repeats GitHub-style (`intro`, `intro-1`, `intro-2`). Has to run over the
|
|
199
|
+
* whole document's tokens before they are split into chunks — a chunk rendered
|
|
200
|
+
* on its own can't know how many earlier chunks already used a slug. A heading
|
|
201
|
+
* whose text slugifies to nothing (e.g. only punctuation) gets no id.
|
|
202
|
+
*/
|
|
203
|
+
export declare function assignMdzipHeadingIds(tokens: readonly Token[]): void;
|
|
204
|
+
/** The ids {@link assignMdzipHeadingIds} gave the headings in `tokens`, in order. */
|
|
205
|
+
export declare function collectMdzipHeadingIds(tokens: readonly Token[]): string[];
|
|
170
206
|
export declare const defaultSafeMarkdownRenderer: MdzipMarkdownRenderer;
|
|
171
207
|
export declare class MdzipRenderingService {
|
|
172
208
|
private readonly renderer;
|
|
@@ -223,7 +259,45 @@ export interface MdzipChunkOptions {
|
|
|
223
259
|
tokenCap?: number;
|
|
224
260
|
/** Approximate max raw markdown chars per chunk (via each token's `.raw.length`). Default 2000. */
|
|
225
261
|
charBudget?: number;
|
|
262
|
+
/**
|
|
263
|
+
* When a token matches, it's isolated into its own chunk regardless of the
|
|
264
|
+
* budget: any chunk-in-progress is closed first, the matching token forms
|
|
265
|
+
* a chunk by itself, and accumulation resumes fresh after it. Callers
|
|
266
|
+
* combine every registered extension's {@link MdzipMarkdownRenderExtension.shouldIsolateChunk}
|
|
267
|
+
* into one predicate — see that doc comment for why this matters.
|
|
268
|
+
*/
|
|
269
|
+
shouldIsolate?: (token: Token) => boolean;
|
|
270
|
+
/**
|
|
271
|
+
* When a token matches, any chunk-in-progress is closed first and the
|
|
272
|
+
* matching token starts a fresh one (unlike `shouldIsolate`, later tokens
|
|
273
|
+
* keep accumulating into that new chunk under the normal budget — the
|
|
274
|
+
* matching token doesn't get a chunk to itself). Used to keep unrelated
|
|
275
|
+
* sections from sharing a chunk: without this, a document short enough to
|
|
276
|
+
* fit several headings' worth of content under the budget lets an edit in
|
|
277
|
+
* one section's prose invalidate a sibling section's untouched heading and
|
|
278
|
+
* content too, tearing them out of the DOM and back in — a real, visible
|
|
279
|
+
* flash for content that never changed.
|
|
280
|
+
*/
|
|
281
|
+
shouldStartChunk?: (token: Token) => boolean;
|
|
226
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* True when a token's raw source embeds an image (Markdown `![]()` syntax or
|
|
285
|
+
* a raw `<img>` tag). Used to isolate the token into its own chunk: without
|
|
286
|
+
* this, a short document can put an image in the same budget-sized chunk as
|
|
287
|
+
* unrelated surrounding prose, so an edit anywhere in that prose tears the
|
|
288
|
+
* image out of the DOM and reinserts it — a real, visible flash, since the
|
|
289
|
+
* browser has to redo layout for a freshly-inserted `<img>` even when its
|
|
290
|
+
* source is a cache hit.
|
|
291
|
+
*/
|
|
292
|
+
export declare function tokenEmbedsImage(token: Token): boolean;
|
|
293
|
+
/**
|
|
294
|
+
* True for a heading token — used as {@link MdzipChunkOptions.shouldStartChunk}
|
|
295
|
+
* so a heading always begins a fresh chunk instead of merging with whatever
|
|
296
|
+
* content (and sibling headings) happened to precede it under the size
|
|
297
|
+
* budget. See that option's doc comment for why: it keeps an edit in one
|
|
298
|
+
* section from also tearing down an untouched sibling section.
|
|
299
|
+
*/
|
|
300
|
+
export declare function tokenIsHeading(token: Token): boolean;
|
|
227
301
|
/**
|
|
228
302
|
* Groups top-level block tokens from {@link MdzipRenderingService.tokenizeMarkdown}
|
|
229
303
|
* into chunks suitable for incremental rendering — a token-count cap and a
|
|
@@ -231,7 +305,26 @@ export interface MdzipChunkOptions {
|
|
|
231
305
|
* tiny tokens (e.g. a chat export's one-line-paragraph-per-message shape)
|
|
232
306
|
* doesn't end up with one chunk per token. A single token larger than the
|
|
233
307
|
* budget still gets its own chunk on its own (a chunk boundary can never
|
|
234
|
-
* split a token).
|
|
308
|
+
* split a token). `shouldIsolate` overrides the budget in one direction —
|
|
309
|
+
* forcing a matching token into its own chunk even when the budget would
|
|
310
|
+
* have happily grouped it with neighbors; `shouldStartChunk` overrides it in
|
|
311
|
+
* the other — forcing a boundary before a matching token even when the
|
|
312
|
+
* budget would have merged it with what came before.
|
|
235
313
|
*/
|
|
236
314
|
export declare function groupTokensIntoChunks(tokens: readonly Token[], options?: MdzipChunkOptions): Token[][];
|
|
315
|
+
/**
|
|
316
|
+
* Stable identity key for a chunk: the exact source text `marked` consumed
|
|
317
|
+
* for it (concatenation of each token's `.raw`). Two chunks with the same key
|
|
318
|
+
* are guaranteed to have identical source text, since `.raw` slices tile the
|
|
319
|
+
* whole input contiguously — safe to use as a plain string equality key (no
|
|
320
|
+
* hashing needed; chunks are budget-capped ~2000 chars, and a render involves
|
|
321
|
+
* at most a few dozen of them).
|
|
322
|
+
*
|
|
323
|
+
* Deliberately keys on SOURCE text only, never on rendered output: an
|
|
324
|
+
* extension's `transformHtml` can be side-effecting per call (e.g. the
|
|
325
|
+
* mermaid extension's per-call SVG id counter), so identical keys must never
|
|
326
|
+
* be treated as license to re-run rendering — only as license to skip it and
|
|
327
|
+
* reuse whatever is already mounted for that chunk.
|
|
328
|
+
*/
|
|
329
|
+
export declare function chunkSourceKey(chunk: readonly Token[]): string;
|
|
237
330
|
//# sourceMappingURL=rendering.d.ts.map
|
package/dist/rendering.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rendering.d.ts","sourceRoot":"","sources":["../src/rendering.ts"],"names":[],"mappings":"AAEA,OAAO,EAAU,KAAK,KAAK,EAAe,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAC;AAE1E,YAAY,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;AAClC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,EACvB,MAAM,gBAAgB,CAAC;AA4BxB;;;;;;;;;;GAUG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,gEAAgE;IAChE,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,yEAAyE;IACzE,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,sFAAsF;IACtF,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAsBD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,aAAa,GAAE,SAAS,yBAAyB,EAAO,GACvD,MAAM,CAER;AAED;;;;GAIG;AACH,MAAM,WAAW,0BAA0B;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,iBAAiB,CAAC;IAChC,WAAW,EAAE,OAAO,GAAG,MAAM,CAAC;IAC9B,IAAI,EAAE,kBAAkB,CAAC;IACzB,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAC;IAC7B;;;;OAIG;IACH,aAAa,CAAC,EAAE,qBAAqB,CAAC;IACtC,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;;;;OAOG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,0BAA0B,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACzF;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;CACpC;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,CAAC,OAAO,EAAE,0BAA0B,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,4BAA4B;IAC3C,IAAI,EAAE,MAAM,CAAC;IAEb;;;;OAIG;IACH,QAAQ,CAAC,EAAE,yBAAyB,CAAC;IAErC,iBAAiB,CAAC,CAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,0BAA0B,GAClC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE5B,aAAa,CAAC,CACZ,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,0BAA0B,GAClC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE5B,KAAK,CAAC,CACJ,SAAS,EAAE,WAAW,EACtB,OAAO,EAAE,0BAA0B,GAClC,IAAI,GAAG,iBAAiB,GAAG,OAAO,CAAC,IAAI,GAAG,iBAAiB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"rendering.d.ts","sourceRoot":"","sources":["../src/rendering.ts"],"names":[],"mappings":"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"}
|
package/dist/rendering.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import DOMPurify from 'dompurify';
|
|
2
|
-
import hljs from 'highlight.js';
|
|
2
|
+
import hljs from './highlight-core.js';
|
|
3
3
|
import { Marked } from 'marked';
|
|
4
4
|
// In browsers the dompurify default export is already bound to the global
|
|
5
5
|
// window. In Node it is an unbound factory, so bind it to a host-provided DOM
|
|
@@ -67,8 +67,105 @@ export function mdzipExtensionMatcher(...extensions) {
|
|
|
67
67
|
return wanted.some((ext) => lower.endsWith(ext));
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Rendered heading ids carry this prefix (GitHub does the same) so a heading
|
|
72
|
+
* like "## Images" or "## Title" can never collide with a document/form
|
|
73
|
+
* property, which DOMPurify would otherwise strip the id for. Links written
|
|
74
|
+
* against the bare slug (`#images`) still resolve — see the preview click handler.
|
|
75
|
+
*/
|
|
76
|
+
export const MDZIP_HEADING_ID_PREFIX = 'user-content-';
|
|
77
|
+
function decodeBasicEntities(text) {
|
|
78
|
+
return text
|
|
79
|
+
.replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCodePoint(parseInt(hex, 16)))
|
|
80
|
+
.replace(/&#(\d+);/g, (_, dec) => String.fromCodePoint(parseInt(dec, 10)))
|
|
81
|
+
.replace(/"/g, '"')
|
|
82
|
+
.replace(/'|'/g, "'")
|
|
83
|
+
.replace(/</g, '<')
|
|
84
|
+
.replace(/>/g, '>')
|
|
85
|
+
.replace(/&/g, '&')
|
|
86
|
+
// Any other named entity (© etc.) decodes to a symbol the slug
|
|
87
|
+
// pass would strip anyway.
|
|
88
|
+
.replace(/&[a-z][a-z0-9]*;/gi, '');
|
|
89
|
+
}
|
|
90
|
+
function inlineTokensPlainText(tokens) {
|
|
91
|
+
let text = '';
|
|
92
|
+
for (const token of tokens ?? []) {
|
|
93
|
+
const inline = token;
|
|
94
|
+
if (inline.type === 'br') {
|
|
95
|
+
text += ' ';
|
|
96
|
+
}
|
|
97
|
+
else if (inline.type === 'html') {
|
|
98
|
+
// Tag markers only — their inner text arrives as sibling text tokens.
|
|
99
|
+
}
|
|
100
|
+
else if (inline.tokens) {
|
|
101
|
+
text += inlineTokensPlainText(inline.tokens);
|
|
102
|
+
}
|
|
103
|
+
else if (typeof inline.text === 'string') {
|
|
104
|
+
text += inline.text;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return decodeBasicEntities(text);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* GitHub-style heading slug: lowercased, everything but letters, marks,
|
|
111
|
+
* numbers, `_`, `-` and spaces removed, spaces turned into hyphens.
|
|
112
|
+
*/
|
|
113
|
+
export function slugifyMdzipHeading(text) {
|
|
114
|
+
return text.toLowerCase().replace(/[^\p{L}\p{M}\p{N}\p{Pc}\- ]/gu, '').replace(/ /g, '-');
|
|
115
|
+
}
|
|
116
|
+
function forEachHeadingToken(tokens, visit) {
|
|
117
|
+
for (const token of tokens) {
|
|
118
|
+
if (token.type === 'heading') {
|
|
119
|
+
visit(token);
|
|
120
|
+
}
|
|
121
|
+
else if (token.type === 'blockquote' || token.type === 'list' || token.type === 'list_item') {
|
|
122
|
+
const container = token;
|
|
123
|
+
forEachHeadingToken(container.tokens ?? container.items ?? [], visit);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Gives every heading token (including ones nested in blockquotes and lists)
|
|
129
|
+
* the id its rendered `<hN>` will carry, in document order, disambiguating
|
|
130
|
+
* repeats GitHub-style (`intro`, `intro-1`, `intro-2`). Has to run over the
|
|
131
|
+
* whole document's tokens before they are split into chunks — a chunk rendered
|
|
132
|
+
* on its own can't know how many earlier chunks already used a slug. A heading
|
|
133
|
+
* whose text slugifies to nothing (e.g. only punctuation) gets no id.
|
|
134
|
+
*/
|
|
135
|
+
export function assignMdzipHeadingIds(tokens) {
|
|
136
|
+
const used = new Map();
|
|
137
|
+
forEachHeadingToken(tokens, (token) => {
|
|
138
|
+
const base = slugifyMdzipHeading(inlineTokensPlainText(token.tokens));
|
|
139
|
+
if (!base) {
|
|
140
|
+
delete token.mdzipAnchorId;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
let candidate = base;
|
|
144
|
+
while (used.has(candidate)) {
|
|
145
|
+
const next = (used.get(base) ?? 0) + 1;
|
|
146
|
+
used.set(base, next);
|
|
147
|
+
candidate = `${base}-${next}`;
|
|
148
|
+
}
|
|
149
|
+
used.set(candidate, 0);
|
|
150
|
+
token.mdzipAnchorId = candidate;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/** The ids {@link assignMdzipHeadingIds} gave the headings in `tokens`, in order. */
|
|
154
|
+
export function collectMdzipHeadingIds(tokens) {
|
|
155
|
+
const ids = [];
|
|
156
|
+
forEachHeadingToken(tokens, (token) => {
|
|
157
|
+
if (token.mdzipAnchorId) {
|
|
158
|
+
ids.push(token.mdzipAnchorId);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
return ids;
|
|
162
|
+
}
|
|
70
163
|
const marked = new Marked({
|
|
71
164
|
renderer: {
|
|
165
|
+
heading(token) {
|
|
166
|
+
const id = token.mdzipAnchorId ? ` id="${MDZIP_HEADING_ID_PREFIX}${escapeHtml(token.mdzipAnchorId)}"` : '';
|
|
167
|
+
return `<h${token.depth}${id}>${this.parser.parseInline(token.tokens)}</h${token.depth}>\n`;
|
|
168
|
+
},
|
|
72
169
|
code(token) {
|
|
73
170
|
const requestedLanguage = token.lang || '';
|
|
74
171
|
const language = requestedLanguage === 'vue' ? 'html' : requestedLanguage;
|
|
@@ -106,7 +203,9 @@ const marked = new Marked({
|
|
|
106
203
|
}
|
|
107
204
|
});
|
|
108
205
|
function renderDefaultMarkdownUnsanitized(markdown) {
|
|
109
|
-
const
|
|
206
|
+
const tokens = marked.lexer(markdown);
|
|
207
|
+
assignMdzipHeadingIds(tokens);
|
|
208
|
+
const rendered = marked.parser(tokens);
|
|
110
209
|
return typeof rendered === 'string' ? rendered : escapeHtml(markdown);
|
|
111
210
|
}
|
|
112
211
|
export const defaultSafeMarkdownRenderer = {
|
|
@@ -228,10 +327,14 @@ export class MdzipRenderingService {
|
|
|
228
327
|
if (context.signal.aborted) {
|
|
229
328
|
throw createAbortError();
|
|
230
329
|
}
|
|
231
|
-
|
|
330
|
+
const tokens = marked.lexer(resolved);
|
|
331
|
+
assignMdzipHeadingIds(tokens);
|
|
332
|
+
return tokens;
|
|
232
333
|
});
|
|
233
334
|
}
|
|
234
|
-
|
|
335
|
+
const tokens = marked.lexer(value);
|
|
336
|
+
assignMdzipHeadingIds(tokens);
|
|
337
|
+
return tokens;
|
|
235
338
|
}
|
|
236
339
|
/**
|
|
237
340
|
* Renders a subset of tokens from {@link tokenizeMarkdown} through
|
|
@@ -261,6 +364,31 @@ export class MdzipRenderingService {
|
|
|
261
364
|
}
|
|
262
365
|
const DEFAULT_CHUNK_TOKEN_CAP = 40;
|
|
263
366
|
const DEFAULT_CHUNK_CHAR_BUDGET = 2000;
|
|
367
|
+
const IMAGE_MARKDOWN_PATTERN = /!\[[^\]]*\]\([^)]*\)/;
|
|
368
|
+
const IMAGE_HTML_PATTERN = /<img\b/i;
|
|
369
|
+
/**
|
|
370
|
+
* True when a token's raw source embeds an image (Markdown `![]()` syntax or
|
|
371
|
+
* a raw `<img>` tag). Used to isolate the token into its own chunk: without
|
|
372
|
+
* this, a short document can put an image in the same budget-sized chunk as
|
|
373
|
+
* unrelated surrounding prose, so an edit anywhere in that prose tears the
|
|
374
|
+
* image out of the DOM and reinserts it — a real, visible flash, since the
|
|
375
|
+
* browser has to redo layout for a freshly-inserted `<img>` even when its
|
|
376
|
+
* source is a cache hit.
|
|
377
|
+
*/
|
|
378
|
+
export function tokenEmbedsImage(token) {
|
|
379
|
+
const raw = token.raw ?? '';
|
|
380
|
+
return IMAGE_MARKDOWN_PATTERN.test(raw) || IMAGE_HTML_PATTERN.test(raw);
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* True for a heading token — used as {@link MdzipChunkOptions.shouldStartChunk}
|
|
384
|
+
* so a heading always begins a fresh chunk instead of merging with whatever
|
|
385
|
+
* content (and sibling headings) happened to precede it under the size
|
|
386
|
+
* budget. See that option's doc comment for why: it keeps an edit in one
|
|
387
|
+
* section from also tearing down an untouched sibling section.
|
|
388
|
+
*/
|
|
389
|
+
export function tokenIsHeading(token) {
|
|
390
|
+
return token.type === 'heading';
|
|
391
|
+
}
|
|
264
392
|
/**
|
|
265
393
|
* Groups top-level block tokens from {@link MdzipRenderingService.tokenizeMarkdown}
|
|
266
394
|
* into chunks suitable for incremental rendering — a token-count cap and a
|
|
@@ -268,7 +396,11 @@ const DEFAULT_CHUNK_CHAR_BUDGET = 2000;
|
|
|
268
396
|
* tiny tokens (e.g. a chat export's one-line-paragraph-per-message shape)
|
|
269
397
|
* doesn't end up with one chunk per token. A single token larger than the
|
|
270
398
|
* budget still gets its own chunk on its own (a chunk boundary can never
|
|
271
|
-
* split a token).
|
|
399
|
+
* split a token). `shouldIsolate` overrides the budget in one direction —
|
|
400
|
+
* forcing a matching token into its own chunk even when the budget would
|
|
401
|
+
* have happily grouped it with neighbors; `shouldStartChunk` overrides it in
|
|
402
|
+
* the other — forcing a boundary before a matching token even when the
|
|
403
|
+
* budget would have merged it with what came before.
|
|
272
404
|
*/
|
|
273
405
|
export function groupTokensIntoChunks(tokens, options = {}) {
|
|
274
406
|
const tokenCap = options.tokenCap ?? DEFAULT_CHUNK_TOKEN_CAP;
|
|
@@ -277,6 +409,20 @@ export function groupTokensIntoChunks(tokens, options = {}) {
|
|
|
277
409
|
let current = [];
|
|
278
410
|
let currentChars = 0;
|
|
279
411
|
for (const token of tokens) {
|
|
412
|
+
if (options.shouldIsolate?.(token)) {
|
|
413
|
+
if (current.length > 0) {
|
|
414
|
+
chunks.push(current);
|
|
415
|
+
current = [];
|
|
416
|
+
currentChars = 0;
|
|
417
|
+
}
|
|
418
|
+
chunks.push([token]);
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (options.shouldStartChunk?.(token) && current.length > 0) {
|
|
422
|
+
chunks.push(current);
|
|
423
|
+
current = [];
|
|
424
|
+
currentChars = 0;
|
|
425
|
+
}
|
|
280
426
|
current.push(token);
|
|
281
427
|
currentChars += token.raw?.length ?? 0;
|
|
282
428
|
if (current.length >= tokenCap || currentChars >= charBudget) {
|
|
@@ -290,6 +436,28 @@ export function groupTokensIntoChunks(tokens, options = {}) {
|
|
|
290
436
|
}
|
|
291
437
|
return chunks;
|
|
292
438
|
}
|
|
439
|
+
/**
|
|
440
|
+
* Stable identity key for a chunk: the exact source text `marked` consumed
|
|
441
|
+
* for it (concatenation of each token's `.raw`). Two chunks with the same key
|
|
442
|
+
* are guaranteed to have identical source text, since `.raw` slices tile the
|
|
443
|
+
* whole input contiguously — safe to use as a plain string equality key (no
|
|
444
|
+
* hashing needed; chunks are budget-capped ~2000 chars, and a render involves
|
|
445
|
+
* at most a few dozen of them).
|
|
446
|
+
*
|
|
447
|
+
* Deliberately keys on SOURCE text only, never on rendered output: an
|
|
448
|
+
* extension's `transformHtml` can be side-effecting per call (e.g. the
|
|
449
|
+
* mermaid extension's per-call SVG id counter), so identical keys must never
|
|
450
|
+
* be treated as license to re-run rendering — only as license to skip it and
|
|
451
|
+
* reuse whatever is already mounted for that chunk.
|
|
452
|
+
*/
|
|
453
|
+
export function chunkSourceKey(chunk) {
|
|
454
|
+
const raw = chunk.map((token) => token.raw ?? '').join('');
|
|
455
|
+
// A heading's id depends on the whole document (an earlier duplicate
|
|
456
|
+
// shifts `intro` to `intro-1`), not just this chunk's own text — so a chunk
|
|
457
|
+
// whose source is unchanged but whose ids moved must not be reused as-is.
|
|
458
|
+
const ids = collectMdzipHeadingIds(chunk);
|
|
459
|
+
return ids.length > 0 ? `${raw}\u0000${ids.join('\u0001')}` : raw;
|
|
460
|
+
}
|
|
293
461
|
function rewriteAssetSources(markdown, resolver) {
|
|
294
462
|
return markdown.replace(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/g, (match, alt, rawPath, title) => {
|
|
295
463
|
const path = rawPath.replace(/^<|>$/g, '');
|