@scribdown/markdown-renderer 0.1.0 → 0.2.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/dist/index.js CHANGED
@@ -1,47 +1,277 @@
1
1
  // src/index.ts
2
- import {
3
- CONTENT_WIDTH_STORAGE_KEY,
4
- PROJECT_HOMEPAGE_URL,
5
- SCRIBDOWN_MARKDOWN_CLASS_NAME,
6
- SCRIBDOWN_TOOLBAR_BTN_CLASS_NAME,
7
- SCRIBDOWN_TOOLBAR_CLASS_NAME,
8
- SCRIBDOWN_TOOLBAR_MENU_CLASS_NAME,
9
- SCRIBDOWN_TOOLBAR_MENU_GROUP_CLASS_NAME,
10
- SCRIBDOWN_TOOLBAR_MENU_ITEM_CLASS_NAME,
11
- SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_CLASS_NAME,
12
- SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME,
13
- SCRIBDOWN_TOOLBAR_CURRENT_CLASS_NAME,
14
- SCRIBDOWN_TOC_HOST_CLASS_NAME,
15
- SCRIBDOWN_CONTENT_AREA_CLASS_NAME,
16
- SCRIBDOWN_CONTENT_SCROLL_CLASS_NAME,
17
- SCRIBDOWN_THIN_SCROLLBAR_CLASS_NAME,
18
- SOURCE_LINE_DATA_ATTRIBUTE
19
- } from "@scribdown/shared";
20
- import DOMPurify from "dompurify";
21
2
  import rehypeRaw from "rehype-raw";
22
- import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
3
+ import rehypeSanitize from "rehype-sanitize";
23
4
  import rehypeStringify from "rehype-stringify";
5
+ import remarkFrontmatter from "remark-frontmatter";
24
6
  import remarkGfm from "remark-gfm";
25
7
  import remarkParse from "remark-parse";
26
8
  import remarkRehype from "remark-rehype";
9
+ import { unified } from "unified";
10
+
11
+ // src/code/code-block-chrome.ts
12
+ import {
13
+ CODE_BLOCK_BODY_CLASS_NAME,
14
+ CODE_BLOCK_CHROME_CLASS_NAME,
15
+ CODE_BLOCK_CLASS_NAME,
16
+ CODE_BLOCK_COPY_CLASS_NAME,
17
+ CODE_BLOCK_COPY_ICON_CHECK_CLASS_NAME,
18
+ CODE_BLOCK_COPY_ICON_CLASS_NAME,
19
+ CODE_BLOCK_COPY_ICON_COPY_CLASS_NAME,
20
+ CODE_BLOCK_GUTTER_CLASS_NAME,
21
+ CODE_BLOCK_GUTTER_LINE_CLASS_NAME,
22
+ CODE_BLOCK_LANG_CLASS_NAME,
23
+ CODE_BLOCK_LINE_CLASS_NAME,
24
+ SOURCE_LINE_DATA_ATTRIBUTE
25
+ } from "@scribdown/shared";
26
+ var CODE_BLOCK_EMPTY_LINE_PLACEHOLDER = "\u200B";
27
+ var CODE_BLOCK_HYDRATED_DATA_KEY = "scribdownCodeBlockHydrated";
28
+ var CODE_LANGUAGE_DISPLAY_NAMES = {
29
+ bash: "Bash",
30
+ c: "C",
31
+ cpp: "C++",
32
+ csharp: "C#",
33
+ css: "CSS",
34
+ diff: "Diff",
35
+ go: "Go",
36
+ graphql: "GraphQL",
37
+ html: "HTML",
38
+ java: "Java",
39
+ javascript: "JavaScript",
40
+ js: "JavaScript",
41
+ json: "JSON",
42
+ jsx: "JSX",
43
+ kotlin: "Kotlin",
44
+ less: "Less",
45
+ markdown: "Markdown",
46
+ md: "Markdown",
47
+ php: "PHP",
48
+ python: "Python",
49
+ py: "Python",
50
+ ruby: "Ruby",
51
+ rust: "Rust",
52
+ rs: "Rust",
53
+ sass: "Sass",
54
+ scss: "SCSS",
55
+ sh: "Shell",
56
+ shell: "Shell",
57
+ sql: "SQL",
58
+ swift: "Swift",
59
+ toml: "TOML",
60
+ ts: "TypeScript",
61
+ tsx: "TSX",
62
+ typescript: "TypeScript",
63
+ vue: "Vue",
64
+ xml: "XML",
65
+ yaml: "YAML",
66
+ yml: "YAML",
67
+ zsh: "Zsh"
68
+ };
69
+ var CODE_LANGUAGE_DEFAULT_LABEL = "Text";
70
+ var CODE_BLOCK_COPY_RESTORE_DELAY_MS = 1600;
71
+ var CODE_BLOCK_COPY_ARIA_LABEL = "\u590D\u5236\u4EE3\u7801";
72
+ var CODE_BLOCK_COPY_ARIA_LABEL_COPIED = "\u5DF2\u590D\u5236";
73
+ function hydrateCodeBlocks(rootElement) {
74
+ const preElements = rootElement.querySelectorAll("pre");
75
+ preElements.forEach(decorateCodeBlock);
76
+ }
77
+ function decorateCodeBlock(preElement) {
78
+ if (preElement.parentElement?.classList.contains(CODE_BLOCK_CLASS_NAME)) {
79
+ return;
80
+ }
81
+ if (preElement.dataset[CODE_BLOCK_HYDRATED_DATA_KEY] === "true") {
82
+ return;
83
+ }
84
+ const codeElement = preElement.querySelector(":scope > code");
85
+ if (!codeElement) {
86
+ return;
87
+ }
88
+ preElement.dataset[CODE_BLOCK_HYDRATED_DATA_KEY] = "true";
89
+ const ownerDocument = preElement.ownerDocument;
90
+ const languageId = resolveCodeBlockLanguageId(codeElement);
91
+ const languageLabel = resolveCodeBlockLanguageLabel(languageId);
92
+ const originalCodeText = codeElement.textContent ?? "";
93
+ const codeLineCount = rewriteCodeBlockLines(codeElement, ownerDocument);
94
+ const chromeElement = ownerDocument.createElement("div");
95
+ chromeElement.className = CODE_BLOCK_CHROME_CLASS_NAME;
96
+ const langElement = ownerDocument.createElement("span");
97
+ langElement.className = CODE_BLOCK_LANG_CLASS_NAME;
98
+ langElement.textContent = languageLabel;
99
+ chromeElement.append(langElement);
100
+ const copyButtonElement = createCodeBlockCopyButton(ownerDocument);
101
+ chromeElement.append(copyButtonElement);
102
+ const figureElement = ownerDocument.createElement("figure");
103
+ figureElement.className = CODE_BLOCK_CLASS_NAME;
104
+ figureElement.dataset.scribdownCodeLang = languageId;
105
+ const bodyElement = ownerDocument.createElement("div");
106
+ bodyElement.className = CODE_BLOCK_BODY_CLASS_NAME;
107
+ const gutterElement = createCodeBlockLineNumberGutter(ownerDocument, codeLineCount);
108
+ preElement.replaceWith(figureElement);
109
+ bodyElement.append(gutterElement, preElement);
110
+ figureElement.append(chromeElement, bodyElement);
111
+ const codeBlockSourceLine = codeElement.getAttribute(SOURCE_LINE_DATA_ATTRIBUTE);
112
+ if (codeBlockSourceLine !== null) {
113
+ figureElement.setAttribute(SOURCE_LINE_DATA_ATTRIBUTE, codeBlockSourceLine);
114
+ codeElement.removeAttribute(SOURCE_LINE_DATA_ATTRIBUTE);
115
+ }
116
+ copyButtonElement.dataset.scribdownCodeSource = originalCodeText;
117
+ copyButtonElement.addEventListener("click", handleCodeBlockCopyClick);
118
+ }
119
+ function resolveCodeBlockLanguageId(codeElement) {
120
+ const languageClassName = Array.from(codeElement.classList).find(
121
+ (className) => className.startsWith("language-")
122
+ );
123
+ if (!languageClassName) {
124
+ return "";
125
+ }
126
+ return languageClassName.slice("language-".length).toLowerCase();
127
+ }
128
+ function resolveCodeBlockLanguageLabel(languageId) {
129
+ if (!languageId) {
130
+ return CODE_LANGUAGE_DEFAULT_LABEL;
131
+ }
132
+ const mappedLabel = CODE_LANGUAGE_DISPLAY_NAMES[languageId];
133
+ if (mappedLabel) {
134
+ return mappedLabel;
135
+ }
136
+ return languageId.charAt(0).toUpperCase() + languageId.slice(1);
137
+ }
138
+ function rewriteCodeBlockLines(codeElement, ownerDocument) {
139
+ const shikiLineElements = codeElement.querySelectorAll(":scope > span.line");
140
+ if (shikiLineElements.length > 0) {
141
+ const shikiLineNodes = Array.from(shikiLineElements);
142
+ shikiLineElements.forEach((lineElement) => {
143
+ lineElement.classList.add(CODE_BLOCK_LINE_CLASS_NAME);
144
+ if (lineElement.textContent === "") {
145
+ lineElement.textContent = CODE_BLOCK_EMPTY_LINE_PLACEHOLDER;
146
+ }
147
+ });
148
+ codeElement.replaceChildren(...shikiLineNodes);
149
+ return shikiLineNodes.length;
150
+ }
151
+ const codeText = codeElement.textContent ?? "";
152
+ const trimmedCodeText = codeText.replace(/\n+$/u, "");
153
+ const codeLines = trimmedCodeText.split("\n");
154
+ while (codeElement.firstChild) {
155
+ codeElement.removeChild(codeElement.firstChild);
156
+ }
157
+ codeLines.forEach((lineText) => {
158
+ const lineElement = ownerDocument.createElement("span");
159
+ lineElement.className = CODE_BLOCK_LINE_CLASS_NAME;
160
+ if (lineText.length === 0) {
161
+ lineElement.textContent = CODE_BLOCK_EMPTY_LINE_PLACEHOLDER;
162
+ } else {
163
+ lineElement.textContent = lineText;
164
+ }
165
+ codeElement.append(lineElement);
166
+ });
167
+ return codeLines.length;
168
+ }
169
+ function createCodeBlockLineNumberGutter(ownerDocument, lineCount) {
170
+ const gutterElement = ownerDocument.createElement("div");
171
+ gutterElement.className = CODE_BLOCK_GUTTER_CLASS_NAME;
172
+ for (let lineIndex = 0; lineIndex < lineCount; lineIndex += 1) {
173
+ const lineNumberText = String(lineIndex + 1);
174
+ const lineNumberElement = ownerDocument.createElement("span");
175
+ lineNumberElement.className = CODE_BLOCK_GUTTER_LINE_CLASS_NAME;
176
+ lineNumberElement.textContent = lineNumberText;
177
+ gutterElement.append(lineNumberElement);
178
+ }
179
+ return gutterElement;
180
+ }
181
+ var CODE_BLOCK_COPY_ICON_COPY_PATHS = '<path d="M8.4 6.8 C10.4 6.5 12.6 6.7 14.6 6.6 C16 6.6 16.5 7.2 16.6 8.6 C16.7 10.8 16.6 13.2 16.6 15.4 C16.5 16.6 15.8 17.2 14.6 17.2 C12.4 17.3 10.2 17.2 8 17.2 C6.6 17.2 6.2 16.6 6.2 15.4 C6.2 13.2 6.2 11 6.2 8.8 C6.2 7.4 6.8 6.8 8.4 6.8 Z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round"/><path d="M8.6 7.4 C10.6 7.2 12.6 7.4 14.4 7.3" fill="none" stroke="currentColor" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" opacity="0.42"/><path d="M3.6 12.8 C3.4 11.0 3.4 9.0 3.5 7.0 C3.5 5.0 3.4 3.6 5.2 3.4 C7.2 3.2 9.2 3.4 11.2 3.4 C12.6 3.4 13.2 4.0 13.3 5.0" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round"/><path d="M4.4 12.4 C4.2 10.6 4.2 8.8 4.2 7.0" fill="none" stroke="currentColor" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" opacity="0.42"/>';
182
+ var CODE_BLOCK_COPY_ICON_CHECK_PATHS = '<path d="M4.2 10.6 C5.6 11.8 6.8 13.2 8.2 14.6 C10.4 11.6 13.0 8.6 16.0 5.6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" stroke-linecap="round"/><path d="M4.6 11.4 C5.6 12.4 6.8 13.6 7.8 14.6 C10.2 12.2 12.6 9.6 15.2 7.0" fill="none" stroke="currentColor" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" opacity="0.42"/>';
183
+ function createCodeBlockCopyButton(ownerDocument) {
184
+ const copyButtonElement = ownerDocument.createElement("button");
185
+ copyButtonElement.type = "button";
186
+ copyButtonElement.className = CODE_BLOCK_COPY_CLASS_NAME;
187
+ copyButtonElement.setAttribute("aria-label", CODE_BLOCK_COPY_ARIA_LABEL);
188
+ const copyIconElement = ownerDocument.createElementNS("http://www.w3.org/2000/svg", "svg");
189
+ copyIconElement.setAttribute(
190
+ "class",
191
+ `${CODE_BLOCK_COPY_ICON_CLASS_NAME} ${CODE_BLOCK_COPY_ICON_COPY_CLASS_NAME}`
192
+ );
193
+ copyIconElement.setAttribute("viewBox", "0 0 20 20");
194
+ copyIconElement.setAttribute("aria-hidden", "true");
195
+ copyIconElement.setAttribute("focusable", "false");
196
+ copyIconElement.innerHTML = CODE_BLOCK_COPY_ICON_COPY_PATHS;
197
+ copyButtonElement.append(copyIconElement);
198
+ const checkIconElement = ownerDocument.createElementNS("http://www.w3.org/2000/svg", "svg");
199
+ checkIconElement.setAttribute(
200
+ "class",
201
+ `${CODE_BLOCK_COPY_ICON_CLASS_NAME} ${CODE_BLOCK_COPY_ICON_CHECK_CLASS_NAME}`
202
+ );
203
+ checkIconElement.setAttribute("viewBox", "0 0 20 20");
204
+ checkIconElement.setAttribute("aria-hidden", "true");
205
+ checkIconElement.setAttribute("focusable", "false");
206
+ checkIconElement.innerHTML = CODE_BLOCK_COPY_ICON_CHECK_PATHS;
207
+ copyButtonElement.append(checkIconElement);
208
+ return copyButtonElement;
209
+ }
210
+ function handleCodeBlockCopyClick(event) {
211
+ const copyButtonElement = event.currentTarget;
212
+ const codeText = copyButtonElement.dataset.scribdownCodeSource ?? "";
213
+ void writeCodeBlockTextToClipboard(codeText).then((isSucceeded) => {
214
+ if (isSucceeded) {
215
+ flashCodeBlockCopyButton(copyButtonElement);
216
+ }
217
+ });
218
+ }
219
+ async function writeCodeBlockTextToClipboard(codeText) {
220
+ if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
221
+ try {
222
+ await navigator.clipboard.writeText(codeText);
223
+ return true;
224
+ } catch {
225
+ }
226
+ }
227
+ if (typeof document === "undefined") {
228
+ return false;
229
+ }
230
+ const fallbackTextarea = document.createElement("textarea");
231
+ fallbackTextarea.value = codeText;
232
+ fallbackTextarea.setAttribute("readonly", "");
233
+ fallbackTextarea.style.position = "fixed";
234
+ fallbackTextarea.style.opacity = "0";
235
+ fallbackTextarea.style.pointerEvents = "none";
236
+ document.body.append(fallbackTextarea);
237
+ fallbackTextarea.select();
238
+ const isSucceeded = document.execCommand("copy");
239
+ fallbackTextarea.remove();
240
+ return isSucceeded;
241
+ }
242
+ function flashCodeBlockCopyButton(copyButtonElement) {
243
+ copyButtonElement.dataset.scribdownCodeCopied = "true";
244
+ copyButtonElement.setAttribute("aria-label", CODE_BLOCK_COPY_ARIA_LABEL_COPIED);
245
+ window.setTimeout(() => {
246
+ delete copyButtonElement.dataset.scribdownCodeCopied;
247
+ copyButtonElement.setAttribute("aria-label", CODE_BLOCK_COPY_ARIA_LABEL);
248
+ }, CODE_BLOCK_COPY_RESTORE_DELAY_MS);
249
+ }
250
+
251
+ // src/code/code-highlight.ts
27
252
  import githubLightTheme from "@shikijs/themes/github-light";
28
253
  import { createHighlighterCore } from "shiki/core";
29
254
  import { createOnigurumaEngine } from "shiki/engine/oniguruma";
30
- import { unified } from "unified";
31
255
 
32
- // src/code-highlighter-langs.ts
33
- var CODE_HIGHLIGHTER_EAGER_LOADERS = [
34
- () => import("@shikijs/langs/bash"),
35
- () => import("@shikijs/langs/css"),
36
- () => import("@shikijs/langs/diff"),
37
- () => import("@shikijs/langs/html"),
38
- () => import("@shikijs/langs/javascript"),
39
- () => import("@shikijs/langs/json"),
40
- () => import("@shikijs/langs/markdown"),
41
- () => import("@shikijs/langs/typescript"),
42
- () => import("@shikijs/langs/yaml")
43
- ];
256
+ // src/code/code-highlighter-langs.ts
257
+ var CODE_HIGHLIGHTER_EAGER_LOADERS = [];
44
258
  var CODE_HIGHLIGHTER_LAZY_LANGS = {
259
+ // 高频基础语言
260
+ bash: () => import("@shikijs/langs/bash"),
261
+ sh: () => import("@shikijs/langs/bash"),
262
+ shell: () => import("@shikijs/langs/bash"),
263
+ css: () => import("@shikijs/langs/css"),
264
+ diff: () => import("@shikijs/langs/diff"),
265
+ html: () => import("@shikijs/langs/html"),
266
+ js: () => import("@shikijs/langs/javascript"),
267
+ javascript: () => import("@shikijs/langs/javascript"),
268
+ json: () => import("@shikijs/langs/json"),
269
+ markdown: () => import("@shikijs/langs/markdown"),
270
+ md: () => import("@shikijs/langs/markdown"),
271
+ ts: () => import("@shikijs/langs/typescript"),
272
+ typescript: () => import("@shikijs/langs/typescript"),
273
+ yaml: () => import("@shikijs/langs/yaml"),
274
+ yml: () => import("@shikijs/langs/yaml"),
45
275
  // 前端框架与扩展
46
276
  jsx: () => import("@shikijs/langs/jsx"),
47
277
  tsx: () => import("@shikijs/langs/tsx"),
@@ -138,540 +368,59 @@ var CODE_HIGHLIGHTER_LAZY_LANGS = {
138
368
  wat: () => import("@shikijs/langs/wasm")
139
369
  };
140
370
 
141
- // src/index.ts
142
- var SOURCE_LINE_HAST_PROPERTY = SOURCE_LINE_DATA_ATTRIBUTE.replace(
143
- /-([a-z])/gu,
144
- (_match, letter) => letter.toUpperCase()
145
- );
146
- var TOC_MARKER_PATTERN = /^\s*\[toc]\s*$/i;
147
- var TOC_CLASS_NAME = "scribdown-toc";
148
- var FRAME_CLASS_NAME = "scribdown-frame";
149
- var DETAILS_CLASS_NAME = "scribdown-details";
150
- var TOC_SUMMARY_CLASS_NAME = "scribdown-toc-summary";
151
- var TOC_NAV_CLASS_NAME = "scribdown-toc-nav";
152
- var TOC_LIST_CLASS_NAME = "scribdown-toc-list";
153
- var TOC_LIST_NESTED_CLASS_NAME = "scribdown-toc-list--nested";
154
- var TOC_ITEM_CLASS_PREFIX = "scribdown-toc-item";
155
- var TOC_ITEM_BRANCH_CLASS_NAME = "scribdown-toc-item--branch";
156
- var TOC_ITEM_COLLAPSED_CLASS_NAME = "scribdown-toc-item--collapsed";
157
- var TOC_LINK_CLASS_NAME = "scribdown-toc-link";
158
- var TOC_LINK_ACTIVE_CLASS_NAME = "scribdown-toc-link--active";
159
- var TOC_TOGGLE_CLASS_NAME = "scribdown-toc-toggle";
160
- var TOC_TOGGLE_ARIA_LABEL = "\u5C55\u5F00\u6216\u6298\u53E0\u5B50\u76EE\u5F55";
161
- var TOC_TOGGLE_HYDRATED_DATA_KEY = "scribdownTocToggleHydrated";
162
- var TOC_LINK_HYDRATED_DATA_KEY = "scribdownTocLinkHydrated";
163
- var TOC_ARIA_LABEL = "\u76EE\u5F55";
164
- var TOC_SUMMARY_TEXT = "\u76EE\u5F55";
165
- var EMPTY_HEADING_SLUG_PREFIX = "section";
166
- var HEADING_MARK_CLASS_NAME = "scribdown-heading-mark";
167
- var IMAGE_FIGURE_CLASS_NAME = "scribdown-image-figure";
168
- var IMAGE_FRAME_CLASS_NAME = "scribdown-image-frame";
169
- var IMAGE_ELEMENT_CLASS_NAME = "scribdown-image";
170
- var IMAGE_CAPTION_CLASS_NAME = "scribdown-image-caption";
171
- var IMAGE_FRAME_FAILED_CLASS_NAME = "scribdown-image-frame--failed";
172
- var IMAGE_FRAME_LOADED_CLASS_NAME = "scribdown-image-frame--loaded";
173
- var IMAGE_FALLBACK_CLASS_NAME = "scribdown-image-fallback";
174
- var IMAGE_FALLBACK_ICON_CLASS_NAME = "scribdown-image-fallback-icon";
175
- var IMAGE_FALLBACK_TEXT_CLASS_NAME = "scribdown-image-fallback-text";
176
- var IMAGE_FALLBACK_SOURCE_CLASS_NAME = "scribdown-image-fallback-source";
177
- var IMAGE_HYDRATED_DATA_KEY = "scribdownImageHydrated";
178
- var VIDEO_FIGURE_CLASS_NAME = "scribdown-video-figure";
179
- var VIDEO_FRAME_CLASS_NAME = "scribdown-video-frame";
180
- var VIDEO_ELEMENT_CLASS_NAME = "scribdown-video";
181
- var VIDEO_FRAME_FAILED_CLASS_NAME = "scribdown-video-frame--failed";
182
- var VIDEO_FRAME_LOADED_CLASS_NAME = "scribdown-video-frame--loaded";
183
- var VIDEO_FALLBACK_CLASS_NAME = "scribdown-video-fallback";
184
- var VIDEO_FALLBACK_ICON_CLASS_NAME = "scribdown-video-fallback-icon";
185
- var VIDEO_FALLBACK_TEXT_CLASS_NAME = "scribdown-video-fallback-text";
186
- var VIDEO_FALLBACK_SOURCE_CLASS_NAME = "scribdown-video-fallback-source";
187
- var VIDEO_HYDRATED_DATA_KEY = "scribdownVideoHydrated";
188
- var VIDEO_FALLBACK_DEFAULT_TEXT = "\u89C6\u9891\u52A0\u8F7D\u5931\u8D25";
189
- var MERMAID_LANGUAGE_ID = "mermaid";
190
- var MERMAID_FIGURE_CLASS_NAME = "scribdown-mermaid";
191
- var MERMAID_CHROME_CLASS_NAME = "scribdown-mermaid__chrome";
192
- var MERMAID_LABEL_CLASS_NAME = "scribdown-mermaid__label";
193
- var MERMAID_BODY_CLASS_NAME = "scribdown-mermaid__body";
194
- var MERMAID_CANVAS_CLASS_NAME = "scribdown-mermaid__canvas";
195
- var MERMAID_FALLBACK_CLASS_NAME = "scribdown-mermaid__fallback";
196
- var MERMAID_FALLBACK_ICON_CLASS_NAME = "scribdown-mermaid__fallback-icon";
197
- var MERMAID_FALLBACK_TEXT_CLASS_NAME = "scribdown-mermaid__fallback-text";
198
- var MERMAID_FALLBACK_SOURCE_CLASS_NAME = "scribdown-mermaid__fallback-source";
199
- var MERMAID_FIGURE_LOADING_CLASS_NAME = "scribdown-mermaid--loading";
200
- var MERMAID_FIGURE_FAILED_CLASS_NAME = "scribdown-mermaid--failed";
201
- var MERMAID_FIGURE_LOADED_CLASS_NAME = "scribdown-mermaid--loaded";
202
- var MERMAID_HYDRATED_DATA_KEY = "scribdownMermaidHydrated";
203
- var MERMAID_RENDER_STARTED_DATA_KEY = "scribdownMermaidRenderStarted";
204
- var MERMAID_SOURCE_DATA_KEY = "scribdownMermaidSourceText";
205
- var MERMAID_LABEL_TEXT = "Mermaid";
206
- var MERMAID_FALLBACK_DEFAULT_TEXT = "\u56FE\u8868\u6E32\u67D3\u5931\u8D25";
207
- var MERMAID_RENDER_ID_PREFIX = "scribdown-mermaid-";
208
- var mermaidRenderIdCounter = 0;
209
- var MERMAID_FULLSCREEN_BUTTON_CLASS_NAME = "scribdown-mermaid__fullscreen";
210
- var MERMAID_FULLSCREEN_BUTTON_ARIA_LABEL = "\u5168\u5C4F\u67E5\u770B\u56FE\u8868";
211
- var MERMAID_VIEWER_DIALOG_CLASS_NAME = "scribdown-mermaid-viewer";
212
- var MERMAID_VIEWER_ZOOMED_CLASS_NAME = "scribdown-mermaid-viewer--zoomed";
213
- var MERMAID_VIEWER_DRAGGING_CLASS_NAME = "scribdown-mermaid-viewer--dragging";
214
- var MERMAID_VIEWER_CHROME_CLASS_NAME = "scribdown-mermaid-viewer__chrome";
215
- var MERMAID_VIEWER_CAPTION_CLASS_NAME = "scribdown-mermaid-viewer__caption";
216
- var MERMAID_VIEWER_CONTROLS_CLASS_NAME = "scribdown-mermaid-viewer__controls";
217
- var MERMAID_VIEWER_BUTTON_CLASS_NAME = "scribdown-mermaid-viewer__button";
218
- var MERMAID_VIEWER_CLOSE_BUTTON_CLASS_NAME = "scribdown-mermaid-viewer__button--close";
219
- var MERMAID_VIEWER_VIEWPORT_CLASS_NAME = "scribdown-mermaid-viewer__viewport";
220
- var MERMAID_VIEWER_CANVAS_CLASS_NAME = "scribdown-mermaid-viewer__canvas";
221
- var MERMAID_VIEWER_ZOOM_VALUE_CLASS_NAME = "scribdown-mermaid-viewer__zoom-value";
222
- var MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY = "scribdownMermaidFullscreenReady";
223
- var MERMAID_VIEWER_SOURCE_DATA_KEY = "scribdownMermaidSource";
224
- var mermaidViewerStateByDocument = /* @__PURE__ */ new WeakMap();
225
- var mermaidViewerStateByDialogElement = /* @__PURE__ */ new WeakMap();
226
- var IMAGE_VIEWER_DIALOG_CLASS_NAME = "scribdown-image-viewer";
227
- var IMAGE_VIEWER_ZOOMED_CLASS_NAME = "scribdown-image-viewer--zoomed";
228
- var IMAGE_VIEWER_DRAGGING_CLASS_NAME = "scribdown-image-viewer--dragging";
229
- var IMAGE_VIEWER_CAPTION_GROUP_CLASS_NAME = "scribdown-image-viewer__caption-group";
230
- var IMAGE_VIEWER_HINT_CLASS_NAME = "scribdown-image-viewer__hint";
231
- var IMAGE_VIEWER_HINT_TEXT = "\u5FEB\u6377\u952E\uFF1A+/= \u653E\u5927 \xB7 - \u7F29\u5C0F \xB7 0 \u91CD\u7F6E \xB7 Esc \u5173\u95ED \xB7 \u9F20\u6807\u62D6\u62FD\u53EF\u5E73\u79FB \xB7 Ctrl/\u2318 + \u6EDA\u8F6E\u7F29\u653E";
232
- var IMAGE_VIEWER_CHROME_CLASS_NAME = "scribdown-image-viewer__chrome";
233
- var IMAGE_VIEWER_CONTROLS_CLASS_NAME = "scribdown-image-viewer__controls";
234
- var IMAGE_VIEWER_BUTTON_CLASS_NAME = "scribdown-image-viewer__button";
235
- var IMAGE_VIEWER_CLOSE_BUTTON_CLASS_NAME = "scribdown-image-viewer__button--close";
236
- var IMAGE_VIEWER_VIEWPORT_CLASS_NAME = "scribdown-image-viewer__viewport";
237
- var IMAGE_VIEWER_IMAGE_CLASS_NAME = "scribdown-image-viewer__image";
238
- var IMAGE_VIEWER_CAPTION_CLASS_NAME = "scribdown-image-viewer__caption";
239
- var IMAGE_VIEWER_ZOOM_VALUE_CLASS_NAME = "scribdown-image-viewer__zoom-value";
240
- var IMAGE_VIEWER_DEFAULT_ZOOM = 1;
241
- var IMAGE_VIEWER_MIN_ZOOM = 0.25;
242
- var IMAGE_VIEWER_MAX_ZOOM = 4;
243
- var IMAGE_VIEWER_ZOOM_STEP = 0.25;
244
- var IMAGE_VIEWER_WHEEL_ZOOM_FACTOR = 0.01;
245
- var IMAGE_VIEWER_WHEEL_ZOOM_MAX_DELTA = 0.1;
246
- var IMAGE_VIEWER_FIT_RATIO = 0.92;
247
- var IMAGE_VIEWER_FALLBACK_LABEL = "\u67E5\u770B\u5168\u56FE";
248
- var IMAGE_VIEWER_CLOSE_TEXT = "x";
249
- var IMAGE_VIEWER_ZOOM_IN_TEXT = "+";
250
- var IMAGE_VIEWER_ZOOM_OUT_TEXT = "-";
251
- var IMAGE_VIEWER_RESET_TEXT = "1:1";
252
- var imageViewerStateByDocument = /* @__PURE__ */ new WeakMap();
253
- var imageViewerStateByDialogElement = /* @__PURE__ */ new WeakMap();
254
- var HIGHLIGHT_MARKER_PATTERN = /==([^=\n]+?)==/g;
255
- var DEFINITION_LIST_PARAGRAPH_PATTERN = /^\s*([^\n:][^\n]*)\n\s*:\s+([^\n]+)\s*$/u;
256
- var CODE_BLOCK_CLASS_NAME = "scribdown-code-block";
257
- var CODE_BLOCK_CHROME_CLASS_NAME = "scribdown-code-block__chrome";
258
- var CODE_BLOCK_BODY_CLASS_NAME = "scribdown-code-block__body";
259
- var CODE_BLOCK_LANG_CLASS_NAME = "scribdown-code-block__lang";
260
- var CODE_BLOCK_COPY_CLASS_NAME = "scribdown-code-block__copy";
261
- var CODE_BLOCK_COPY_ICON_CLASS_NAME = "scribdown-code-block__copy-icon";
262
- var CODE_BLOCK_COPY_ICON_COPY_CLASS_NAME = "scribdown-code-block__copy-icon--copy";
263
- var CODE_BLOCK_COPY_ICON_CHECK_CLASS_NAME = "scribdown-code-block__copy-icon--check";
264
- var CODE_BLOCK_LINE_CLASS_NAME = "scribdown-code-block__line";
265
- var CODE_BLOCK_GUTTER_CLASS_NAME = "scribdown-code-block__gutter";
266
- var CODE_BLOCK_GUTTER_LINE_CLASS_NAME = "scribdown-code-block__gutter-line";
267
- var CODE_BLOCK_EMPTY_LINE_PLACEHOLDER = "\u200B";
268
- var CODE_BLOCK_HYDRATED_DATA_KEY = "scribdownCodeBlockHydrated";
269
- var CODE_LANGUAGE_DISPLAY_NAMES = {
270
- bash: "Bash",
271
- c: "C",
272
- cpp: "C++",
273
- csharp: "C#",
274
- css: "CSS",
275
- diff: "Diff",
276
- go: "Go",
277
- graphql: "GraphQL",
278
- html: "HTML",
279
- java: "Java",
280
- javascript: "JavaScript",
281
- js: "JavaScript",
282
- json: "JSON",
283
- jsx: "JSX",
284
- kotlin: "Kotlin",
285
- less: "Less",
286
- markdown: "Markdown",
287
- md: "Markdown",
288
- php: "PHP",
289
- python: "Python",
290
- py: "Python",
291
- ruby: "Ruby",
292
- rust: "Rust",
293
- rs: "Rust",
294
- sass: "Sass",
295
- scss: "SCSS",
296
- sh: "Shell",
297
- shell: "Shell",
298
- sql: "SQL",
299
- swift: "Swift",
300
- toml: "TOML",
301
- ts: "TypeScript",
302
- tsx: "TSX",
303
- typescript: "TypeScript",
304
- vue: "Vue",
305
- xml: "XML",
306
- yaml: "YAML",
307
- yml: "YAML",
308
- zsh: "Zsh"
309
- };
310
- var CODE_LANGUAGE_DEFAULT_LABEL = "Text";
311
- var CODE_BLOCK_COPY_RESTORE_DELAY_MS = 1600;
312
- var CODE_BLOCK_COPY_ARIA_LABEL = "\u590D\u5236\u4EE3\u7801";
313
- var CODE_BLOCK_COPY_ARIA_LABEL_COPIED = "\u5DF2\u590D\u5236";
314
- var CODE_HIGHLIGHTER_THEME = "github-light";
315
- var codeHighlighterPromise;
316
- var codeHighlighterLazyLoadPromises = /* @__PURE__ */ new Map();
317
- var CODE_BLOCK_HIGHLIGHT_PATTERN = /<pre([^>]*)><code class="language-([\w-]+)"([^>]*)>([\s\S]*?)<\/code><\/pre>/g;
318
- var SHIKI_CODE_INNER_PATTERN = /<code[^>]*>([\s\S]*?)<\/code>/u;
319
- async function renderMarkdown(markdownText) {
320
- const processor = unified().use(remarkParse).use(remarkGfm).use(remarkHighlightMark).use(remarkDefinitionLists).use(remarkTableOfContents).use(remarkImageFigures).use(remarkSourceLine).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeRaw).use(rehypeFrameClass).use(rehypeVideoFigures).use(rehypeSanitize, createScribdownSanitizeSchema()).use(rehypeStringify);
321
- const renderedHtml = String(await processor.process(markdownText));
322
- const sanitizedHtml = sanitizeHtmlWithDomPurify(renderedHtml);
323
- return highlightMarkdownCodeBlocks(sanitizedHtml);
324
- }
325
- async function getCodeHighlighter() {
326
- if (!codeHighlighterPromise) {
327
- codeHighlighterPromise = createHighlighterCore({
328
- themes: [githubLightTheme],
329
- // eager loaders 通过动态 import 拆为独立 chunk,初始化时并发拉取;
330
- // 主 bundle 不再内联 grammar JSON,体积更小,多个 grammar 也可并行加载。
331
- langs: CODE_HIGHLIGHTER_EAGER_LOADERS,
332
- // oniguruma 引擎 + wasm;shiki/wasm 内部走 base64 内联,扩展环境无需额外资源声明。
333
- engine: createOnigurumaEngine(import("shiki/wasm"))
334
- });
335
- }
336
- return codeHighlighterPromise;
337
- }
338
- async function highlightMarkdownCodeBlocks(html) {
339
- if (!CODE_BLOCK_HIGHLIGHT_PATTERN.test(html)) {
340
- return html;
341
- }
342
- CODE_BLOCK_HIGHLIGHT_PATTERN.lastIndex = 0;
343
- const highlighter = await getCodeHighlighter();
344
- let resultHtml = "";
345
- let lastIndex = 0;
346
- for (const match of html.matchAll(CODE_BLOCK_HIGHLIGHT_PATTERN)) {
347
- const [matched, preAttributes, rawLanguage, codeAttributes, encodedCode] = match;
348
- const matchStart = match.index ?? 0;
349
- resultHtml += html.slice(lastIndex, matchStart);
350
- resultHtml += await highlightSingleCodeBlock(highlighter, {
351
- rawLanguage,
352
- encodedCode,
353
- matchedHtml: matched,
354
- preAttributes,
355
- codeAttributes
356
- });
357
- lastIndex = matchStart + matched.length;
358
- }
359
- resultHtml += html.slice(lastIndex);
360
- return resultHtml;
361
- }
362
- async function highlightSingleCodeBlock(highlighter, input) {
363
- const { rawLanguage, encodedCode, matchedHtml, preAttributes, codeAttributes } = input;
364
- const normalizedLanguage = rawLanguage.toLowerCase();
365
- if (normalizedLanguage === MERMAID_LANGUAGE_ID) {
366
- return matchedHtml;
367
- }
368
- const resolvedLanguage = await ensureHighlighterLanguage(highlighter, normalizedLanguage);
369
- const codeText = decodeHtmlEntities(encodedCode);
370
- try {
371
- const highlightedHtml = highlighter.codeToHtml(codeText, {
372
- lang: resolvedLanguage,
373
- theme: CODE_HIGHLIGHTER_THEME
374
- });
375
- const innerMatch = highlightedHtml.match(SHIKI_CODE_INNER_PATTERN);
376
- const innerHtml = innerMatch?.[1] ?? encodedCode;
377
- return `<pre${preAttributes}><code class="language-${rawLanguage}"${codeAttributes}>${innerHtml}</code></pre>`;
378
- } catch {
379
- return matchedHtml;
380
- }
381
- }
382
- async function ensureHighlighterLanguage(highlighter, normalizedLanguage) {
383
- if (normalizedLanguage.length === 0) {
384
- return "text";
385
- }
386
- if (highlighter.getLoadedLanguages().includes(normalizedLanguage)) {
387
- return normalizedLanguage;
388
- }
389
- const lazyLoader = CODE_HIGHLIGHTER_LAZY_LANGS[normalizedLanguage];
390
- if (!lazyLoader) {
391
- return "text";
392
- }
393
- let pending = codeHighlighterLazyLoadPromises.get(normalizedLanguage);
394
- if (!pending) {
395
- pending = (async () => {
396
- const grammarModule = await lazyLoader();
397
- await highlighter.loadLanguage(grammarModule.default);
398
- })();
399
- codeHighlighterLazyLoadPromises.set(normalizedLanguage, pending);
400
- }
401
- try {
402
- await pending;
403
- return normalizedLanguage;
404
- } catch {
405
- return "text";
406
- }
407
- }
408
- var HTML_NAMED_ENTITIES = {
409
- lt: "<",
410
- gt: ">",
411
- quot: '"',
412
- apos: "'",
413
- amp: "&"
414
- };
415
- var HTML_ENTITY_PATTERN = /&(?:(lt|gt|quot|apos|amp)|#x([0-9a-fA-F]+)|#(\d+));/g;
416
- function decodeHtmlEntities(encodedText) {
417
- return encodedText.replace(
418
- HTML_ENTITY_PATTERN,
419
- (matched, namedEntity, hexCodePoint, decimalCodePoint) => {
420
- if (namedEntity) {
421
- return HTML_NAMED_ENTITIES[namedEntity] ?? matched;
422
- }
423
- if (hexCodePoint) {
424
- return safeFromCodePoint(parseInt(hexCodePoint, 16), matched);
425
- }
426
- if (decimalCodePoint) {
427
- return safeFromCodePoint(parseInt(decimalCodePoint, 10), matched);
428
- }
429
- return matched;
430
- }
431
- );
432
- }
433
- function safeFromCodePoint(codePoint, fallback) {
434
- if (!Number.isFinite(codePoint) || codePoint < 0 || codePoint > 1114111) {
435
- return fallback;
436
- }
437
- try {
438
- return String.fromCodePoint(codePoint);
439
- } catch {
440
- return fallback;
441
- }
442
- }
443
- function sanitizeHtmlWithDomPurify(unsafeHtml) {
444
- const domPurify = DOMPurify;
445
- if (typeof domPurify.sanitize !== "function") {
446
- return unsafeHtml;
447
- }
448
- return domPurify.sanitize(unsafeHtml);
449
- }
450
- function hydrateMarkdownImages(rootElement) {
451
- const imageElements = rootElement.querySelectorAll(
452
- `img.${IMAGE_ELEMENT_CLASS_NAME}`
453
- );
454
- imageElements.forEach(bindMarkdownImageState);
455
- }
456
- function hydrateMarkdownVideos(rootElement) {
457
- const videoElements = rootElement.querySelectorAll(
458
- `video.${VIDEO_ELEMENT_CLASS_NAME}`
459
- );
460
- videoElements.forEach(bindMarkdownVideoState);
461
- }
462
- function hydrateCodeBlocks(rootElement) {
463
- const preElements = rootElement.querySelectorAll("pre");
464
- preElements.forEach(decorateCodeBlock);
465
- }
466
- var mermaidLoaderPromise;
467
- function hydrateMermaidBlocks(rootElement) {
468
- const mermaidCodeElements = rootElement.querySelectorAll(
469
- `pre > code.language-${MERMAID_LANGUAGE_ID}`
470
- );
471
- mermaidCodeElements.forEach((codeElement) => {
472
- const preElement = codeElement.parentElement;
473
- if (!preElement) {
474
- return;
475
- }
476
- if (preElement.dataset[CODE_BLOCK_HYDRATED_DATA_KEY] === "true") {
477
- return;
478
- }
479
- decorateMermaidBlock(preElement, codeElement);
480
- });
481
- kickOffPendingMermaidRenders(rootElement);
482
- }
483
- function kickOffPendingMermaidRenders(rootElement) {
484
- const pendingFigures = rootElement.querySelectorAll(
485
- `.${MERMAID_FIGURE_CLASS_NAME}.${MERMAID_FIGURE_LOADING_CLASS_NAME}`
486
- );
487
- pendingFigures.forEach((figureElement) => {
488
- if (!figureElement.isConnected) {
489
- return;
490
- }
491
- if (figureElement.dataset[MERMAID_RENDER_STARTED_DATA_KEY] === "true") {
492
- return;
493
- }
494
- const canvasElement = figureElement.querySelector(
495
- `.${MERMAID_CANVAS_CLASS_NAME}`
496
- );
497
- const mermaidSource = figureElement.dataset[MERMAID_SOURCE_DATA_KEY] ?? "";
498
- if (!canvasElement || mermaidSource.length === 0) {
499
- return;
500
- }
501
- figureElement.dataset[MERMAID_RENDER_STARTED_DATA_KEY] = "true";
502
- void renderMermaidIntoCanvas(figureElement, canvasElement, mermaidSource);
503
- });
504
- }
505
- function decorateMermaidBlock(preElement, codeElement) {
506
- if (preElement.dataset[MERMAID_HYDRATED_DATA_KEY] === "true") {
507
- return;
508
- }
509
- preElement.dataset[MERMAID_HYDRATED_DATA_KEY] = "true";
510
- const ownerDocument = preElement.ownerDocument;
511
- const mermaidSource = (codeElement.textContent ?? "").replace(/\n+$/u, "");
512
- const figureElement = ownerDocument.createElement("figure");
513
- figureElement.className = `${MERMAID_FIGURE_CLASS_NAME} ${MERMAID_FIGURE_LOADING_CLASS_NAME}`;
514
- const chromeElement = ownerDocument.createElement("div");
515
- chromeElement.className = MERMAID_CHROME_CLASS_NAME;
516
- const labelElement = ownerDocument.createElement("span");
517
- labelElement.className = MERMAID_LABEL_CLASS_NAME;
518
- labelElement.textContent = MERMAID_LABEL_TEXT;
519
- chromeElement.append(labelElement);
520
- const bodyElement = ownerDocument.createElement("div");
521
- bodyElement.className = MERMAID_BODY_CLASS_NAME;
522
- const canvasElement = ownerDocument.createElement("div");
523
- canvasElement.className = MERMAID_CANVAS_CLASS_NAME;
524
- canvasElement.setAttribute("role", "img");
525
- canvasElement.setAttribute("aria-label", MERMAID_LABEL_TEXT);
526
- const fullscreenButtonElement = ownerDocument.createElement("button");
527
- fullscreenButtonElement.type = "button";
528
- fullscreenButtonElement.className = MERMAID_FULLSCREEN_BUTTON_CLASS_NAME;
529
- fullscreenButtonElement.setAttribute("aria-label", MERMAID_FULLSCREEN_BUTTON_ARIA_LABEL);
530
- fullscreenButtonElement.disabled = true;
531
- fullscreenButtonElement.innerHTML = MERMAID_FULLSCREEN_ICON_SVG;
532
- fullscreenButtonElement.addEventListener("click", handleMermaidFullscreenButtonClick);
533
- bodyElement.append(canvasElement, fullscreenButtonElement);
534
- figureElement.append(chromeElement, bodyElement);
535
- const sourceLine = codeElement.getAttribute(SOURCE_LINE_DATA_ATTRIBUTE);
536
- if (sourceLine !== null) {
537
- figureElement.setAttribute(SOURCE_LINE_DATA_ATTRIBUTE, sourceLine);
538
- }
539
- figureElement.dataset[MERMAID_SOURCE_DATA_KEY] = mermaidSource;
540
- preElement.replaceWith(figureElement);
541
- }
542
- async function loadMermaid() {
543
- if (typeof window === "undefined" || typeof document === "undefined") {
544
- return void 0;
545
- }
546
- if (!mermaidLoaderPromise) {
547
- mermaidLoaderPromise = (async () => {
548
- const mermaidModule = await import("mermaid");
549
- const mermaidApi = mermaidModule.default;
550
- mermaidApi.initialize({
551
- startOnLoad: false,
552
- securityLevel: "strict",
553
- theme: "default",
554
- flowchart: { useMaxWidth: false },
555
- sequence: { useMaxWidth: false },
556
- class: { useMaxWidth: false },
557
- state: { useMaxWidth: false },
558
- gantt: { useMaxWidth: false },
559
- er: { useMaxWidth: false },
560
- journey: { useMaxWidth: false },
561
- pie: { useMaxWidth: false },
562
- requirement: { useMaxWidth: false },
563
- c4: { useMaxWidth: false },
564
- mindmap: { useMaxWidth: false },
565
- timeline: { useMaxWidth: false },
566
- gitGraph: { useMaxWidth: false },
567
- quadrantChart: { useMaxWidth: false },
568
- xyChart: { useMaxWidth: false },
569
- sankey: { useMaxWidth: false },
570
- block: { useMaxWidth: false }
571
- });
572
- return mermaidApi;
573
- })().catch((loadError) => {
574
- mermaidLoaderPromise = void 0;
575
- throw loadError;
576
- });
577
- }
578
- return mermaidLoaderPromise;
579
- }
580
- async function renderMermaidIntoCanvas(figureElement, canvasElement, mermaidSource) {
581
- try {
582
- const mermaidApi = await loadMermaid();
583
- if (!mermaidApi) {
584
- return;
585
- }
586
- mermaidRenderIdCounter += 1;
587
- const renderId = `${MERMAID_RENDER_ID_PREFIX}${mermaidRenderIdCounter}`;
588
- const { svg, bindFunctions } = await mermaidApi.render(renderId, mermaidSource);
589
- canvasElement.innerHTML = svg;
590
- bindFunctions?.(canvasElement);
591
- figureElement.classList.remove(MERMAID_FIGURE_LOADING_CLASS_NAME);
592
- figureElement.classList.add(MERMAID_FIGURE_LOADED_CLASS_NAME);
593
- figureElement.dataset[MERMAID_VIEWER_SOURCE_DATA_KEY] = svg;
594
- figureElement.dataset[MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY] = "true";
595
- const fullscreenButtonElement = figureElement.querySelector(
596
- `.${MERMAID_FULLSCREEN_BUTTON_CLASS_NAME}`
597
- );
598
- if (fullscreenButtonElement) {
599
- fullscreenButtonElement.disabled = false;
600
- }
601
- } catch (renderError) {
602
- showMermaidFallback(figureElement, canvasElement, mermaidSource, renderError);
603
- }
604
- }
605
- function showMermaidFallback(figureElement, canvasElement, mermaidSource, renderError) {
606
- const ownerDocument = figureElement.ownerDocument;
607
- figureElement.classList.remove(MERMAID_FIGURE_LOADING_CLASS_NAME);
608
- figureElement.classList.add(MERMAID_FIGURE_FAILED_CLASS_NAME);
609
- const fullscreenButtonElement = figureElement.querySelector(
610
- `.${MERMAID_FULLSCREEN_BUTTON_CLASS_NAME}`
611
- );
612
- fullscreenButtonElement?.remove();
613
- canvasElement.replaceChildren();
614
- cleanupOrphanMermaidNodes(ownerDocument);
615
- const fallbackElement = ownerDocument.createElement("div");
616
- fallbackElement.className = MERMAID_FALLBACK_CLASS_NAME;
617
- const iconElement = ownerDocument.createElement("span");
618
- iconElement.className = MERMAID_FALLBACK_ICON_CLASS_NAME;
619
- iconElement.setAttribute("aria-hidden", "true");
620
- fallbackElement.append(iconElement);
621
- const textElement = ownerDocument.createElement("p");
622
- textElement.className = MERMAID_FALLBACK_TEXT_CLASS_NAME;
623
- textElement.textContent = MERMAID_FALLBACK_DEFAULT_TEXT;
624
- fallbackElement.append(textElement);
625
- const errorMessage = extractErrorMessage(renderError);
626
- if (errorMessage) {
627
- const messageElement = ownerDocument.createElement("p");
628
- messageElement.className = MERMAID_FALLBACK_SOURCE_CLASS_NAME;
629
- messageElement.textContent = errorMessage;
630
- fallbackElement.append(messageElement);
631
- }
632
- if (mermaidSource.length > 0) {
633
- const sourceElement = ownerDocument.createElement("pre");
634
- sourceElement.className = MERMAID_FALLBACK_SOURCE_CLASS_NAME;
635
- sourceElement.textContent = mermaidSource;
636
- fallbackElement.append(sourceElement);
637
- }
638
- canvasElement.replaceWith(fallbackElement);
639
- }
640
- function extractErrorMessage(renderError) {
641
- if (renderError instanceof Error) {
642
- return renderError.message;
643
- }
644
- if (typeof renderError === "string") {
645
- return renderError;
646
- }
647
- return "";
648
- }
649
- function cleanupOrphanMermaidNodes(ownerDocument) {
650
- const orphanNodes = ownerDocument.querySelectorAll(
651
- `body > [id^="${MERMAID_RENDER_ID_PREFIX}"], body > div[id^="d"][id*="mermaid"]`
652
- );
653
- orphanNodes.forEach((orphanNode) => {
654
- orphanNode.remove();
655
- });
656
- }
657
- var MERMAID_FULLSCREEN_ICON_SVG = '<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false"><path d="M4 9 V4 H9 M15 4 H20 V9 M20 15 V20 H15 M9 20 H4 V15" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
658
- function handleMermaidFullscreenButtonClick(event) {
659
- const buttonElement = event.currentTarget;
660
- const figureElement = buttonElement.closest(`.${MERMAID_FIGURE_CLASS_NAME}`);
661
- if (!figureElement) {
662
- return;
663
- }
664
- if (figureElement.dataset[MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY] !== "true") {
665
- return;
666
- }
667
- const svgSource = figureElement.dataset[MERMAID_VIEWER_SOURCE_DATA_KEY] ?? "";
668
- if (svgSource.length === 0) {
669
- return;
670
- }
671
- event.preventDefault();
672
- event.stopPropagation();
673
- openMarkdownMermaidViewer(figureElement.ownerDocument, svgSource);
371
+ // src/code/mermaid.ts
372
+ import {
373
+ MERMAID_BODY_CLASS_NAME,
374
+ MERMAID_CANVAS_CLASS_NAME,
375
+ MERMAID_CHROME_CLASS_NAME,
376
+ MERMAID_FALLBACK_CLASS_NAME,
377
+ MERMAID_FALLBACK_ICON_CLASS_NAME,
378
+ MERMAID_FALLBACK_SOURCE_CLASS_NAME,
379
+ MERMAID_FALLBACK_TEXT_CLASS_NAME,
380
+ MERMAID_FIGURE_CLASS_NAME,
381
+ MERMAID_FIGURE_FAILED_CLASS_NAME,
382
+ MERMAID_FIGURE_LOADED_CLASS_NAME,
383
+ MERMAID_FIGURE_LOADING_CLASS_NAME,
384
+ MERMAID_FULLSCREEN_BUTTON_CLASS_NAME,
385
+ MERMAID_LABEL_CLASS_NAME,
386
+ SOURCE_LINE_DATA_ATTRIBUTE as SOURCE_LINE_DATA_ATTRIBUTE2
387
+ } from "@scribdown/shared";
388
+
389
+ // src/code/mermaid-viewer.ts
390
+ import {
391
+ MERMAID_VIEWER_BUTTON_CLASS_NAME,
392
+ MERMAID_VIEWER_CANVAS_CLASS_NAME,
393
+ MERMAID_VIEWER_CAPTION_CLASS_NAME,
394
+ MERMAID_VIEWER_CHROME_CLASS_NAME,
395
+ MERMAID_VIEWER_CLOSE_BUTTON_CLASS_NAME,
396
+ MERMAID_VIEWER_CONTROLS_CLASS_NAME,
397
+ MERMAID_VIEWER_DIALOG_CLASS_NAME,
398
+ MERMAID_VIEWER_DRAGGING_CLASS_NAME,
399
+ MERMAID_VIEWER_VIEWPORT_CLASS_NAME,
400
+ MERMAID_VIEWER_ZOOMED_CLASS_NAME,
401
+ MERMAID_VIEWER_ZOOM_VALUE_CLASS_NAME
402
+ } from "@scribdown/shared";
403
+
404
+ // src/core/viewer-shared.ts
405
+ var VIEWER_DEFAULT_ZOOM = 1;
406
+ var VIEWER_MIN_ZOOM = 0.25;
407
+ var VIEWER_MAX_ZOOM = 4;
408
+ var VIEWER_ZOOM_STEP = 0.25;
409
+ var VIEWER_WHEEL_ZOOM_FACTOR = 0.01;
410
+ var VIEWER_WHEEL_ZOOM_MAX_DELTA = 0.1;
411
+ var VIEWER_FIT_RATIO = 0.92;
412
+ var VIEWER_CLOSE_TEXT = "x";
413
+ var VIEWER_ZOOM_IN_TEXT = "+";
414
+ var VIEWER_ZOOM_OUT_TEXT = "-";
415
+ var VIEWER_RESET_TEXT = "1:1";
416
+ function clampMarkdownViewerZoom(zoomValue) {
417
+ return Math.min(VIEWER_MAX_ZOOM, Math.max(VIEWER_MIN_ZOOM, zoomValue));
674
418
  }
419
+
420
+ // src/code/mermaid-viewer.ts
421
+ var MERMAID_LABEL_TEXT = "Mermaid";
422
+ var mermaidViewerStateByDocument = /* @__PURE__ */ new WeakMap();
423
+ var mermaidViewerStateByDialogElement = /* @__PURE__ */ new WeakMap();
675
424
  function openMarkdownMermaidViewer(ownerDocument, svgSource) {
676
425
  const viewerState = getOrCreateMarkdownMermaidViewerState(ownerDocument);
677
426
  viewerState.canvasElement.innerHTML = svgSource;
@@ -680,7 +429,7 @@ function openMarkdownMermaidViewer(ownerDocument, svgSource) {
680
429
  viewerState.naturalHeight = svgDimensions.height;
681
430
  viewerState.viewportElement.scrollLeft = 0;
682
431
  viewerState.viewportElement.scrollTop = 0;
683
- viewerState.zoomValue = IMAGE_VIEWER_DEFAULT_ZOOM;
432
+ viewerState.zoomValue = VIEWER_DEFAULT_ZOOM;
684
433
  showMarkdownMermaidViewerDialog(viewerState);
685
434
  requestMarkdownMermaidViewerLayout(viewerState);
686
435
  }
@@ -719,21 +468,21 @@ function createMarkdownMermaidViewerState(ownerDocument) {
719
468
  const controlsElement = ownerDocument.createElement("div");
720
469
  const zoomOutButtonElement = createMarkdownMermaidViewerButton(ownerDocument, {
721
470
  ariaLabel: "\u7F29\u5C0F\u56FE\u8868",
722
- text: IMAGE_VIEWER_ZOOM_OUT_TEXT
471
+ text: VIEWER_ZOOM_OUT_TEXT
723
472
  });
724
473
  const zoomValueElement = ownerDocument.createElement("span");
725
474
  const zoomInButtonElement = createMarkdownMermaidViewerButton(ownerDocument, {
726
475
  ariaLabel: "\u653E\u5927\u56FE\u8868",
727
- text: IMAGE_VIEWER_ZOOM_IN_TEXT
476
+ text: VIEWER_ZOOM_IN_TEXT
728
477
  });
729
478
  const resetButtonElement = createMarkdownMermaidViewerButton(ownerDocument, {
730
479
  ariaLabel: "\u91CD\u7F6E\u7F29\u653E",
731
- text: IMAGE_VIEWER_RESET_TEXT
480
+ text: VIEWER_RESET_TEXT
732
481
  });
733
482
  const closeButtonElement = createMarkdownMermaidViewerButton(ownerDocument, {
734
483
  ariaLabel: "\u5173\u95ED\u5168\u5C4F\u67E5\u770B",
735
484
  className: MERMAID_VIEWER_CLOSE_BUTTON_CLASS_NAME,
736
- text: IMAGE_VIEWER_CLOSE_TEXT
485
+ text: VIEWER_CLOSE_TEXT
737
486
  });
738
487
  const viewportElement = ownerDocument.createElement("div");
739
488
  const canvasElement = ownerDocument.createElement("div");
@@ -749,7 +498,7 @@ function createMarkdownMermaidViewerState(ownerDocument) {
749
498
  canvasElement,
750
499
  naturalWidth: 720,
751
500
  naturalHeight: 480,
752
- zoomValue: IMAGE_VIEWER_DEFAULT_ZOOM,
501
+ zoomValue: VIEWER_DEFAULT_ZOOM,
753
502
  isDragging: false,
754
503
  dragStartClientX: 0,
755
504
  dragStartClientY: 0,
@@ -795,7 +544,7 @@ function createMarkdownMermaidViewerState(ownerDocument) {
795
544
  if (ownerWindow) {
796
545
  ownerWindow.addEventListener("resize", handleMarkdownMermaidViewerWindowResize);
797
546
  }
798
- updateMarkdownMermaidViewerZoom(viewerState, IMAGE_VIEWER_DEFAULT_ZOOM);
547
+ updateMarkdownMermaidViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);
799
548
  return viewerState;
800
549
  }
801
550
  function createMarkdownMermaidViewerButton(ownerDocument, options) {
@@ -850,7 +599,7 @@ function resetMarkdownMermaidViewerAfterClose(viewerState) {
850
599
  viewerState.canvasElement.replaceChildren();
851
600
  viewerState.viewportElement.scrollLeft = 0;
852
601
  viewerState.viewportElement.scrollTop = 0;
853
- viewerState.zoomValue = IMAGE_VIEWER_DEFAULT_ZOOM;
602
+ viewerState.zoomValue = VIEWER_DEFAULT_ZOOM;
854
603
  viewerState.isDragging = false;
855
604
  viewerState.dialogElement.classList.remove(MERMAID_VIEWER_DRAGGING_CLASS_NAME);
856
605
  viewerState.dialogElement.classList.remove(MERMAID_VIEWER_ZOOMED_CLASS_NAME);
@@ -862,17 +611,17 @@ function handleMarkdownMermaidViewerKeyDown(event) {
862
611
  }
863
612
  if (event.key === "+" || event.key === "=") {
864
613
  event.preventDefault();
865
- updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue + IMAGE_VIEWER_ZOOM_STEP);
614
+ updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue + VIEWER_ZOOM_STEP);
866
615
  return;
867
616
  }
868
617
  if (event.key === "-") {
869
618
  event.preventDefault();
870
- updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue - IMAGE_VIEWER_ZOOM_STEP);
619
+ updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue - VIEWER_ZOOM_STEP);
871
620
  return;
872
621
  }
873
622
  if (event.key === "0") {
874
623
  event.preventDefault();
875
- updateMarkdownMermaidViewerZoom(viewerState, IMAGE_VIEWER_DEFAULT_ZOOM);
624
+ updateMarkdownMermaidViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);
876
625
  return;
877
626
  }
878
627
  if (event.key === "Escape") {
@@ -888,10 +637,10 @@ function handleMarkdownMermaidViewerWheel(event) {
888
637
  if (!viewerState) {
889
638
  return;
890
639
  }
891
- const rawDelta = -event.deltaY * IMAGE_VIEWER_WHEEL_ZOOM_FACTOR;
640
+ const rawDelta = -event.deltaY * VIEWER_WHEEL_ZOOM_FACTOR;
892
641
  const zoomDelta = Math.max(
893
- -IMAGE_VIEWER_WHEEL_ZOOM_MAX_DELTA,
894
- Math.min(IMAGE_VIEWER_WHEEL_ZOOM_MAX_DELTA, rawDelta)
642
+ -VIEWER_WHEEL_ZOOM_MAX_DELTA,
643
+ Math.min(VIEWER_WHEEL_ZOOM_MAX_DELTA, rawDelta)
895
644
  );
896
645
  event.preventDefault();
897
646
  updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue + zoomDelta, {
@@ -960,21 +709,21 @@ function handleMarkdownMermaidViewerZoomOutClick(event) {
960
709
  if (!viewerState) {
961
710
  return;
962
711
  }
963
- updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue - IMAGE_VIEWER_ZOOM_STEP);
712
+ updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue - VIEWER_ZOOM_STEP);
964
713
  }
965
714
  function handleMarkdownMermaidViewerZoomInClick(event) {
966
715
  const viewerState = getMarkdownMermaidViewerStateFromEvent(event);
967
716
  if (!viewerState) {
968
717
  return;
969
718
  }
970
- updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue + IMAGE_VIEWER_ZOOM_STEP);
719
+ updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue + VIEWER_ZOOM_STEP);
971
720
  }
972
721
  function handleMarkdownMermaidViewerResetClick(event) {
973
722
  const viewerState = getMarkdownMermaidViewerStateFromEvent(event);
974
723
  if (!viewerState) {
975
724
  return;
976
725
  }
977
- updateMarkdownMermaidViewerZoom(viewerState, IMAGE_VIEWER_DEFAULT_ZOOM);
726
+ updateMarkdownMermaidViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);
978
727
  }
979
728
  function handleMarkdownMermaidViewerCloseClick(event) {
980
729
  const viewerState = getMarkdownMermaidViewerStateFromEvent(event);
@@ -1003,17 +752,17 @@ function isMarkdownMermaidViewerOpen(viewerState) {
1003
752
  return viewerState.dialogElement.open === true;
1004
753
  }
1005
754
  function updateMarkdownMermaidViewerZoom(viewerState, nextZoom, zoomAnchor) {
1006
- const normalizedZoom = clampMarkdownImageViewerZoom(nextZoom);
755
+ const normalizedZoom = clampMarkdownViewerZoom(nextZoom);
1007
756
  const focalPoint = isMarkdownMermaidViewerOpen(viewerState) ? captureMarkdownMermaidViewerFocalPoint(viewerState, zoomAnchor) : void 0;
1008
757
  viewerState.zoomValue = normalizedZoom;
1009
758
  viewerState.dialogElement.classList.toggle(
1010
759
  MERMAID_VIEWER_ZOOMED_CLASS_NAME,
1011
- normalizedZoom > IMAGE_VIEWER_DEFAULT_ZOOM
760
+ normalizedZoom > VIEWER_DEFAULT_ZOOM
1012
761
  );
1013
762
  viewerState.zoomValueElement.textContent = `${Math.round(normalizedZoom * 100)}%`;
1014
- viewerState.zoomOutButtonElement.disabled = normalizedZoom <= IMAGE_VIEWER_MIN_ZOOM;
1015
- viewerState.zoomInButtonElement.disabled = normalizedZoom >= IMAGE_VIEWER_MAX_ZOOM;
1016
- viewerState.resetButtonElement.disabled = normalizedZoom === IMAGE_VIEWER_DEFAULT_ZOOM;
763
+ viewerState.zoomOutButtonElement.disabled = normalizedZoom <= VIEWER_MIN_ZOOM;
764
+ viewerState.zoomInButtonElement.disabled = normalizedZoom >= VIEWER_MAX_ZOOM;
765
+ viewerState.resetButtonElement.disabled = normalizedZoom === VIEWER_DEFAULT_ZOOM;
1017
766
  updateMarkdownMermaidViewerCanvasSize(viewerState);
1018
767
  if (focalPoint) {
1019
768
  applyMarkdownMermaidViewerFocalPoint(viewerState, focalPoint);
@@ -1045,11 +794,11 @@ function applyMarkdownMermaidViewerFocalPoint(viewerState, focalPoint) {
1045
794
  }
1046
795
  function updateMarkdownMermaidViewerCanvasSize(viewerState) {
1047
796
  const viewportWidth = Math.max(
1048
- viewerState.viewportElement.clientWidth * IMAGE_VIEWER_FIT_RATIO,
797
+ viewerState.viewportElement.clientWidth * VIEWER_FIT_RATIO,
1049
798
  1
1050
799
  );
1051
800
  const viewportHeight = Math.max(
1052
- viewerState.viewportElement.clientHeight * IMAGE_VIEWER_FIT_RATIO,
801
+ viewerState.viewportElement.clientHeight * VIEWER_FIT_RATIO,
1053
802
  1
1054
803
  );
1055
804
  const naturalWidth = Math.max(viewerState.naturalWidth, 1);
@@ -1069,286 +818,453 @@ function updateMarkdownMermaidViewerCanvasSize(viewerState) {
1069
818
  innerSvgElement.style.display = "block";
1070
819
  }
1071
820
  }
1072
- function defaultScrollToHeading(targetElement) {
1073
- targetElement.scrollIntoView({ behavior: "smooth", block: "start" });
821
+
822
+ // src/code/mermaid.ts
823
+ var MERMAID_LANGUAGE_ID = "mermaid";
824
+ var MERMAID_HYDRATED_DATA_KEY = "scribdownMermaidHydrated";
825
+ var MERMAID_RENDER_STARTED_DATA_KEY = "scribdownMermaidRenderStarted";
826
+ var MERMAID_SOURCE_DATA_KEY = "scribdownMermaidSourceText";
827
+ var MERMAID_FALLBACK_DEFAULT_TEXT = "\u56FE\u8868\u6E32\u67D3\u5931\u8D25";
828
+ var MERMAID_RENDER_ID_PREFIX = "scribdown-mermaid-";
829
+ var mermaidRenderIdCounter = 0;
830
+ var MERMAID_FULLSCREEN_BUTTON_ARIA_LABEL = "\u5168\u5C4F\u67E5\u770B\u56FE\u8868";
831
+ var MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY = "scribdownMermaidFullscreenReady";
832
+ var MERMAID_VIEWER_SOURCE_DATA_KEY = "scribdownMermaidSource";
833
+ var mermaidLoaderPromise;
834
+ function hydrateMermaidBlocks(rootElement) {
835
+ const mermaidCodeElements = rootElement.querySelectorAll(
836
+ `pre > code.language-${MERMAID_LANGUAGE_ID}`
837
+ );
838
+ mermaidCodeElements.forEach((codeElement) => {
839
+ const preElement = codeElement.parentElement;
840
+ if (!preElement) {
841
+ return;
842
+ }
843
+ if (preElement.dataset[CODE_BLOCK_HYDRATED_DATA_KEY] === "true") {
844
+ return;
845
+ }
846
+ decorateMermaidBlock(preElement, codeElement);
847
+ });
848
+ kickOffPendingMermaidRenders(rootElement);
1074
849
  }
1075
- function hydrateMarkdown(rootElement, options = {}) {
1076
- hydrateMarkdownImages(rootElement);
1077
- hydrateMarkdownVideos(rootElement);
1078
- hydrateMermaidBlocks(rootElement);
1079
- hydrateCodeBlocks(rootElement);
1080
- hydrateToc(rootElement, options.scrollToHeading ?? defaultScrollToHeading);
850
+ function kickOffPendingMermaidRenders(rootElement) {
851
+ const pendingFigures = rootElement.querySelectorAll(
852
+ `.${MERMAID_FIGURE_CLASS_NAME}.${MERMAID_FIGURE_LOADING_CLASS_NAME}`
853
+ );
854
+ pendingFigures.forEach((figureElement) => {
855
+ if (!figureElement.isConnected) {
856
+ return;
857
+ }
858
+ if (figureElement.dataset[MERMAID_RENDER_STARTED_DATA_KEY] === "true") {
859
+ return;
860
+ }
861
+ const canvasElement = figureElement.querySelector(
862
+ `.${MERMAID_CANVAS_CLASS_NAME}`
863
+ );
864
+ const mermaidSource = figureElement.dataset[MERMAID_SOURCE_DATA_KEY] ?? "";
865
+ if (!canvasElement || mermaidSource.length === 0) {
866
+ return;
867
+ }
868
+ figureElement.dataset[MERMAID_RENDER_STARTED_DATA_KEY] = "true";
869
+ void renderMermaidIntoCanvas(figureElement, canvasElement, mermaidSource);
870
+ });
1081
871
  }
1082
- function mountMarkdownToolbar(container, options = {}) {
1083
- const ownerDocument = container.ownerDocument;
1084
- if (!ownerDocument) {
872
+ function decorateMermaidBlock(preElement, codeElement) {
873
+ if (preElement.dataset[MERMAID_HYDRATED_DATA_KEY] === "true") {
1085
874
  return;
1086
875
  }
1087
- if (typeof ownerDocument.defaultView?.scrollTo !== "function") {
1088
- return;
876
+ preElement.dataset[MERMAID_HYDRATED_DATA_KEY] = "true";
877
+ const ownerDocument = preElement.ownerDocument;
878
+ const mermaidSource = (codeElement.textContent ?? "").replace(/\n+$/u, "");
879
+ const figureElement = ownerDocument.createElement("figure");
880
+ figureElement.className = `${MERMAID_FIGURE_CLASS_NAME} ${MERMAID_FIGURE_LOADING_CLASS_NAME}`;
881
+ const chromeElement = ownerDocument.createElement("div");
882
+ chromeElement.className = MERMAID_CHROME_CLASS_NAME;
883
+ const labelElement = ownerDocument.createElement("span");
884
+ labelElement.className = MERMAID_LABEL_CLASS_NAME;
885
+ labelElement.textContent = MERMAID_LABEL_TEXT;
886
+ chromeElement.append(labelElement);
887
+ const bodyElement = ownerDocument.createElement("div");
888
+ bodyElement.className = MERMAID_BODY_CLASS_NAME;
889
+ const canvasElement = ownerDocument.createElement("div");
890
+ canvasElement.className = MERMAID_CANVAS_CLASS_NAME;
891
+ canvasElement.setAttribute("role", "img");
892
+ canvasElement.setAttribute("aria-label", MERMAID_LABEL_TEXT);
893
+ const fullscreenButtonElement = ownerDocument.createElement("button");
894
+ fullscreenButtonElement.type = "button";
895
+ fullscreenButtonElement.className = MERMAID_FULLSCREEN_BUTTON_CLASS_NAME;
896
+ fullscreenButtonElement.setAttribute("aria-label", MERMAID_FULLSCREEN_BUTTON_ARIA_LABEL);
897
+ fullscreenButtonElement.disabled = true;
898
+ fullscreenButtonElement.innerHTML = MERMAID_FULLSCREEN_ICON_SVG;
899
+ fullscreenButtonElement.addEventListener("click", handleMermaidFullscreenButtonClick);
900
+ bodyElement.append(canvasElement, fullscreenButtonElement);
901
+ figureElement.append(chromeElement, bodyElement);
902
+ const sourceLine = codeElement.getAttribute(SOURCE_LINE_DATA_ATTRIBUTE2);
903
+ if (sourceLine !== null) {
904
+ figureElement.setAttribute(SOURCE_LINE_DATA_ATTRIBUTE2, sourceLine);
1089
905
  }
1090
- applyContentWidth(ownerDocument, loadContentWidth());
1091
- mountPageToolbar(ownerDocument, container, options.scrollToHeading ?? defaultScrollToHeading);
906
+ figureElement.dataset[MERMAID_SOURCE_DATA_KEY] = mermaidSource;
907
+ preElement.replaceWith(figureElement);
1092
908
  }
1093
- function decorateCodeBlock(preElement) {
1094
- if (preElement.parentElement?.classList.contains(CODE_BLOCK_CLASS_NAME)) {
1095
- return;
909
+ async function loadMermaid() {
910
+ if (typeof window === "undefined" || typeof document === "undefined") {
911
+ return void 0;
1096
912
  }
1097
- if (preElement.dataset[CODE_BLOCK_HYDRATED_DATA_KEY] === "true") {
1098
- return;
913
+ if (!mermaidLoaderPromise) {
914
+ mermaidLoaderPromise = (async () => {
915
+ const mermaidModule = await import("mermaid");
916
+ const mermaidApi = mermaidModule.default;
917
+ mermaidApi.initialize({
918
+ startOnLoad: false,
919
+ securityLevel: "strict",
920
+ theme: "default",
921
+ flowchart: { useMaxWidth: false },
922
+ sequence: { useMaxWidth: false },
923
+ class: { useMaxWidth: false },
924
+ state: { useMaxWidth: false },
925
+ gantt: { useMaxWidth: false },
926
+ er: { useMaxWidth: false },
927
+ journey: { useMaxWidth: false },
928
+ pie: { useMaxWidth: false },
929
+ requirement: { useMaxWidth: false },
930
+ c4: { useMaxWidth: false },
931
+ mindmap: { useMaxWidth: false },
932
+ timeline: { useMaxWidth: false },
933
+ gitGraph: { useMaxWidth: false },
934
+ quadrantChart: { useMaxWidth: false },
935
+ xyChart: { useMaxWidth: false },
936
+ sankey: { useMaxWidth: false },
937
+ block: { useMaxWidth: false }
938
+ });
939
+ return mermaidApi;
940
+ })().catch((loadError) => {
941
+ mermaidLoaderPromise = void 0;
942
+ throw loadError;
943
+ });
1099
944
  }
1100
- const codeElement = preElement.querySelector(":scope > code");
1101
- if (!codeElement) {
1102
- return;
945
+ return mermaidLoaderPromise;
946
+ }
947
+ async function renderMermaidIntoCanvas(figureElement, canvasElement, mermaidSource) {
948
+ try {
949
+ const mermaidApi = await loadMermaid();
950
+ if (!mermaidApi) {
951
+ return;
952
+ }
953
+ mermaidRenderIdCounter += 1;
954
+ const renderId = `${MERMAID_RENDER_ID_PREFIX}${mermaidRenderIdCounter}`;
955
+ const { svg, bindFunctions } = await mermaidApi.render(renderId, mermaidSource);
956
+ canvasElement.innerHTML = svg;
957
+ bindFunctions?.(canvasElement);
958
+ figureElement.classList.remove(MERMAID_FIGURE_LOADING_CLASS_NAME);
959
+ figureElement.classList.add(MERMAID_FIGURE_LOADED_CLASS_NAME);
960
+ figureElement.dataset[MERMAID_VIEWER_SOURCE_DATA_KEY] = svg;
961
+ figureElement.dataset[MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY] = "true";
962
+ const fullscreenButtonElement = figureElement.querySelector(
963
+ `.${MERMAID_FULLSCREEN_BUTTON_CLASS_NAME}`
964
+ );
965
+ if (fullscreenButtonElement) {
966
+ fullscreenButtonElement.disabled = false;
967
+ }
968
+ } catch (renderError) {
969
+ showMermaidFallback(figureElement, canvasElement, mermaidSource, renderError);
1103
970
  }
1104
- preElement.dataset[CODE_BLOCK_HYDRATED_DATA_KEY] = "true";
1105
- const ownerDocument = preElement.ownerDocument;
1106
- const languageId = resolveCodeBlockLanguageId(codeElement);
1107
- const languageLabel = resolveCodeBlockLanguageLabel(languageId);
1108
- const originalCodeText = codeElement.textContent ?? "";
1109
- const codeLineCount = rewriteCodeBlockLines(codeElement, ownerDocument);
1110
- const chromeElement = ownerDocument.createElement("div");
1111
- chromeElement.className = CODE_BLOCK_CHROME_CLASS_NAME;
1112
- const langElement = ownerDocument.createElement("span");
1113
- langElement.className = CODE_BLOCK_LANG_CLASS_NAME;
1114
- langElement.textContent = languageLabel;
1115
- chromeElement.append(langElement);
1116
- const copyButtonElement = createCodeBlockCopyButton(ownerDocument);
1117
- chromeElement.append(copyButtonElement);
1118
- const figureElement = ownerDocument.createElement("figure");
1119
- figureElement.className = CODE_BLOCK_CLASS_NAME;
1120
- figureElement.dataset.scribdownCodeLang = languageId;
1121
- const bodyElement = ownerDocument.createElement("div");
1122
- bodyElement.className = CODE_BLOCK_BODY_CLASS_NAME;
1123
- const gutterElement = createCodeBlockLineNumberGutter(ownerDocument, codeLineCount);
1124
- preElement.replaceWith(figureElement);
1125
- bodyElement.append(gutterElement, preElement);
1126
- figureElement.append(chromeElement, bodyElement);
1127
- const codeBlockSourceLine = codeElement.getAttribute(SOURCE_LINE_DATA_ATTRIBUTE);
1128
- if (codeBlockSourceLine !== null) {
1129
- figureElement.setAttribute(SOURCE_LINE_DATA_ATTRIBUTE, codeBlockSourceLine);
1130
- codeElement.removeAttribute(SOURCE_LINE_DATA_ATTRIBUTE);
971
+ }
972
+ function showMermaidFallback(figureElement, canvasElement, mermaidSource, renderError) {
973
+ const ownerDocument = figureElement.ownerDocument;
974
+ figureElement.classList.remove(MERMAID_FIGURE_LOADING_CLASS_NAME);
975
+ figureElement.classList.add(MERMAID_FIGURE_FAILED_CLASS_NAME);
976
+ const fullscreenButtonElement = figureElement.querySelector(
977
+ `.${MERMAID_FULLSCREEN_BUTTON_CLASS_NAME}`
978
+ );
979
+ fullscreenButtonElement?.remove();
980
+ canvasElement.replaceChildren();
981
+ cleanupOrphanMermaidNodes(ownerDocument);
982
+ const fallbackElement = ownerDocument.createElement("div");
983
+ fallbackElement.className = MERMAID_FALLBACK_CLASS_NAME;
984
+ const iconElement = ownerDocument.createElement("span");
985
+ iconElement.className = MERMAID_FALLBACK_ICON_CLASS_NAME;
986
+ iconElement.setAttribute("aria-hidden", "true");
987
+ fallbackElement.append(iconElement);
988
+ const textElement = ownerDocument.createElement("p");
989
+ textElement.className = MERMAID_FALLBACK_TEXT_CLASS_NAME;
990
+ textElement.textContent = MERMAID_FALLBACK_DEFAULT_TEXT;
991
+ fallbackElement.append(textElement);
992
+ const errorMessage = extractErrorMessage(renderError);
993
+ if (errorMessage) {
994
+ const messageElement = ownerDocument.createElement("p");
995
+ messageElement.className = MERMAID_FALLBACK_SOURCE_CLASS_NAME;
996
+ messageElement.textContent = errorMessage;
997
+ fallbackElement.append(messageElement);
998
+ }
999
+ if (mermaidSource.length > 0) {
1000
+ const sourceElement = ownerDocument.createElement("pre");
1001
+ sourceElement.className = MERMAID_FALLBACK_SOURCE_CLASS_NAME;
1002
+ sourceElement.textContent = mermaidSource;
1003
+ fallbackElement.append(sourceElement);
1004
+ }
1005
+ canvasElement.replaceWith(fallbackElement);
1006
+ }
1007
+ function extractErrorMessage(renderError) {
1008
+ if (renderError instanceof Error) {
1009
+ return renderError.message;
1131
1010
  }
1132
- copyButtonElement.dataset.scribdownCodeSource = originalCodeText;
1133
- copyButtonElement.addEventListener("click", handleCodeBlockCopyClick);
1011
+ if (typeof renderError === "string") {
1012
+ return renderError;
1013
+ }
1014
+ return "";
1134
1015
  }
1135
- function resolveCodeBlockLanguageId(codeElement) {
1136
- const languageClassName = Array.from(codeElement.classList).find(
1137
- (className) => className.startsWith("language-")
1016
+ function cleanupOrphanMermaidNodes(ownerDocument) {
1017
+ const orphanNodes = ownerDocument.querySelectorAll(
1018
+ `body > [id^="${MERMAID_RENDER_ID_PREFIX}"], body > div[id^="d"][id*="mermaid"]`
1138
1019
  );
1139
- if (!languageClassName) {
1140
- return "";
1141
- }
1142
- return languageClassName.slice("language-".length).toLowerCase();
1020
+ orphanNodes.forEach((orphanNode) => {
1021
+ orphanNode.remove();
1022
+ });
1143
1023
  }
1144
- function resolveCodeBlockLanguageLabel(languageId) {
1145
- if (!languageId) {
1146
- return CODE_LANGUAGE_DEFAULT_LABEL;
1024
+ var MERMAID_FULLSCREEN_ICON_SVG = '<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false"><path d="M4 9 V4 H9 M15 4 H20 V9 M20 15 V20 H15 M9 20 H4 V15" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
1025
+ function handleMermaidFullscreenButtonClick(event) {
1026
+ const buttonElement = event.currentTarget;
1027
+ const figureElement = buttonElement.closest(`.${MERMAID_FIGURE_CLASS_NAME}`);
1028
+ if (!figureElement) {
1029
+ return;
1147
1030
  }
1148
- const mappedLabel = CODE_LANGUAGE_DISPLAY_NAMES[languageId];
1149
- if (mappedLabel) {
1150
- return mappedLabel;
1031
+ if (figureElement.dataset[MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY] !== "true") {
1032
+ return;
1151
1033
  }
1152
- return languageId.charAt(0).toUpperCase() + languageId.slice(1);
1034
+ const svgSource = figureElement.dataset[MERMAID_VIEWER_SOURCE_DATA_KEY] ?? "";
1035
+ if (svgSource.length === 0) {
1036
+ return;
1037
+ }
1038
+ event.preventDefault();
1039
+ event.stopPropagation();
1040
+ openMarkdownMermaidViewer(figureElement.ownerDocument, svgSource);
1153
1041
  }
1154
- function rewriteCodeBlockLines(codeElement, ownerDocument) {
1155
- const shikiLineElements = codeElement.querySelectorAll(":scope > span.line");
1156
- if (shikiLineElements.length > 0) {
1157
- const shikiLineNodes = Array.from(shikiLineElements);
1158
- shikiLineElements.forEach((lineElement) => {
1159
- lineElement.classList.add(CODE_BLOCK_LINE_CLASS_NAME);
1160
- if (lineElement.textContent === "") {
1161
- lineElement.textContent = CODE_BLOCK_EMPTY_LINE_PLACEHOLDER;
1162
- }
1042
+
1043
+ // src/code/code-highlight.ts
1044
+ var CODE_HIGHLIGHTER_THEME = "github-light";
1045
+ var codeHighlighterPromise;
1046
+ var codeHighlighterLazyLoadPromises = /* @__PURE__ */ new Map();
1047
+ var CODE_BLOCK_HIGHLIGHT_PATTERN = /<pre([^>]*)><code class="language-([\w-]+)"([^>]*)>([\s\S]*?)<\/code><\/pre>/g;
1048
+ var SHIKI_CODE_INNER_PATTERN = /<code[^>]*>([\s\S]*?)<\/code>/u;
1049
+ async function getCodeHighlighter() {
1050
+ if (!codeHighlighterPromise) {
1051
+ codeHighlighterPromise = createHighlighterCore({
1052
+ themes: [githubLightTheme],
1053
+ // eager loaders 通过动态 import 拆为独立 chunk,初始化时并发拉取;
1054
+ // 主 bundle 不再内联 grammar JSON,体积更小,多个 grammar 也可并行加载。
1055
+ langs: CODE_HIGHLIGHTER_EAGER_LOADERS,
1056
+ // oniguruma 引擎 + wasm;shiki/wasm 内部走 base64 内联,扩展环境无需额外资源声明。
1057
+ engine: createOnigurumaEngine(import("shiki/wasm"))
1163
1058
  });
1164
- codeElement.replaceChildren(...shikiLineNodes);
1165
- return shikiLineNodes.length;
1166
- }
1167
- const codeText = codeElement.textContent ?? "";
1168
- const trimmedCodeText = codeText.replace(/\n+$/u, "");
1169
- const codeLines = trimmedCodeText.split("\n");
1170
- while (codeElement.firstChild) {
1171
- codeElement.removeChild(codeElement.firstChild);
1172
1059
  }
1173
- codeLines.forEach((lineText) => {
1174
- const lineElement = ownerDocument.createElement("span");
1175
- lineElement.className = CODE_BLOCK_LINE_CLASS_NAME;
1176
- if (lineText.length === 0) {
1177
- lineElement.textContent = CODE_BLOCK_EMPTY_LINE_PLACEHOLDER;
1178
- } else {
1179
- lineElement.textContent = lineText;
1180
- }
1181
- codeElement.append(lineElement);
1182
- });
1183
- return codeLines.length;
1060
+ return codeHighlighterPromise;
1184
1061
  }
1185
- function createCodeBlockLineNumberGutter(ownerDocument, lineCount) {
1186
- const gutterElement = ownerDocument.createElement("div");
1187
- gutterElement.className = CODE_BLOCK_GUTTER_CLASS_NAME;
1188
- for (let lineIndex = 0; lineIndex < lineCount; lineIndex += 1) {
1189
- const lineNumberText = String(lineIndex + 1);
1190
- const lineNumberElement = ownerDocument.createElement("span");
1191
- lineNumberElement.className = CODE_BLOCK_GUTTER_LINE_CLASS_NAME;
1192
- lineNumberElement.textContent = lineNumberText;
1193
- gutterElement.append(lineNumberElement);
1062
+ async function highlightMarkdownCodeBlocks(html) {
1063
+ if (!CODE_BLOCK_HIGHLIGHT_PATTERN.test(html)) {
1064
+ return html;
1194
1065
  }
1195
- return gutterElement;
1196
- }
1197
- var CODE_BLOCK_COPY_ICON_COPY_PATHS = '<path d="M8.4 6.8 C10.4 6.5 12.6 6.7 14.6 6.6 C16 6.6 16.5 7.2 16.6 8.6 C16.7 10.8 16.6 13.2 16.6 15.4 C16.5 16.6 15.8 17.2 14.6 17.2 C12.4 17.3 10.2 17.2 8 17.2 C6.6 17.2 6.2 16.6 6.2 15.4 C6.2 13.2 6.2 11 6.2 8.8 C6.2 7.4 6.8 6.8 8.4 6.8 Z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round"/><path d="M8.6 7.4 C10.6 7.2 12.6 7.4 14.4 7.3" fill="none" stroke="currentColor" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" opacity="0.42"/><path d="M3.6 12.8 C3.4 11.0 3.4 9.0 3.5 7.0 C3.5 5.0 3.4 3.6 5.2 3.4 C7.2 3.2 9.2 3.4 11.2 3.4 C12.6 3.4 13.2 4.0 13.3 5.0" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round"/><path d="M4.4 12.4 C4.2 10.6 4.2 8.8 4.2 7.0" fill="none" stroke="currentColor" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" opacity="0.42"/>';
1198
- var CODE_BLOCK_COPY_ICON_CHECK_PATHS = '<path d="M4.2 10.6 C5.6 11.8 6.8 13.2 8.2 14.6 C10.4 11.6 13.0 8.6 16.0 5.6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" stroke-linecap="round"/><path d="M4.6 11.4 C5.6 12.4 6.8 13.6 7.8 14.6 C10.2 12.2 12.6 9.6 15.2 7.0" fill="none" stroke="currentColor" stroke-width="1" stroke-linejoin="round" stroke-linecap="round" opacity="0.42"/>';
1199
- function createCodeBlockCopyButton(ownerDocument) {
1200
- const copyButtonElement = ownerDocument.createElement("button");
1201
- copyButtonElement.type = "button";
1202
- copyButtonElement.className = CODE_BLOCK_COPY_CLASS_NAME;
1203
- copyButtonElement.setAttribute("aria-label", CODE_BLOCK_COPY_ARIA_LABEL);
1204
- const copyIconElement = ownerDocument.createElementNS("http://www.w3.org/2000/svg", "svg");
1205
- copyIconElement.setAttribute(
1206
- "class",
1207
- `${CODE_BLOCK_COPY_ICON_CLASS_NAME} ${CODE_BLOCK_COPY_ICON_COPY_CLASS_NAME}`
1208
- );
1209
- copyIconElement.setAttribute("viewBox", "0 0 20 20");
1210
- copyIconElement.setAttribute("aria-hidden", "true");
1211
- copyIconElement.setAttribute("focusable", "false");
1212
- copyIconElement.innerHTML = CODE_BLOCK_COPY_ICON_COPY_PATHS;
1213
- copyButtonElement.append(copyIconElement);
1214
- const checkIconElement = ownerDocument.createElementNS("http://www.w3.org/2000/svg", "svg");
1215
- checkIconElement.setAttribute(
1216
- "class",
1217
- `${CODE_BLOCK_COPY_ICON_CLASS_NAME} ${CODE_BLOCK_COPY_ICON_CHECK_CLASS_NAME}`
1218
- );
1219
- checkIconElement.setAttribute("viewBox", "0 0 20 20");
1220
- checkIconElement.setAttribute("aria-hidden", "true");
1221
- checkIconElement.setAttribute("focusable", "false");
1222
- checkIconElement.innerHTML = CODE_BLOCK_COPY_ICON_CHECK_PATHS;
1223
- copyButtonElement.append(checkIconElement);
1224
- return copyButtonElement;
1225
- }
1226
- function handleCodeBlockCopyClick(event) {
1227
- const copyButtonElement = event.currentTarget;
1228
- const codeText = copyButtonElement.dataset.scribdownCodeSource ?? "";
1229
- void writeCodeBlockTextToClipboard(codeText).then((isSucceeded) => {
1230
- if (isSucceeded) {
1231
- flashCodeBlockCopyButton(copyButtonElement);
1232
- }
1233
- });
1066
+ CODE_BLOCK_HIGHLIGHT_PATTERN.lastIndex = 0;
1067
+ const highlighter = await getCodeHighlighter();
1068
+ let resultHtml = "";
1069
+ let lastIndex = 0;
1070
+ for (const match of html.matchAll(CODE_BLOCK_HIGHLIGHT_PATTERN)) {
1071
+ const [matched, preAttributes, rawLanguage, codeAttributes, encodedCode] = match;
1072
+ const matchStart = match.index ?? 0;
1073
+ resultHtml += html.slice(lastIndex, matchStart);
1074
+ resultHtml += await highlightSingleCodeBlock(highlighter, {
1075
+ rawLanguage,
1076
+ encodedCode,
1077
+ matchedHtml: matched,
1078
+ preAttributes,
1079
+ codeAttributes
1080
+ });
1081
+ lastIndex = matchStart + matched.length;
1082
+ }
1083
+ resultHtml += html.slice(lastIndex);
1084
+ return resultHtml;
1234
1085
  }
1235
- async function writeCodeBlockTextToClipboard(codeText) {
1236
- if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
1237
- try {
1238
- await navigator.clipboard.writeText(codeText);
1239
- return true;
1240
- } catch {
1241
- }
1086
+ async function highlightSingleCodeBlock(highlighter, input) {
1087
+ const { rawLanguage, encodedCode, matchedHtml, preAttributes, codeAttributes } = input;
1088
+ const normalizedLanguage = rawLanguage.toLowerCase();
1089
+ if (normalizedLanguage === MERMAID_LANGUAGE_ID) {
1090
+ return matchedHtml;
1242
1091
  }
1243
- if (typeof document === "undefined") {
1244
- return false;
1092
+ const resolvedLanguage = await ensureHighlighterLanguage(highlighter, normalizedLanguage);
1093
+ const codeText = decodeHtmlEntities(encodedCode);
1094
+ try {
1095
+ const highlightedHtml = highlighter.codeToHtml(codeText, {
1096
+ lang: resolvedLanguage,
1097
+ theme: CODE_HIGHLIGHTER_THEME
1098
+ });
1099
+ const innerMatch = highlightedHtml.match(SHIKI_CODE_INNER_PATTERN);
1100
+ const innerHtml = innerMatch?.[1] ?? encodedCode;
1101
+ return `<pre${preAttributes}><code class="language-${rawLanguage}"${codeAttributes}>${innerHtml}</code></pre>`;
1102
+ } catch {
1103
+ return matchedHtml;
1245
1104
  }
1246
- const fallbackTextarea = document.createElement("textarea");
1247
- fallbackTextarea.value = codeText;
1248
- fallbackTextarea.setAttribute("readonly", "");
1249
- fallbackTextarea.style.position = "fixed";
1250
- fallbackTextarea.style.opacity = "0";
1251
- fallbackTextarea.style.pointerEvents = "none";
1252
- document.body.append(fallbackTextarea);
1253
- fallbackTextarea.select();
1254
- const isSucceeded = document.execCommand("copy");
1255
- fallbackTextarea.remove();
1256
- return isSucceeded;
1257
- }
1258
- function flashCodeBlockCopyButton(copyButtonElement) {
1259
- copyButtonElement.dataset.scribdownCodeCopied = "true";
1260
- copyButtonElement.setAttribute("aria-label", CODE_BLOCK_COPY_ARIA_LABEL_COPIED);
1261
- window.setTimeout(() => {
1262
- delete copyButtonElement.dataset.scribdownCodeCopied;
1263
- copyButtonElement.setAttribute("aria-label", CODE_BLOCK_COPY_ARIA_LABEL);
1264
- }, CODE_BLOCK_COPY_RESTORE_DELAY_MS);
1265
1105
  }
1266
- function bindMarkdownImageState(imageElement) {
1267
- updateMarkdownImageState(imageElement);
1268
- if (imageElement.dataset[IMAGE_HYDRATED_DATA_KEY] === "true") {
1269
- return;
1106
+ async function ensureHighlighterLanguage(highlighter, normalizedLanguage) {
1107
+ if (normalizedLanguage.length === 0) {
1108
+ return "text";
1270
1109
  }
1271
- imageElement.dataset[IMAGE_HYDRATED_DATA_KEY] = "true";
1272
- imageElement.addEventListener("load", handleMarkdownImageLoad);
1273
- imageElement.addEventListener("error", handleMarkdownImageError);
1274
- bindMarkdownImageViewer(imageElement);
1110
+ if (highlighter.getLoadedLanguages().includes(normalizedLanguage)) {
1111
+ return normalizedLanguage;
1112
+ }
1113
+ const lazyLoader = CODE_HIGHLIGHTER_LAZY_LANGS[normalizedLanguage];
1114
+ if (!lazyLoader) {
1115
+ return "text";
1116
+ }
1117
+ let pending = codeHighlighterLazyLoadPromises.get(normalizedLanguage);
1118
+ if (!pending) {
1119
+ pending = (async () => {
1120
+ const grammarModule = await lazyLoader();
1121
+ await highlighter.loadLanguage(grammarModule.default);
1122
+ })();
1123
+ codeHighlighterLazyLoadPromises.set(normalizedLanguage, pending);
1124
+ }
1125
+ try {
1126
+ await pending;
1127
+ return normalizedLanguage;
1128
+ } catch {
1129
+ codeHighlighterLazyLoadPromises.delete(normalizedLanguage);
1130
+ return "text";
1131
+ }
1132
+ }
1133
+ var HTML_NAMED_ENTITIES = {
1134
+ lt: "<",
1135
+ gt: ">",
1136
+ quot: '"',
1137
+ apos: "'",
1138
+ amp: "&"
1139
+ };
1140
+ var HTML_ENTITY_PATTERN = /&(?:(lt|gt|quot|apos|amp)|#x([0-9a-fA-F]+)|#(\d+));/g;
1141
+ function decodeHtmlEntities(encodedText) {
1142
+ return encodedText.replace(
1143
+ HTML_ENTITY_PATTERN,
1144
+ (matched, namedEntity, hexCodePoint, decimalCodePoint) => {
1145
+ if (namedEntity) {
1146
+ return HTML_NAMED_ENTITIES[namedEntity] ?? matched;
1147
+ }
1148
+ if (hexCodePoint) {
1149
+ return safeFromCodePoint(parseInt(hexCodePoint, 16), matched);
1150
+ }
1151
+ if (decimalCodePoint) {
1152
+ return safeFromCodePoint(parseInt(decimalCodePoint, 10), matched);
1153
+ }
1154
+ return matched;
1155
+ }
1156
+ );
1275
1157
  }
1276
- function handleMarkdownImageLoad(event) {
1277
- const imageElement = event.currentTarget;
1278
- updateMarkdownImageState(imageElement);
1158
+ function safeFromCodePoint(codePoint, fallback) {
1159
+ if (!Number.isFinite(codePoint) || codePoint < 0 || codePoint > 1114111) {
1160
+ return fallback;
1161
+ }
1162
+ try {
1163
+ return String.fromCodePoint(codePoint);
1164
+ } catch {
1165
+ return fallback;
1166
+ }
1279
1167
  }
1280
- function handleMarkdownImageError(event) {
1281
- const imageElement = event.currentTarget;
1282
- updateMarkdownImageState(imageElement);
1168
+
1169
+ // src/core/source-line.ts
1170
+ import { SOURCE_LINE_DATA_ATTRIBUTE as SOURCE_LINE_DATA_ATTRIBUTE3 } from "@scribdown/shared";
1171
+ var SOURCE_LINE_HAST_PROPERTY = SOURCE_LINE_DATA_ATTRIBUTE3.replace(
1172
+ /-([a-z])/gu,
1173
+ (_match, letter) => letter.toUpperCase()
1174
+ );
1175
+ var BLOCK_CONTAINER_NODE_TYPES = /* @__PURE__ */ new Set(["list", "listItem", "blockquote"]);
1176
+ function remarkSourceLine() {
1177
+ return (tree) => {
1178
+ const blockNodes = tree.children ?? [];
1179
+ blockNodes.forEach((blockNode) => {
1180
+ annotateSourceLine(blockNode);
1181
+ annotateNestedBlockSourceLines(blockNode);
1182
+ });
1183
+ };
1283
1184
  }
1284
- function updateMarkdownImageState(imageElement) {
1285
- const frameElement = imageElement.closest(`.${IMAGE_FRAME_CLASS_NAME}`);
1286
- if (!frameElement) {
1185
+ function annotateSourceLine(node) {
1186
+ const startLine = node.position?.start.line;
1187
+ if (typeof startLine !== "number") {
1287
1188
  return;
1288
1189
  }
1289
- const isFailed = imageElement.complete && imageElement.naturalWidth === 0;
1290
- const isLoaded = imageElement.complete && imageElement.naturalWidth > 0;
1291
- frameElement.classList.toggle(IMAGE_FRAME_FAILED_CLASS_NAME, isFailed);
1292
- frameElement.classList.toggle(IMAGE_FRAME_LOADED_CLASS_NAME, isLoaded);
1190
+ const nodeData = node.data ?? {};
1191
+ const hProperties = nodeData.hProperties ?? {};
1192
+ hProperties[SOURCE_LINE_HAST_PROPERTY] = startLine;
1193
+ nodeData.hProperties = hProperties;
1194
+ node.data = nodeData;
1293
1195
  }
1294
- function bindMarkdownVideoState(videoElement) {
1295
- updateMarkdownVideoState(videoElement);
1296
- if (videoElement.dataset[VIDEO_HYDRATED_DATA_KEY] === "true") {
1196
+ function annotateNestedBlockSourceLines(node) {
1197
+ if (!BLOCK_CONTAINER_NODE_TYPES.has(node.type)) {
1297
1198
  return;
1298
1199
  }
1299
- videoElement.dataset[VIDEO_HYDRATED_DATA_KEY] = "true";
1300
- videoElement.addEventListener("loadeddata", handleMarkdownVideoLoaded);
1301
- videoElement.addEventListener("error", handleMarkdownVideoError);
1302
- videoElement.querySelectorAll("source").forEach((sourceElement) => {
1303
- sourceElement.addEventListener("error", handleMarkdownVideoSourceError);
1200
+ const childNodes = node.children ?? [];
1201
+ childNodes.forEach((childNode) => {
1202
+ annotateSourceLine(childNode);
1203
+ annotateNestedBlockSourceLines(childNode);
1304
1204
  });
1305
1205
  }
1306
- function handleMarkdownVideoLoaded(event) {
1307
- const videoElement = event.currentTarget;
1308
- updateMarkdownVideoState(videoElement);
1309
- }
1310
- function handleMarkdownVideoError(event) {
1311
- const videoElement = event.currentTarget;
1312
- markMarkdownVideoFailed(videoElement);
1313
- }
1314
- function handleMarkdownVideoSourceError(event) {
1315
- const sourceElement = event.currentTarget;
1316
- const videoElement = sourceElement.closest(
1317
- `video.${VIDEO_ELEMENT_CLASS_NAME}`
1318
- );
1319
- if (!videoElement) {
1206
+
1207
+ // src/media/images.ts
1208
+ import {
1209
+ IMAGE_CAPTION_CLASS_NAME,
1210
+ IMAGE_ELEMENT_CLASS_NAME,
1211
+ IMAGE_FALLBACK_CLASS_NAME,
1212
+ IMAGE_FALLBACK_ICON_CLASS_NAME,
1213
+ IMAGE_FALLBACK_SOURCE_CLASS_NAME,
1214
+ IMAGE_FALLBACK_TEXT_CLASS_NAME,
1215
+ IMAGE_FIGURE_CLASS_NAME,
1216
+ IMAGE_FRAME_CLASS_NAME,
1217
+ IMAGE_FRAME_FAILED_CLASS_NAME,
1218
+ IMAGE_FRAME_LOADED_CLASS_NAME
1219
+ } from "@scribdown/shared";
1220
+
1221
+ // src/core/ast.ts
1222
+ function visitMarkdownNode(node, visitor) {
1223
+ visitor(node);
1224
+ if (!node.children) {
1320
1225
  return;
1321
1226
  }
1322
- if (videoElement.networkState !== videoElement.NETWORK_NO_SOURCE) {
1323
- return;
1227
+ const childNodes = node.children;
1228
+ for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {
1229
+ visitMarkdownNode(childNodes[childIndex], visitor);
1324
1230
  }
1325
- markMarkdownVideoFailed(videoElement);
1326
1231
  }
1327
- function updateMarkdownVideoState(videoElement) {
1328
- const frameElement = videoElement.closest(
1329
- `.${VIDEO_FRAME_CLASS_NAME}`
1330
- );
1331
- if (!frameElement) {
1332
- return;
1232
+ function extractNodeText(node) {
1233
+ if (node.type === "text" || node.type === "inlineCode") {
1234
+ return node.value ?? "";
1333
1235
  }
1334
- const isFailed = videoElement.error !== null || videoElement.networkState === videoElement.NETWORK_NO_SOURCE;
1335
- const isLoaded = videoElement.readyState >= videoElement.HAVE_CURRENT_DATA;
1336
- frameElement.classList.toggle(VIDEO_FRAME_FAILED_CLASS_NAME, isFailed);
1337
- frameElement.classList.toggle(
1338
- VIDEO_FRAME_LOADED_CLASS_NAME,
1339
- isLoaded && !isFailed
1340
- );
1341
- }
1342
- function markMarkdownVideoFailed(videoElement) {
1343
- const frameElement = videoElement.closest(
1344
- `.${VIDEO_FRAME_CLASS_NAME}`
1345
- );
1346
- if (!frameElement) {
1347
- return;
1236
+ if (isImageNode(node)) {
1237
+ return node.alt ?? "";
1348
1238
  }
1349
- frameElement.classList.add(VIDEO_FRAME_FAILED_CLASS_NAME);
1350
- frameElement.classList.remove(VIDEO_FRAME_LOADED_CLASS_NAME);
1239
+ if (!node.children) {
1240
+ return "";
1241
+ }
1242
+ return node.children.map(extractNodeText).join("");
1351
1243
  }
1244
+ function isImageNode(node) {
1245
+ return node.type === "image" || node.type === "imageReference";
1246
+ }
1247
+
1248
+ // src/media/image-viewer.ts
1249
+ import {
1250
+ IMAGE_VIEWER_BUTTON_CLASS_NAME,
1251
+ IMAGE_VIEWER_CAPTION_CLASS_NAME,
1252
+ IMAGE_VIEWER_CAPTION_GROUP_CLASS_NAME,
1253
+ IMAGE_VIEWER_CHROME_CLASS_NAME,
1254
+ IMAGE_VIEWER_CLOSE_BUTTON_CLASS_NAME,
1255
+ IMAGE_VIEWER_CONTROLS_CLASS_NAME,
1256
+ IMAGE_VIEWER_DIALOG_CLASS_NAME,
1257
+ IMAGE_VIEWER_DRAGGING_CLASS_NAME,
1258
+ IMAGE_VIEWER_HINT_CLASS_NAME,
1259
+ IMAGE_VIEWER_IMAGE_CLASS_NAME,
1260
+ IMAGE_VIEWER_VIEWPORT_CLASS_NAME,
1261
+ IMAGE_VIEWER_ZOOMED_CLASS_NAME,
1262
+ IMAGE_VIEWER_ZOOM_VALUE_CLASS_NAME
1263
+ } from "@scribdown/shared";
1264
+ var IMAGE_VIEWER_HINT_TEXT = "\u5FEB\u6377\u952E\uFF1A+/= \u653E\u5927 \xB7 - \u7F29\u5C0F \xB7 0 \u91CD\u7F6E \xB7 Esc \u5173\u95ED \xB7 \u9F20\u6807\u62D6\u62FD\u53EF\u5E73\u79FB \xB7 Ctrl/\u2318 + \u6EDA\u8F6E\u7F29\u653E";
1265
+ var IMAGE_VIEWER_FALLBACK_LABEL = "\u67E5\u770B\u5168\u56FE";
1266
+ var imageViewerStateByDocument = /* @__PURE__ */ new WeakMap();
1267
+ var imageViewerStateByDialogElement = /* @__PURE__ */ new WeakMap();
1352
1268
  function bindMarkdownImageViewer(imageElement) {
1353
1269
  const linkElement = imageElement.closest("a[href]");
1354
1270
  if (linkElement) {
@@ -1402,7 +1318,7 @@ function openMarkdownImageViewer(sourceImageElement) {
1402
1318
  );
1403
1319
  viewerState.viewportElement.scrollLeft = 0;
1404
1320
  viewerState.viewportElement.scrollTop = 0;
1405
- viewerState.zoomValue = IMAGE_VIEWER_DEFAULT_ZOOM;
1321
+ viewerState.zoomValue = VIEWER_DEFAULT_ZOOM;
1406
1322
  showMarkdownImageViewerDialog(viewerState);
1407
1323
  requestMarkdownImageViewerLayout(viewerState);
1408
1324
  }
@@ -1424,21 +1340,21 @@ function createMarkdownImageViewerState(ownerDocument) {
1424
1340
  const controlsElement = ownerDocument.createElement("div");
1425
1341
  const zoomOutButtonElement = createMarkdownImageViewerButton(ownerDocument, {
1426
1342
  ariaLabel: "\u7F29\u5C0F\u56FE\u7247",
1427
- text: IMAGE_VIEWER_ZOOM_OUT_TEXT
1343
+ text: VIEWER_ZOOM_OUT_TEXT
1428
1344
  });
1429
1345
  const zoomValueElement = ownerDocument.createElement("span");
1430
1346
  const zoomInButtonElement = createMarkdownImageViewerButton(ownerDocument, {
1431
1347
  ariaLabel: "\u653E\u5927\u56FE\u7247",
1432
- text: IMAGE_VIEWER_ZOOM_IN_TEXT
1348
+ text: VIEWER_ZOOM_IN_TEXT
1433
1349
  });
1434
1350
  const resetButtonElement = createMarkdownImageViewerButton(ownerDocument, {
1435
1351
  ariaLabel: "\u91CD\u7F6E\u7F29\u653E",
1436
- text: IMAGE_VIEWER_RESET_TEXT
1352
+ text: VIEWER_RESET_TEXT
1437
1353
  });
1438
1354
  const closeButtonElement = createMarkdownImageViewerButton(ownerDocument, {
1439
1355
  ariaLabel: "\u5173\u95ED\u5168\u56FE\u67E5\u770B",
1440
1356
  className: IMAGE_VIEWER_CLOSE_BUTTON_CLASS_NAME,
1441
- text: IMAGE_VIEWER_CLOSE_TEXT
1357
+ text: VIEWER_CLOSE_TEXT
1442
1358
  });
1443
1359
  const viewportElement = ownerDocument.createElement("div");
1444
1360
  const imageElement = ownerDocument.createElement("img");
@@ -1459,7 +1375,7 @@ function createMarkdownImageViewerState(ownerDocument) {
1459
1375
  viewportElement,
1460
1376
  zoomInButtonElement,
1461
1377
  zoomOutButtonElement,
1462
- zoomValue: IMAGE_VIEWER_DEFAULT_ZOOM,
1378
+ zoomValue: VIEWER_DEFAULT_ZOOM,
1463
1379
  zoomValueElement
1464
1380
  };
1465
1381
  const ownerWindow = ownerDocument.defaultView;
@@ -1506,7 +1422,7 @@ function createMarkdownImageViewerState(ownerDocument) {
1506
1422
  if (ownerWindow) {
1507
1423
  ownerWindow.addEventListener("resize", handleMarkdownImageViewerWindowResize);
1508
1424
  }
1509
- updateMarkdownImageViewerZoom(viewerState, IMAGE_VIEWER_DEFAULT_ZOOM);
1425
+ updateMarkdownImageViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);
1510
1426
  return viewerState;
1511
1427
  }
1512
1428
  function createMarkdownImageViewerButton(ownerDocument, options) {
@@ -1555,274 +1471,670 @@ function handleMarkdownImageViewerClose(event) {
1555
1471
  if (!viewerState) {
1556
1472
  return;
1557
1473
  }
1558
- resetMarkdownImageViewerAfterClose(viewerState);
1474
+ resetMarkdownImageViewerAfterClose(viewerState);
1475
+ }
1476
+ function handleMarkdownImageViewerKeyDown(event) {
1477
+ const viewerState = getMarkdownImageViewerStateFromEvent(event);
1478
+ if (!viewerState) {
1479
+ return;
1480
+ }
1481
+ if (event.key === "+" || event.key === "=") {
1482
+ event.preventDefault();
1483
+ updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + VIEWER_ZOOM_STEP);
1484
+ return;
1485
+ }
1486
+ if (event.key === "-") {
1487
+ event.preventDefault();
1488
+ updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue - VIEWER_ZOOM_STEP);
1489
+ return;
1490
+ }
1491
+ if (event.key === "0") {
1492
+ event.preventDefault();
1493
+ updateMarkdownImageViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);
1494
+ return;
1495
+ }
1496
+ if (event.key === "Escape") {
1497
+ event.preventDefault();
1498
+ closeMarkdownImageViewer(viewerState);
1499
+ }
1500
+ }
1501
+ function handleMarkdownImageViewerWheel(event) {
1502
+ if (!event.ctrlKey && !event.metaKey) {
1503
+ return;
1504
+ }
1505
+ const viewerState = getMarkdownImageViewerStateFromEvent(event);
1506
+ if (!viewerState) {
1507
+ return;
1508
+ }
1509
+ const rawDelta = -event.deltaY * VIEWER_WHEEL_ZOOM_FACTOR;
1510
+ const zoomDelta = Math.max(
1511
+ -VIEWER_WHEEL_ZOOM_MAX_DELTA,
1512
+ Math.min(VIEWER_WHEEL_ZOOM_MAX_DELTA, rawDelta)
1513
+ );
1514
+ event.preventDefault();
1515
+ updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + zoomDelta, {
1516
+ x: event.clientX,
1517
+ y: event.clientY
1518
+ });
1519
+ }
1520
+ function handleMarkdownImageViewerPointerDown(event) {
1521
+ if (event.button !== 0) {
1522
+ return;
1523
+ }
1524
+ const viewportElement = event.currentTarget;
1525
+ const viewerState = getMarkdownImageViewerStateFromEvent(event);
1526
+ if (!viewerState) {
1527
+ return;
1528
+ }
1529
+ const canScrollHorizontally = viewportElement.scrollWidth > viewportElement.clientWidth;
1530
+ const canScrollVertically = viewportElement.scrollHeight > viewportElement.clientHeight;
1531
+ if (!canScrollHorizontally && !canScrollVertically) {
1532
+ return;
1533
+ }
1534
+ viewerState.isDragging = true;
1535
+ viewerState.dragStartClientX = event.clientX;
1536
+ viewerState.dragStartClientY = event.clientY;
1537
+ viewerState.dragStartScrollLeft = viewportElement.scrollLeft;
1538
+ viewerState.dragStartScrollTop = viewportElement.scrollTop;
1539
+ viewerState.dialogElement.classList.add(IMAGE_VIEWER_DRAGGING_CLASS_NAME);
1540
+ if (typeof viewportElement.setPointerCapture === "function") {
1541
+ viewportElement.setPointerCapture(event.pointerId);
1542
+ }
1543
+ event.preventDefault();
1544
+ }
1545
+ function handleMarkdownImageViewerPointerMove(event) {
1546
+ const viewportElement = event.currentTarget;
1547
+ const viewerState = getMarkdownImageViewerStateFromEvent(event);
1548
+ if (!viewerState || !viewerState.isDragging) {
1549
+ return;
1550
+ }
1551
+ const deltaX = event.clientX - viewerState.dragStartClientX;
1552
+ const deltaY = event.clientY - viewerState.dragStartClientY;
1553
+ viewportElement.scrollLeft = viewerState.dragStartScrollLeft - deltaX;
1554
+ viewportElement.scrollTop = viewerState.dragStartScrollTop - deltaY;
1555
+ }
1556
+ function handleMarkdownImageViewerPointerUp(event) {
1557
+ const viewportElement = event.currentTarget;
1558
+ const viewerState = getMarkdownImageViewerStateFromEvent(event);
1559
+ if (!viewerState || !viewerState.isDragging) {
1560
+ return;
1561
+ }
1562
+ viewerState.isDragging = false;
1563
+ viewerState.dialogElement.classList.remove(IMAGE_VIEWER_DRAGGING_CLASS_NAME);
1564
+ if (typeof viewportElement.releasePointerCapture === "function" && viewportElement.hasPointerCapture(event.pointerId)) {
1565
+ viewportElement.releasePointerCapture(event.pointerId);
1566
+ }
1567
+ }
1568
+ function handleMarkdownImageViewerImageDragStart(event) {
1569
+ event.preventDefault();
1570
+ }
1571
+ function handleMarkdownImageViewerImageLoad(event) {
1572
+ const imageElement = event.currentTarget;
1573
+ const dialogElement = imageElement.closest(
1574
+ `.${IMAGE_VIEWER_DIALOG_CLASS_NAME}`
1575
+ );
1576
+ if (!dialogElement) {
1577
+ return;
1578
+ }
1579
+ const viewerState = imageViewerStateByDialogElement.get(dialogElement);
1580
+ if (!viewerState) {
1581
+ return;
1582
+ }
1583
+ viewerState.naturalWidth = imageElement.naturalWidth || viewerState.naturalWidth;
1584
+ viewerState.naturalHeight = imageElement.naturalHeight || viewerState.naturalHeight;
1585
+ updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);
1586
+ }
1587
+ function handleMarkdownImageViewerWindowResize(event) {
1588
+ const ownerWindow = event.currentTarget;
1589
+ const viewerState = imageViewerStateByDocument.get(ownerWindow.document);
1590
+ if (!viewerState || !isMarkdownImageViewerOpen(viewerState)) {
1591
+ return;
1592
+ }
1593
+ updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);
1594
+ }
1595
+ function handleMarkdownImageViewerZoomOutClick(event) {
1596
+ const viewerState = getMarkdownImageViewerStateFromEvent(event);
1597
+ if (!viewerState) {
1598
+ return;
1599
+ }
1600
+ updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue - VIEWER_ZOOM_STEP);
1601
+ }
1602
+ function handleMarkdownImageViewerZoomInClick(event) {
1603
+ const viewerState = getMarkdownImageViewerStateFromEvent(event);
1604
+ if (!viewerState) {
1605
+ return;
1606
+ }
1607
+ updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + VIEWER_ZOOM_STEP);
1559
1608
  }
1560
- function handleMarkdownImageViewerKeyDown(event) {
1609
+ function handleMarkdownImageViewerResetClick(event) {
1561
1610
  const viewerState = getMarkdownImageViewerStateFromEvent(event);
1562
1611
  if (!viewerState) {
1563
1612
  return;
1564
1613
  }
1565
- if (event.key === "+" || event.key === "=") {
1566
- event.preventDefault();
1567
- updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + IMAGE_VIEWER_ZOOM_STEP);
1614
+ updateMarkdownImageViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);
1615
+ }
1616
+ function handleMarkdownImageViewerCloseClick(event) {
1617
+ const viewerState = getMarkdownImageViewerStateFromEvent(event);
1618
+ if (!viewerState) {
1568
1619
  return;
1569
1620
  }
1570
- if (event.key === "-") {
1571
- event.preventDefault();
1572
- updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue - IMAGE_VIEWER_ZOOM_STEP);
1573
- return;
1621
+ closeMarkdownImageViewer(viewerState);
1622
+ }
1623
+ function getMarkdownImageViewerStateFromEvent(event) {
1624
+ const currentTargetElement = event.currentTarget;
1625
+ if (!currentTargetElement) {
1626
+ return void 0;
1574
1627
  }
1575
- if (event.key === "0") {
1576
- event.preventDefault();
1577
- updateMarkdownImageViewerZoom(viewerState, IMAGE_VIEWER_DEFAULT_ZOOM);
1578
- return;
1628
+ const dialogElement = currentTargetElement.closest(
1629
+ `.${IMAGE_VIEWER_DIALOG_CLASS_NAME}`
1630
+ );
1631
+ if (!dialogElement) {
1632
+ return void 0;
1579
1633
  }
1580
- if (event.key === "Escape") {
1581
- event.preventDefault();
1582
- closeMarkdownImageViewer(viewerState);
1634
+ return imageViewerStateByDialogElement.get(dialogElement);
1635
+ }
1636
+ function updateMarkdownImageViewerZoom(viewerState, nextZoom, zoomAnchor) {
1637
+ const normalizedZoom = clampMarkdownViewerZoom(nextZoom);
1638
+ const focalPoint = isMarkdownImageViewerOpen(viewerState) ? captureMarkdownImageViewerFocalPoint(viewerState, zoomAnchor) : void 0;
1639
+ viewerState.zoomValue = normalizedZoom;
1640
+ viewerState.dialogElement.classList.toggle(
1641
+ IMAGE_VIEWER_ZOOMED_CLASS_NAME,
1642
+ normalizedZoom > VIEWER_DEFAULT_ZOOM
1643
+ );
1644
+ viewerState.zoomValueElement.textContent = `${Math.round(normalizedZoom * 100)}%`;
1645
+ viewerState.zoomOutButtonElement.disabled = normalizedZoom <= VIEWER_MIN_ZOOM;
1646
+ viewerState.zoomInButtonElement.disabled = normalizedZoom >= VIEWER_MAX_ZOOM;
1647
+ viewerState.resetButtonElement.disabled = normalizedZoom === VIEWER_DEFAULT_ZOOM;
1648
+ updateMarkdownImageViewerImageSize(viewerState);
1649
+ if (focalPoint) {
1650
+ applyMarkdownImageViewerFocalPoint(viewerState, focalPoint);
1583
1651
  }
1584
1652
  }
1585
- function handleMarkdownImageViewerWheel(event) {
1586
- if (!event.ctrlKey && !event.metaKey) {
1587
- return;
1653
+ function captureMarkdownImageViewerFocalPoint(viewerState, zoomAnchor) {
1654
+ const preImageRect = viewerState.imageElement.getBoundingClientRect();
1655
+ if (preImageRect.width <= 0 || preImageRect.height <= 0) {
1656
+ return void 0;
1588
1657
  }
1589
- const viewerState = getMarkdownImageViewerStateFromEvent(event);
1590
- if (!viewerState) {
1658
+ const viewportRect = viewerState.viewportElement.getBoundingClientRect();
1659
+ const anchorClientX = zoomAnchor?.x ?? viewportRect.left + viewportRect.width / 2;
1660
+ const anchorClientY = zoomAnchor?.y ?? viewportRect.top + viewportRect.height / 2;
1661
+ const normalizedX = (anchorClientX - preImageRect.left) / preImageRect.width;
1662
+ const normalizedY = (anchorClientY - preImageRect.top) / preImageRect.height;
1663
+ return {
1664
+ anchorClientX,
1665
+ anchorClientY,
1666
+ normalizedX,
1667
+ normalizedY
1668
+ };
1669
+ }
1670
+ function applyMarkdownImageViewerFocalPoint(viewerState, focalPoint) {
1671
+ viewerState.viewportElement.scrollLeft = 0;
1672
+ viewerState.viewportElement.scrollTop = 0;
1673
+ const postImageRect = viewerState.imageElement.getBoundingClientRect();
1674
+ if (postImageRect.width <= 0 || postImageRect.height <= 0) {
1591
1675
  return;
1592
1676
  }
1593
- const rawDelta = -event.deltaY * IMAGE_VIEWER_WHEEL_ZOOM_FACTOR;
1594
- const zoomDelta = Math.max(
1595
- -IMAGE_VIEWER_WHEEL_ZOOM_MAX_DELTA,
1596
- Math.min(IMAGE_VIEWER_WHEEL_ZOOM_MAX_DELTA, rawDelta)
1677
+ const targetImageClientLeft = focalPoint.anchorClientX - focalPoint.normalizedX * postImageRect.width;
1678
+ const targetImageClientTop = focalPoint.anchorClientY - focalPoint.normalizedY * postImageRect.height;
1679
+ viewerState.viewportElement.scrollLeft = postImageRect.left - targetImageClientLeft;
1680
+ viewerState.viewportElement.scrollTop = postImageRect.top - targetImageClientTop;
1681
+ }
1682
+ function updateMarkdownImageViewerImageSize(viewerState) {
1683
+ const viewportWidth = Math.max(
1684
+ viewerState.viewportElement.clientWidth * VIEWER_FIT_RATIO,
1685
+ 1
1597
1686
  );
1598
- event.preventDefault();
1599
- updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + zoomDelta, {
1600
- x: event.clientX,
1601
- y: event.clientY
1602
- });
1687
+ const viewportHeight = Math.max(
1688
+ viewerState.viewportElement.clientHeight * VIEWER_FIT_RATIO,
1689
+ 1
1690
+ );
1691
+ const naturalWidth = Math.max(viewerState.naturalWidth, 1);
1692
+ const naturalHeight = Math.max(viewerState.naturalHeight, 1);
1693
+ const fitScale = Math.min(1, viewportWidth / naturalWidth, viewportHeight / naturalHeight);
1694
+ const displayWidth = Math.max(1, Math.round(naturalWidth * fitScale * viewerState.zoomValue));
1695
+ const displayHeight = Math.max(1, Math.round(naturalHeight * fitScale * viewerState.zoomValue));
1696
+ viewerState.imageElement.style.width = `${displayWidth}px`;
1697
+ viewerState.imageElement.style.height = `${displayHeight}px`;
1603
1698
  }
1604
- function handleMarkdownImageViewerPointerDown(event) {
1605
- if (event.button !== 0) {
1699
+ function closeMarkdownImageViewer(viewerState) {
1700
+ if (typeof viewerState.dialogElement.close === "function" && viewerState.dialogElement.open) {
1701
+ viewerState.dialogElement.close();
1606
1702
  return;
1607
1703
  }
1608
- const viewportElement = event.currentTarget;
1609
- const viewerState = getMarkdownImageViewerStateFromEvent(event);
1610
- if (!viewerState) {
1611
- return;
1704
+ viewerState.dialogElement.removeAttribute("open");
1705
+ resetMarkdownImageViewerAfterClose(viewerState);
1706
+ }
1707
+ function resetMarkdownImageViewerAfterClose(viewerState) {
1708
+ viewerState.dialogElement.classList.remove(IMAGE_VIEWER_ZOOMED_CLASS_NAME);
1709
+ viewerState.dialogElement.classList.remove(IMAGE_VIEWER_DRAGGING_CLASS_NAME);
1710
+ viewerState.isDragging = false;
1711
+ viewerState.imageElement.removeAttribute("src");
1712
+ viewerState.imageElement.removeAttribute("alt");
1713
+ viewerState.imageElement.removeAttribute("style");
1714
+ viewerState.captionElement.textContent = "";
1715
+ viewerState.viewportElement.scrollLeft = 0;
1716
+ viewerState.viewportElement.scrollTop = 0;
1717
+ viewerState.zoomValue = VIEWER_DEFAULT_ZOOM;
1718
+ if (viewerState.sourceImageElement?.isConnected) {
1719
+ viewerState.sourceImageElement.focus();
1612
1720
  }
1613
- const canScrollHorizontally = viewportElement.scrollWidth > viewportElement.clientWidth;
1614
- const canScrollVertically = viewportElement.scrollHeight > viewportElement.clientHeight;
1615
- if (!canScrollHorizontally && !canScrollVertically) {
1721
+ }
1722
+ function isMarkdownImageViewerOpen(viewerState) {
1723
+ return viewerState.dialogElement.open || viewerState.dialogElement.hasAttribute("open");
1724
+ }
1725
+ function getMarkdownImageSource(imageElement) {
1726
+ return imageElement.currentSrc || imageElement.getAttribute("src") || imageElement.src || "";
1727
+ }
1728
+ function createMarkdownImageViewerAriaLabel(imageElement) {
1729
+ const imageAltText = imageElement.alt.trim();
1730
+ if (imageAltText.length === 0) {
1731
+ return IMAGE_VIEWER_FALLBACK_LABEL;
1732
+ }
1733
+ return `${IMAGE_VIEWER_FALLBACK_LABEL}\uFF1A${imageAltText}`;
1734
+ }
1735
+ function createMarkdownImageViewerCaption(imageElement, imageSource) {
1736
+ const imageAltText = imageElement.alt.trim();
1737
+ const imageTitleText = imageElement.title.trim();
1738
+ return imageAltText || imageTitleText || imageSource;
1739
+ }
1740
+
1741
+ // src/media/images.ts
1742
+ var IMAGE_HYDRATED_DATA_KEY = "scribdownImageHydrated";
1743
+ function remarkImageFigures() {
1744
+ return (tree) => {
1745
+ transformImageFigures(tree);
1746
+ };
1747
+ }
1748
+ function transformImageFigures(node) {
1749
+ if (!node.children) {
1616
1750
  return;
1617
1751
  }
1618
- viewerState.isDragging = true;
1619
- viewerState.dragStartClientX = event.clientX;
1620
- viewerState.dragStartClientY = event.clientY;
1621
- viewerState.dragStartScrollLeft = viewportElement.scrollLeft;
1622
- viewerState.dragStartScrollTop = viewportElement.scrollTop;
1623
- viewerState.dialogElement.classList.add(IMAGE_VIEWER_DRAGGING_CLASS_NAME);
1624
- if (typeof viewportElement.setPointerCapture === "function") {
1625
- viewportElement.setPointerCapture(event.pointerId);
1752
+ const childNodes = node.children;
1753
+ for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {
1754
+ const childNode = childNodes[childIndex];
1755
+ const imageFigureNode = createImageFigureNode(childNode);
1756
+ if (imageFigureNode) {
1757
+ childNodes[childIndex] = imageFigureNode;
1758
+ continue;
1759
+ }
1760
+ if (isImageNode(childNode)) {
1761
+ decorateImageNode(childNode);
1762
+ continue;
1763
+ }
1764
+ transformImageFigures(childNode);
1626
1765
  }
1627
- event.preventDefault();
1628
1766
  }
1629
- function handleMarkdownImageViewerPointerMove(event) {
1630
- const viewportElement = event.currentTarget;
1631
- const viewerState = getMarkdownImageViewerStateFromEvent(event);
1632
- if (!viewerState || !viewerState.isDragging) {
1633
- return;
1634
- }
1635
- const deltaX = event.clientX - viewerState.dragStartClientX;
1636
- const deltaY = event.clientY - viewerState.dragStartClientY;
1637
- viewportElement.scrollLeft = viewerState.dragStartScrollLeft - deltaX;
1638
- viewportElement.scrollTop = viewerState.dragStartScrollTop - deltaY;
1767
+ function createImageFigureNode(node) {
1768
+ if (node.type !== "paragraph" || !node.children || node.children.length !== 1) {
1769
+ return void 0;
1770
+ }
1771
+ const onlyChild = node.children[0];
1772
+ if (!isImageNode(onlyChild)) {
1773
+ return void 0;
1774
+ }
1775
+ decorateImageNode(onlyChild);
1776
+ return {
1777
+ type: "imageFigure",
1778
+ // 关键步骤:保留原段落源码位置,使 remarkSourceLine 能为 figure 标注 data-source-line。
1779
+ position: node.position,
1780
+ data: {
1781
+ hName: "figure",
1782
+ hProperties: {
1783
+ className: [IMAGE_FIGURE_CLASS_NAME]
1784
+ }
1785
+ },
1786
+ children: [createImageFrameNode(onlyChild), ...createImageCaptionNodes(onlyChild)]
1787
+ };
1788
+ }
1789
+ function decorateImageNode(imageNode) {
1790
+ imageNode.data = {
1791
+ ...imageNode.data,
1792
+ hProperties: {
1793
+ ...imageNode.data?.hProperties,
1794
+ className: [IMAGE_ELEMENT_CLASS_NAME]
1795
+ }
1796
+ };
1797
+ }
1798
+ function createImageFrameNode(imageNode) {
1799
+ return {
1800
+ type: "imageFrame",
1801
+ data: {
1802
+ hName: "span",
1803
+ hProperties: {
1804
+ className: [IMAGE_FRAME_CLASS_NAME]
1805
+ }
1806
+ },
1807
+ children: [imageNode, createImageFallbackNode(imageNode)]
1808
+ };
1809
+ }
1810
+ function createImageCaptionNodes(imageNode) {
1811
+ const imageTitle = typeof imageNode.title === "string" ? imageNode.title.trim() : "";
1812
+ if (imageTitle.length === 0) {
1813
+ return [];
1814
+ }
1815
+ return [
1816
+ {
1817
+ type: "imageCaption",
1818
+ data: {
1819
+ hName: "figcaption",
1820
+ hProperties: {
1821
+ className: [IMAGE_CAPTION_CLASS_NAME]
1822
+ }
1823
+ },
1824
+ children: [{ type: "text", value: imageTitle }]
1825
+ }
1826
+ ];
1827
+ }
1828
+ function createImageFallbackNode(imageNode) {
1829
+ const fallbackText = imageNode.alt?.trim() || "\u56FE\u7247\u52A0\u8F7D\u5931\u8D25";
1830
+ const fallbackSource = imageNode.url ?? imageNode.identifier ?? "";
1831
+ return {
1832
+ type: "imageFallback",
1833
+ data: {
1834
+ hName: "span",
1835
+ hProperties: {
1836
+ className: [IMAGE_FALLBACK_CLASS_NAME]
1837
+ }
1838
+ },
1839
+ children: [
1840
+ {
1841
+ type: "imageFallbackIcon",
1842
+ data: {
1843
+ hName: "span",
1844
+ hProperties: {
1845
+ className: [IMAGE_FALLBACK_ICON_CLASS_NAME]
1846
+ }
1847
+ }
1848
+ },
1849
+ {
1850
+ type: "imageFallbackText",
1851
+ data: {
1852
+ hName: "span",
1853
+ hProperties: {
1854
+ className: [IMAGE_FALLBACK_TEXT_CLASS_NAME]
1855
+ }
1856
+ },
1857
+ children: [{ type: "text", value: fallbackText }]
1858
+ },
1859
+ {
1860
+ type: "imageFallbackSource",
1861
+ data: {
1862
+ hName: "span",
1863
+ hProperties: {
1864
+ className: [IMAGE_FALLBACK_SOURCE_CLASS_NAME]
1865
+ }
1866
+ },
1867
+ children: [{ type: "text", value: fallbackSource }]
1868
+ }
1869
+ ]
1870
+ };
1871
+ }
1872
+ function hydrateMarkdownImages(rootElement) {
1873
+ const imageElements = rootElement.querySelectorAll(
1874
+ `img.${IMAGE_ELEMENT_CLASS_NAME}`
1875
+ );
1876
+ imageElements.forEach(bindMarkdownImageState);
1639
1877
  }
1640
- function handleMarkdownImageViewerPointerUp(event) {
1641
- const viewportElement = event.currentTarget;
1642
- const viewerState = getMarkdownImageViewerStateFromEvent(event);
1643
- if (!viewerState || !viewerState.isDragging) {
1878
+ function bindMarkdownImageState(imageElement) {
1879
+ updateMarkdownImageState(imageElement);
1880
+ if (imageElement.dataset[IMAGE_HYDRATED_DATA_KEY] === "true") {
1644
1881
  return;
1645
1882
  }
1646
- viewerState.isDragging = false;
1647
- viewerState.dialogElement.classList.remove(IMAGE_VIEWER_DRAGGING_CLASS_NAME);
1648
- if (typeof viewportElement.releasePointerCapture === "function" && viewportElement.hasPointerCapture(event.pointerId)) {
1649
- viewportElement.releasePointerCapture(event.pointerId);
1650
- }
1883
+ imageElement.dataset[IMAGE_HYDRATED_DATA_KEY] = "true";
1884
+ imageElement.addEventListener("load", handleMarkdownImageLoad);
1885
+ imageElement.addEventListener("error", handleMarkdownImageError);
1886
+ bindMarkdownImageViewer(imageElement);
1651
1887
  }
1652
- function handleMarkdownImageViewerImageDragStart(event) {
1653
- event.preventDefault();
1888
+ function handleMarkdownImageLoad(event) {
1889
+ const imageElement = event.currentTarget;
1890
+ updateMarkdownImageState(imageElement);
1654
1891
  }
1655
- function handleMarkdownImageViewerImageLoad(event) {
1892
+ function handleMarkdownImageError(event) {
1656
1893
  const imageElement = event.currentTarget;
1657
- const dialogElement = imageElement.closest(
1658
- `.${IMAGE_VIEWER_DIALOG_CLASS_NAME}`
1659
- );
1660
- if (!dialogElement) {
1661
- return;
1662
- }
1663
- const viewerState = imageViewerStateByDialogElement.get(dialogElement);
1664
- if (!viewerState) {
1665
- return;
1666
- }
1667
- viewerState.naturalWidth = imageElement.naturalWidth || viewerState.naturalWidth;
1668
- viewerState.naturalHeight = imageElement.naturalHeight || viewerState.naturalHeight;
1669
- updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);
1894
+ updateMarkdownImageState(imageElement);
1670
1895
  }
1671
- function handleMarkdownImageViewerWindowResize(event) {
1672
- const ownerWindow = event.currentTarget;
1673
- const viewerState = imageViewerStateByDocument.get(ownerWindow.document);
1674
- if (!viewerState || !isMarkdownImageViewerOpen(viewerState)) {
1896
+ function updateMarkdownImageState(imageElement) {
1897
+ const frameElement = imageElement.closest(`.${IMAGE_FRAME_CLASS_NAME}`);
1898
+ if (!frameElement) {
1675
1899
  return;
1676
1900
  }
1677
- updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);
1901
+ const isFailed = imageElement.complete && imageElement.naturalWidth === 0;
1902
+ const isLoaded = imageElement.complete && imageElement.naturalWidth > 0;
1903
+ frameElement.classList.toggle(IMAGE_FRAME_FAILED_CLASS_NAME, isFailed);
1904
+ frameElement.classList.toggle(IMAGE_FRAME_LOADED_CLASS_NAME, isLoaded);
1678
1905
  }
1679
- function handleMarkdownImageViewerZoomOutClick(event) {
1680
- const viewerState = getMarkdownImageViewerStateFromEvent(event);
1681
- if (!viewerState) {
1682
- return;
1683
- }
1684
- updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue - IMAGE_VIEWER_ZOOM_STEP);
1906
+
1907
+ // src/media/videos.ts
1908
+ import {
1909
+ VIDEO_ELEMENT_CLASS_NAME,
1910
+ VIDEO_FALLBACK_CLASS_NAME,
1911
+ VIDEO_FALLBACK_ICON_CLASS_NAME,
1912
+ VIDEO_FALLBACK_SOURCE_CLASS_NAME,
1913
+ VIDEO_FALLBACK_TEXT_CLASS_NAME,
1914
+ VIDEO_FIGURE_CLASS_NAME,
1915
+ VIDEO_FRAME_CLASS_NAME,
1916
+ VIDEO_FRAME_FAILED_CLASS_NAME,
1917
+ VIDEO_FRAME_LOADED_CLASS_NAME
1918
+ } from "@scribdown/shared";
1919
+ var VIDEO_HYDRATED_DATA_KEY = "scribdownVideoHydrated";
1920
+ var VIDEO_FALLBACK_DEFAULT_TEXT = "\u89C6\u9891\u52A0\u8F7D\u5931\u8D25";
1921
+ function rehypeVideoFigures() {
1922
+ return (tree) => {
1923
+ transformVideoFigures(tree);
1924
+ };
1685
1925
  }
1686
- function handleMarkdownImageViewerZoomInClick(event) {
1687
- const viewerState = getMarkdownImageViewerStateFromEvent(event);
1688
- if (!viewerState) {
1926
+ function transformVideoFigures(node) {
1927
+ if (!Array.isArray(node.children)) {
1689
1928
  return;
1690
1929
  }
1691
- updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + IMAGE_VIEWER_ZOOM_STEP);
1692
- }
1693
- function handleMarkdownImageViewerResetClick(event) {
1694
- const viewerState = getMarkdownImageViewerStateFromEvent(event);
1695
- if (!viewerState) {
1696
- return;
1930
+ const childNodes = node.children;
1931
+ for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {
1932
+ const childNode = childNodes[childIndex];
1933
+ if (isHastVideoElement(childNode)) {
1934
+ childNodes[childIndex] = createVideoFigureHast(childNode);
1935
+ continue;
1936
+ }
1937
+ const standaloneVideo = extractStandaloneParagraphVideo(childNode);
1938
+ if (standaloneVideo) {
1939
+ childNodes[childIndex] = createVideoFigureHast(standaloneVideo);
1940
+ continue;
1941
+ }
1942
+ transformVideoFigures(childNode);
1697
1943
  }
1698
- updateMarkdownImageViewerZoom(viewerState, IMAGE_VIEWER_DEFAULT_ZOOM);
1699
1944
  }
1700
- function handleMarkdownImageViewerCloseClick(event) {
1701
- const viewerState = getMarkdownImageViewerStateFromEvent(event);
1702
- if (!viewerState) {
1703
- return;
1704
- }
1705
- closeMarkdownImageViewer(viewerState);
1945
+ function isHastVideoElement(node) {
1946
+ return node.type === "element" && node.tagName === "video";
1706
1947
  }
1707
- function getMarkdownImageViewerStateFromEvent(event) {
1708
- const currentTargetElement = event.currentTarget;
1709
- if (!currentTargetElement) {
1948
+ function extractStandaloneParagraphVideo(node) {
1949
+ if (node.type !== "element" || node.tagName !== "p" || !Array.isArray(node.children)) {
1710
1950
  return void 0;
1711
1951
  }
1712
- const dialogElement = currentTargetElement.closest(
1713
- `.${IMAGE_VIEWER_DIALOG_CLASS_NAME}`
1714
- );
1715
- if (!dialogElement) {
1716
- return void 0;
1952
+ const significantChildren = node.children.filter((childNode) => {
1953
+ if (childNode.type === "text" && typeof childNode.value === "string") {
1954
+ return childNode.value.trim().length > 0;
1955
+ }
1956
+ return true;
1957
+ });
1958
+ if (significantChildren.length === 1 && isHastVideoElement(significantChildren[0])) {
1959
+ return significantChildren[0];
1717
1960
  }
1718
- return imageViewerStateByDialogElement.get(dialogElement);
1961
+ return void 0;
1719
1962
  }
1720
- function updateMarkdownImageViewerZoom(viewerState, nextZoom, zoomAnchor) {
1721
- const normalizedZoom = clampMarkdownImageViewerZoom(nextZoom);
1722
- const focalPoint = isMarkdownImageViewerOpen(viewerState) ? captureMarkdownImageViewerFocalPoint(viewerState, zoomAnchor) : void 0;
1723
- viewerState.zoomValue = normalizedZoom;
1724
- viewerState.dialogElement.classList.toggle(
1725
- IMAGE_VIEWER_ZOOMED_CLASS_NAME,
1726
- normalizedZoom > IMAGE_VIEWER_DEFAULT_ZOOM
1727
- );
1728
- viewerState.zoomValueElement.textContent = `${Math.round(normalizedZoom * 100)}%`;
1729
- viewerState.zoomOutButtonElement.disabled = normalizedZoom <= IMAGE_VIEWER_MIN_ZOOM;
1730
- viewerState.zoomInButtonElement.disabled = normalizedZoom >= IMAGE_VIEWER_MAX_ZOOM;
1731
- viewerState.resetButtonElement.disabled = normalizedZoom === IMAGE_VIEWER_DEFAULT_ZOOM;
1732
- updateMarkdownImageViewerImageSize(viewerState);
1733
- if (focalPoint) {
1734
- applyMarkdownImageViewerFocalPoint(viewerState, focalPoint);
1963
+ function createVideoFigureHast(videoNode) {
1964
+ decorateHastVideoElement(videoNode);
1965
+ const sourceUrl = readHastVideoSourceUrl(videoNode);
1966
+ return {
1967
+ type: "element",
1968
+ tagName: "figure",
1969
+ properties: { className: [VIDEO_FIGURE_CLASS_NAME] },
1970
+ children: [
1971
+ {
1972
+ type: "element",
1973
+ tagName: "span",
1974
+ properties: { className: [VIDEO_FRAME_CLASS_NAME] },
1975
+ children: [videoNode, createVideoFallbackHast(sourceUrl)]
1976
+ }
1977
+ ]
1978
+ };
1979
+ }
1980
+ function decorateHastVideoElement(videoNode) {
1981
+ const properties = videoNode.properties ?? {};
1982
+ const existingClassName = properties.className;
1983
+ const classList = Array.isArray(existingClassName) ? existingClassName.map(String) : typeof existingClassName === "string" ? [existingClassName] : [];
1984
+ if (!classList.includes(VIDEO_ELEMENT_CLASS_NAME)) {
1985
+ classList.push(VIDEO_ELEMENT_CLASS_NAME);
1735
1986
  }
1987
+ properties.className = classList;
1988
+ videoNode.properties = properties;
1736
1989
  }
1737
- function captureMarkdownImageViewerFocalPoint(viewerState, zoomAnchor) {
1738
- const preImageRect = viewerState.imageElement.getBoundingClientRect();
1739
- if (preImageRect.width <= 0 || preImageRect.height <= 0) {
1740
- return void 0;
1990
+ function readHastVideoSourceUrl(videoNode) {
1991
+ const properties = videoNode.properties ?? {};
1992
+ if (typeof properties.src === "string") {
1993
+ return properties.src;
1741
1994
  }
1742
- const viewportRect = viewerState.viewportElement.getBoundingClientRect();
1743
- const anchorClientX = zoomAnchor?.x ?? viewportRect.left + viewportRect.width / 2;
1744
- const anchorClientY = zoomAnchor?.y ?? viewportRect.top + viewportRect.height / 2;
1745
- const normalizedX = (anchorClientX - preImageRect.left) / preImageRect.width;
1746
- const normalizedY = (anchorClientY - preImageRect.top) / preImageRect.height;
1995
+ if (Array.isArray(videoNode.children)) {
1996
+ for (const childNode of videoNode.children) {
1997
+ if (childNode.type === "element" && childNode.tagName === "source" && typeof childNode.properties?.src === "string") {
1998
+ return childNode.properties.src;
1999
+ }
2000
+ }
2001
+ }
2002
+ return "";
2003
+ }
2004
+ function createVideoFallbackHast(sourceUrl) {
1747
2005
  return {
1748
- anchorClientX,
1749
- anchorClientY,
1750
- normalizedX,
1751
- normalizedY
2006
+ type: "element",
2007
+ tagName: "span",
2008
+ properties: { className: [VIDEO_FALLBACK_CLASS_NAME] },
2009
+ children: [
2010
+ {
2011
+ type: "element",
2012
+ tagName: "span",
2013
+ properties: { className: [VIDEO_FALLBACK_ICON_CLASS_NAME] },
2014
+ children: []
2015
+ },
2016
+ {
2017
+ type: "element",
2018
+ tagName: "span",
2019
+ properties: { className: [VIDEO_FALLBACK_TEXT_CLASS_NAME] },
2020
+ children: [{ type: "text", value: VIDEO_FALLBACK_DEFAULT_TEXT }]
2021
+ },
2022
+ {
2023
+ type: "element",
2024
+ tagName: "span",
2025
+ properties: { className: [VIDEO_FALLBACK_SOURCE_CLASS_NAME] },
2026
+ children: [{ type: "text", value: sourceUrl }]
2027
+ }
2028
+ ]
1752
2029
  };
1753
2030
  }
1754
- function applyMarkdownImageViewerFocalPoint(viewerState, focalPoint) {
1755
- viewerState.viewportElement.scrollLeft = 0;
1756
- viewerState.viewportElement.scrollTop = 0;
1757
- const postImageRect = viewerState.imageElement.getBoundingClientRect();
1758
- if (postImageRect.width <= 0 || postImageRect.height <= 0) {
2031
+ function hydrateMarkdownVideos(rootElement) {
2032
+ const videoElements = rootElement.querySelectorAll(
2033
+ `video.${VIDEO_ELEMENT_CLASS_NAME}`
2034
+ );
2035
+ videoElements.forEach(bindMarkdownVideoState);
2036
+ }
2037
+ function bindMarkdownVideoState(videoElement) {
2038
+ updateMarkdownVideoState(videoElement);
2039
+ if (videoElement.dataset[VIDEO_HYDRATED_DATA_KEY] === "true") {
2040
+ return;
2041
+ }
2042
+ videoElement.dataset[VIDEO_HYDRATED_DATA_KEY] = "true";
2043
+ videoElement.addEventListener("loadeddata", handleMarkdownVideoLoaded);
2044
+ videoElement.addEventListener("error", handleMarkdownVideoError);
2045
+ videoElement.querySelectorAll("source").forEach((sourceElement) => {
2046
+ sourceElement.addEventListener("error", handleMarkdownVideoSourceError);
2047
+ });
2048
+ }
2049
+ function handleMarkdownVideoLoaded(event) {
2050
+ const videoElement = event.currentTarget;
2051
+ updateMarkdownVideoState(videoElement);
2052
+ }
2053
+ function handleMarkdownVideoError(event) {
2054
+ const videoElement = event.currentTarget;
2055
+ markMarkdownVideoFailed(videoElement);
2056
+ }
2057
+ function handleMarkdownVideoSourceError(event) {
2058
+ const sourceElement = event.currentTarget;
2059
+ const videoElement = sourceElement.closest(
2060
+ `video.${VIDEO_ELEMENT_CLASS_NAME}`
2061
+ );
2062
+ if (!videoElement) {
1759
2063
  return;
1760
2064
  }
1761
- const targetImageClientLeft = focalPoint.anchorClientX - focalPoint.normalizedX * postImageRect.width;
1762
- const targetImageClientTop = focalPoint.anchorClientY - focalPoint.normalizedY * postImageRect.height;
1763
- viewerState.viewportElement.scrollLeft = postImageRect.left - targetImageClientLeft;
1764
- viewerState.viewportElement.scrollTop = postImageRect.top - targetImageClientTop;
2065
+ if (videoElement.networkState !== videoElement.NETWORK_NO_SOURCE) {
2066
+ return;
2067
+ }
2068
+ markMarkdownVideoFailed(videoElement);
1765
2069
  }
1766
- function updateMarkdownImageViewerImageSize(viewerState) {
1767
- const viewportWidth = Math.max(
1768
- viewerState.viewportElement.clientWidth * IMAGE_VIEWER_FIT_RATIO,
1769
- 1
1770
- );
1771
- const viewportHeight = Math.max(
1772
- viewerState.viewportElement.clientHeight * IMAGE_VIEWER_FIT_RATIO,
1773
- 1
2070
+ function updateMarkdownVideoState(videoElement) {
2071
+ const frameElement = videoElement.closest(
2072
+ `.${VIDEO_FRAME_CLASS_NAME}`
1774
2073
  );
1775
- const naturalWidth = Math.max(viewerState.naturalWidth, 1);
1776
- const naturalHeight = Math.max(viewerState.naturalHeight, 1);
1777
- const fitScale = Math.min(1, viewportWidth / naturalWidth, viewportHeight / naturalHeight);
1778
- const displayWidth = Math.max(1, Math.round(naturalWidth * fitScale * viewerState.zoomValue));
1779
- const displayHeight = Math.max(1, Math.round(naturalHeight * fitScale * viewerState.zoomValue));
1780
- viewerState.imageElement.style.width = `${displayWidth}px`;
1781
- viewerState.imageElement.style.height = `${displayHeight}px`;
1782
- }
1783
- function clampMarkdownImageViewerZoom(zoomValue) {
1784
- return Math.min(IMAGE_VIEWER_MAX_ZOOM, Math.max(IMAGE_VIEWER_MIN_ZOOM, zoomValue));
1785
- }
1786
- function closeMarkdownImageViewer(viewerState) {
1787
- if (typeof viewerState.dialogElement.close === "function" && viewerState.dialogElement.open) {
1788
- viewerState.dialogElement.close();
2074
+ if (!frameElement) {
1789
2075
  return;
1790
2076
  }
1791
- viewerState.dialogElement.removeAttribute("open");
1792
- resetMarkdownImageViewerAfterClose(viewerState);
2077
+ const isFailed = videoElement.error !== null || videoElement.networkState === videoElement.NETWORK_NO_SOURCE;
2078
+ const isLoaded = videoElement.readyState >= videoElement.HAVE_CURRENT_DATA;
2079
+ frameElement.classList.toggle(VIDEO_FRAME_FAILED_CLASS_NAME, isFailed);
2080
+ frameElement.classList.toggle(
2081
+ VIDEO_FRAME_LOADED_CLASS_NAME,
2082
+ isLoaded && !isFailed
2083
+ );
1793
2084
  }
1794
- function resetMarkdownImageViewerAfterClose(viewerState) {
1795
- viewerState.dialogElement.classList.remove(IMAGE_VIEWER_ZOOMED_CLASS_NAME);
1796
- viewerState.dialogElement.classList.remove(IMAGE_VIEWER_DRAGGING_CLASS_NAME);
1797
- viewerState.isDragging = false;
1798
- viewerState.imageElement.removeAttribute("src");
1799
- viewerState.imageElement.removeAttribute("alt");
1800
- viewerState.imageElement.removeAttribute("style");
1801
- viewerState.captionElement.textContent = "";
1802
- viewerState.viewportElement.scrollLeft = 0;
1803
- viewerState.viewportElement.scrollTop = 0;
1804
- viewerState.zoomValue = IMAGE_VIEWER_DEFAULT_ZOOM;
1805
- if (viewerState.sourceImageElement?.isConnected) {
1806
- viewerState.sourceImageElement.focus();
2085
+ function markMarkdownVideoFailed(videoElement) {
2086
+ const frameElement = videoElement.closest(
2087
+ `.${VIDEO_FRAME_CLASS_NAME}`
2088
+ );
2089
+ if (!frameElement) {
2090
+ return;
1807
2091
  }
2092
+ frameElement.classList.add(VIDEO_FRAME_FAILED_CLASS_NAME);
2093
+ frameElement.classList.remove(VIDEO_FRAME_LOADED_CLASS_NAME);
1808
2094
  }
1809
- function isMarkdownImageViewerOpen(viewerState) {
1810
- return viewerState.dialogElement.open || viewerState.dialogElement.hasAttribute("open");
1811
- }
1812
- function getMarkdownImageSource(imageElement) {
1813
- return imageElement.currentSrc || imageElement.getAttribute("src") || imageElement.src || "";
1814
- }
1815
- function createMarkdownImageViewerAriaLabel(imageElement) {
1816
- const imageAltText = imageElement.alt.trim();
1817
- if (imageAltText.length === 0) {
1818
- return IMAGE_VIEWER_FALLBACK_LABEL;
2095
+
2096
+ // src/sanitize.ts
2097
+ import {
2098
+ DETAILS_CLASS_NAME,
2099
+ FRAME_CLASS_NAME,
2100
+ FRONTMATTER_CHROME_CLASS_NAME,
2101
+ FRONTMATTER_CLASS_NAME,
2102
+ FRONTMATTER_LABEL_CLASS_NAME,
2103
+ FRONTMATTER_LIST_CLASS_NAME,
2104
+ FRONTMATTER_LIST_NESTED_CLASS_NAME,
2105
+ HEADING_MARK_CLASS_NAME,
2106
+ IMAGE_CAPTION_CLASS_NAME as IMAGE_CAPTION_CLASS_NAME2,
2107
+ IMAGE_ELEMENT_CLASS_NAME as IMAGE_ELEMENT_CLASS_NAME2,
2108
+ IMAGE_FALLBACK_CLASS_NAME as IMAGE_FALLBACK_CLASS_NAME2,
2109
+ IMAGE_FALLBACK_ICON_CLASS_NAME as IMAGE_FALLBACK_ICON_CLASS_NAME2,
2110
+ IMAGE_FALLBACK_SOURCE_CLASS_NAME as IMAGE_FALLBACK_SOURCE_CLASS_NAME2,
2111
+ IMAGE_FALLBACK_TEXT_CLASS_NAME as IMAGE_FALLBACK_TEXT_CLASS_NAME2,
2112
+ IMAGE_FIGURE_CLASS_NAME as IMAGE_FIGURE_CLASS_NAME2,
2113
+ IMAGE_FRAME_CLASS_NAME as IMAGE_FRAME_CLASS_NAME2,
2114
+ TOC_CLASS_NAME,
2115
+ TOC_ITEM_CLASS_PREFIX,
2116
+ TOC_LINK_CLASS_NAME,
2117
+ TOC_LIST_CLASS_NAME,
2118
+ TOC_LIST_NESTED_CLASS_NAME,
2119
+ TOC_NAV_CLASS_NAME,
2120
+ TOC_SUMMARY_CLASS_NAME,
2121
+ TOC_TOGGLE_CLASS_NAME,
2122
+ VIDEO_ELEMENT_CLASS_NAME as VIDEO_ELEMENT_CLASS_NAME2,
2123
+ VIDEO_FALLBACK_CLASS_NAME as VIDEO_FALLBACK_CLASS_NAME2,
2124
+ VIDEO_FALLBACK_ICON_CLASS_NAME as VIDEO_FALLBACK_ICON_CLASS_NAME2,
2125
+ VIDEO_FALLBACK_SOURCE_CLASS_NAME as VIDEO_FALLBACK_SOURCE_CLASS_NAME2,
2126
+ VIDEO_FALLBACK_TEXT_CLASS_NAME as VIDEO_FALLBACK_TEXT_CLASS_NAME2,
2127
+ VIDEO_FIGURE_CLASS_NAME as VIDEO_FIGURE_CLASS_NAME2,
2128
+ VIDEO_FRAME_CLASS_NAME as VIDEO_FRAME_CLASS_NAME2
2129
+ } from "@scribdown/shared";
2130
+ import DOMPurify from "dompurify";
2131
+ import { defaultSchema } from "rehype-sanitize";
2132
+ function sanitizeHtmlWithDomPurify(unsafeHtml) {
2133
+ const domPurify = DOMPurify;
2134
+ if (typeof domPurify.sanitize !== "function") {
2135
+ return unsafeHtml;
1819
2136
  }
1820
- return `${IMAGE_VIEWER_FALLBACK_LABEL}\uFF1A${imageAltText}`;
1821
- }
1822
- function createMarkdownImageViewerCaption(imageElement, imageSource) {
1823
- const imageAltText = imageElement.alt.trim();
1824
- const imageTitleText = imageElement.title.trim();
1825
- return imageAltText || imageTitleText || imageSource;
2137
+ return domPurify.sanitize(unsafeHtml);
1826
2138
  }
1827
2139
  function createScribdownSanitizeSchema() {
1828
2140
  const defaultTagNames = defaultSchema.tagNames ?? [];
@@ -1835,7 +2147,7 @@ function createScribdownSanitizeSchema() {
1835
2147
  ...defaultAttributes.summary ?? [],
1836
2148
  ["className", TOC_SUMMARY_CLASS_NAME]
1837
2149
  ];
1838
- const navAttributes = ["ariaLabel", ["className", /^scribdown-toc-nav$/u]];
2150
+ const navAttributes = ["ariaLabel", ["className", TOC_NAV_CLASS_NAME]];
1839
2151
  const linkAttributes = [
1840
2152
  "ariaDescribedBy",
1841
2153
  "ariaLabel",
@@ -1855,32 +2167,40 @@ function createScribdownSanitizeSchema() {
1855
2167
  "ariaDescribedBy",
1856
2168
  "ariaLabel",
1857
2169
  "ariaLabelledBy",
1858
- ["className", "contains-task-list", /^scribdown-toc-list(?:--nested)?$/u]
2170
+ ["className", "contains-task-list", TOC_LIST_CLASS_NAME, TOC_LIST_NESTED_CLASS_NAME]
2171
+ ];
2172
+ const divAttributes = [
2173
+ ...defaultAttributes.div ?? [],
2174
+ ["className", FRONTMATTER_CLASS_NAME, FRAME_CLASS_NAME, FRONTMATTER_CHROME_CLASS_NAME]
2175
+ ];
2176
+ const definitionListAttributes = [
2177
+ ["className", FRONTMATTER_LIST_CLASS_NAME, FRONTMATTER_LIST_NESTED_CLASS_NAME]
1859
2178
  ];
1860
2179
  const spanAttributes = [
1861
2180
  ...defaultAttributes.span ?? [],
1862
2181
  [
1863
2182
  "className",
1864
2183
  HEADING_MARK_CLASS_NAME,
1865
- IMAGE_FRAME_CLASS_NAME,
1866
- IMAGE_FALLBACK_CLASS_NAME,
1867
- IMAGE_FALLBACK_ICON_CLASS_NAME,
1868
- IMAGE_FALLBACK_TEXT_CLASS_NAME,
1869
- IMAGE_FALLBACK_SOURCE_CLASS_NAME,
1870
- VIDEO_FRAME_CLASS_NAME,
1871
- VIDEO_FALLBACK_CLASS_NAME,
1872
- VIDEO_FALLBACK_ICON_CLASS_NAME,
1873
- VIDEO_FALLBACK_TEXT_CLASS_NAME,
1874
- VIDEO_FALLBACK_SOURCE_CLASS_NAME
2184
+ FRONTMATTER_LABEL_CLASS_NAME,
2185
+ IMAGE_FRAME_CLASS_NAME2,
2186
+ IMAGE_FALLBACK_CLASS_NAME2,
2187
+ IMAGE_FALLBACK_ICON_CLASS_NAME2,
2188
+ IMAGE_FALLBACK_TEXT_CLASS_NAME2,
2189
+ IMAGE_FALLBACK_SOURCE_CLASS_NAME2,
2190
+ VIDEO_FRAME_CLASS_NAME2,
2191
+ VIDEO_FALLBACK_CLASS_NAME2,
2192
+ VIDEO_FALLBACK_ICON_CLASS_NAME2,
2193
+ VIDEO_FALLBACK_TEXT_CLASS_NAME2,
2194
+ VIDEO_FALLBACK_SOURCE_CLASS_NAME2
1875
2195
  ]
1876
2196
  ];
1877
2197
  const figureAttributes = [
1878
- ["className", IMAGE_FIGURE_CLASS_NAME, VIDEO_FIGURE_CLASS_NAME]
2198
+ ["className", IMAGE_FIGURE_CLASS_NAME2, VIDEO_FIGURE_CLASS_NAME2]
1879
2199
  ];
1880
- const figcaptionAttributes = [["className", IMAGE_CAPTION_CLASS_NAME]];
2200
+ const figcaptionAttributes = [["className", IMAGE_CAPTION_CLASS_NAME2]];
1881
2201
  const imageAttributes = [
1882
2202
  ...defaultAttributes.img ?? [],
1883
- ["className", IMAGE_ELEMENT_CLASS_NAME]
2203
+ ["className", IMAGE_ELEMENT_CLASS_NAME2]
1884
2204
  ];
1885
2205
  const videoAttributes = [
1886
2206
  "src",
@@ -1895,13 +2215,17 @@ function createScribdownSanitizeSchema() {
1895
2215
  "loop",
1896
2216
  "autoPlay",
1897
2217
  "crossOrigin",
1898
- ["className", VIDEO_ELEMENT_CLASS_NAME]
2218
+ ["className", VIDEO_ELEMENT_CLASS_NAME2]
1899
2219
  ];
1900
2220
  const sourceAttributes = ["src", "type", "media"];
1901
2221
  const abbrAttributes = ["title"];
1902
2222
  const listItemAttributes = [
1903
2223
  "dataTocIndex",
1904
- ["className", "task-list-item", /^scribdown-toc-item(?:--(?:branch|depth-[1-6]))?$/u]
2224
+ [
2225
+ "className",
2226
+ "task-list-item",
2227
+ new RegExp(`^${TOC_ITEM_CLASS_PREFIX}(?:--(?:branch|depth-[1-6]))?$`, "u")
2228
+ ]
1905
2229
  ];
1906
2230
  const wildcardAttributes = [...defaultAttributes["*"] ?? [], SOURCE_LINE_HAST_PROPERTY];
1907
2231
  return {
@@ -1940,6 +2264,8 @@ function createScribdownSanitizeSchema() {
1940
2264
  a: linkAttributes,
1941
2265
  ol: orderedListAttributes,
1942
2266
  li: listItemAttributes,
2267
+ div: divAttributes,
2268
+ dl: definitionListAttributes,
1943
2269
  span: spanAttributes,
1944
2270
  abbr: abbrAttributes,
1945
2271
  figure: figureAttributes,
@@ -1950,41 +2276,235 @@ function createScribdownSanitizeSchema() {
1950
2276
  }
1951
2277
  };
1952
2278
  }
1953
- function remarkHighlightMark() {
2279
+
2280
+ // src/syntax/definition-lists.ts
2281
+ var DEFINITION_LIST_PARAGRAPH_PATTERN = /^\s*([^\n:][^\n]*)\n\s*:\s+([^\n]+)\s*$/u;
2282
+ function remarkDefinitionLists() {
1954
2283
  return (tree) => {
1955
- transformHighlightMarks(tree);
2284
+ transformDefinitionLists(tree);
1956
2285
  };
1957
2286
  }
1958
- var BLOCK_CONTAINER_NODE_TYPES = /* @__PURE__ */ new Set(["list", "listItem", "blockquote"]);
1959
- function remarkSourceLine() {
2287
+ function transformDefinitionLists(node) {
2288
+ if (!node.children) {
2289
+ return;
2290
+ }
2291
+ const childNodes = node.children;
2292
+ for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {
2293
+ const childNode = childNodes[childIndex];
2294
+ const definitionListNode = createDefinitionListNode(childNode);
2295
+ if (definitionListNode) {
2296
+ childNodes[childIndex] = definitionListNode;
2297
+ continue;
2298
+ }
2299
+ transformDefinitionLists(childNode);
2300
+ }
2301
+ }
2302
+ function createDefinitionListNode(node) {
2303
+ if (node.type !== "paragraph") {
2304
+ return void 0;
2305
+ }
2306
+ const paragraphText = extractNodeText(node);
2307
+ const definitionMatch = DEFINITION_LIST_PARAGRAPH_PATTERN.exec(paragraphText);
2308
+ if (!definitionMatch) {
2309
+ return void 0;
2310
+ }
2311
+ const definitionTerm = definitionMatch[1].trim();
2312
+ const definitionDescription = definitionMatch[2].trim();
2313
+ if (definitionTerm.length === 0 || definitionDescription.length === 0) {
2314
+ return void 0;
2315
+ }
2316
+ return {
2317
+ type: "definitionList",
2318
+ // 关键步骤:保留原段落源码位置,使 remarkSourceLine 能为 dl 标注 data-source-line。
2319
+ position: node.position,
2320
+ data: {
2321
+ hName: "dl"
2322
+ },
2323
+ children: [
2324
+ {
2325
+ type: "definitionTerm",
2326
+ data: {
2327
+ hName: "dt"
2328
+ },
2329
+ children: [{ type: "text", value: definitionTerm }]
2330
+ },
2331
+ {
2332
+ type: "definitionDescription",
2333
+ data: {
2334
+ hName: "dd"
2335
+ },
2336
+ children: [{ type: "text", value: definitionDescription }]
2337
+ }
2338
+ ]
2339
+ };
2340
+ }
2341
+
2342
+ // src/syntax/frame.ts
2343
+ import { DETAILS_CLASS_NAME as DETAILS_CLASS_NAME2, FRAME_CLASS_NAME as FRAME_CLASS_NAME2, TOC_CLASS_NAME as TOC_CLASS_NAME2 } from "@scribdown/shared";
2344
+ function rehypeFrameClass() {
1960
2345
  return (tree) => {
1961
- const blockNodes = tree.children ?? [];
1962
- blockNodes.forEach((blockNode) => {
1963
- annotateSourceLine(blockNode);
1964
- annotateNestedBlockSourceLines(blockNode);
1965
- });
2346
+ tagContentDetails(tree);
1966
2347
  };
1967
2348
  }
1968
- function annotateSourceLine(node) {
1969
- const startLine = node.position?.start.line;
1970
- if (typeof startLine !== "number") {
1971
- return;
2349
+ function tagContentDetails(node) {
2350
+ if (node.type === "element" && node.tagName === "details") {
2351
+ const classNames = normalizeClassNames(node.properties?.className);
2352
+ const isTocDetails = classNames.includes(TOC_CLASS_NAME2);
2353
+ if (!isTocDetails) {
2354
+ addClassNames(node, [DETAILS_CLASS_NAME2, FRAME_CLASS_NAME2]);
2355
+ }
2356
+ }
2357
+ if (Array.isArray(node.children)) {
2358
+ for (const childNode of node.children) {
2359
+ tagContentDetails(childNode);
2360
+ }
1972
2361
  }
1973
- const nodeData = node.data ?? {};
1974
- const hProperties = nodeData.hProperties ?? {};
1975
- hProperties[SOURCE_LINE_HAST_PROPERTY] = startLine;
1976
- nodeData.hProperties = hProperties;
1977
- node.data = nodeData;
1978
2362
  }
1979
- function annotateNestedBlockSourceLines(node) {
1980
- if (!BLOCK_CONTAINER_NODE_TYPES.has(node.type)) {
1981
- return;
2363
+ function normalizeClassNames(className) {
2364
+ if (Array.isArray(className)) {
2365
+ return className.filter((item) => typeof item === "string");
1982
2366
  }
1983
- const childNodes = node.children ?? [];
1984
- childNodes.forEach((childNode) => {
1985
- annotateSourceLine(childNode);
1986
- annotateNestedBlockSourceLines(childNode);
2367
+ if (typeof className === "string") {
2368
+ return className.split(/\s+/u).filter(Boolean);
2369
+ }
2370
+ return [];
2371
+ }
2372
+ function addClassNames(node, classNamesToAdd) {
2373
+ const mergedClassNames = normalizeClassNames(node.properties?.className);
2374
+ for (const classNameToAdd of classNamesToAdd) {
2375
+ if (!mergedClassNames.includes(classNameToAdd)) {
2376
+ mergedClassNames.push(classNameToAdd);
2377
+ }
2378
+ }
2379
+ node.properties = { ...node.properties, className: mergedClassNames };
2380
+ }
2381
+
2382
+ // src/syntax/frontmatter.ts
2383
+ import {
2384
+ FRAME_CLASS_NAME as FRAME_CLASS_NAME3,
2385
+ FRONTMATTER_CHROME_CLASS_NAME as FRONTMATTER_CHROME_CLASS_NAME2,
2386
+ FRONTMATTER_CLASS_NAME as FRONTMATTER_CLASS_NAME2,
2387
+ FRONTMATTER_LABEL_CLASS_NAME as FRONTMATTER_LABEL_CLASS_NAME2,
2388
+ FRONTMATTER_LIST_CLASS_NAME as FRONTMATTER_LIST_CLASS_NAME2,
2389
+ FRONTMATTER_LIST_NESTED_CLASS_NAME as FRONTMATTER_LIST_NESTED_CLASS_NAME2
2390
+ } from "@scribdown/shared";
2391
+ import { parse as parseYamlSource } from "yaml";
2392
+ var FRONTMATTER_LABEL_TEXT = "\u5143\u6570\u636E";
2393
+ var FRONTMATTER_ARRAY_SEPARATOR = ", ";
2394
+ var FRONTMATTER_FALLBACK_LANGUAGE_ID = "yaml";
2395
+ function remarkFrontmatterMetadata() {
2396
+ return (tree) => {
2397
+ const childNodes = tree.children ?? [];
2398
+ const yamlNode = childNodes[0];
2399
+ if (!yamlNode || yamlNode.type !== "yaml" || typeof yamlNode.value !== "string") {
2400
+ return;
2401
+ }
2402
+ childNodes[0] = createFrontmatterNode(yamlNode);
2403
+ };
2404
+ }
2405
+ function createFrontmatterNode(yamlNode) {
2406
+ const yamlSource = yamlNode.value ?? "";
2407
+ let parsedValue;
2408
+ try {
2409
+ parsedValue = parseYamlSource(yamlSource);
2410
+ } catch {
2411
+ parsedValue = void 0;
2412
+ }
2413
+ if (!isPlainRecord(parsedValue)) {
2414
+ return {
2415
+ type: "code",
2416
+ lang: FRONTMATTER_FALLBACK_LANGUAGE_ID,
2417
+ position: yamlNode.position,
2418
+ value: yamlSource
2419
+ };
2420
+ }
2421
+ return {
2422
+ type: "frontmatterMetadata",
2423
+ // 关键步骤:保留原节点源码位置,使 remarkSourceLine 能标注 data-source-line。
2424
+ position: yamlNode.position,
2425
+ data: {
2426
+ hName: "div",
2427
+ hProperties: { className: [FRONTMATTER_CLASS_NAME2, FRAME_CLASS_NAME3] }
2428
+ },
2429
+ children: [
2430
+ {
2431
+ type: "frontmatterChrome",
2432
+ data: {
2433
+ hName: "div",
2434
+ hProperties: { className: [FRONTMATTER_CHROME_CLASS_NAME2] }
2435
+ },
2436
+ children: [
2437
+ {
2438
+ type: "frontmatterLabel",
2439
+ data: {
2440
+ hName: "span",
2441
+ hProperties: { className: [FRONTMATTER_LABEL_CLASS_NAME2] }
2442
+ },
2443
+ children: [{ type: "text", value: FRONTMATTER_LABEL_TEXT }]
2444
+ }
2445
+ ]
2446
+ },
2447
+ createFrontmatterListNode(parsedValue, false)
2448
+ ]
2449
+ };
2450
+ }
2451
+ function isPlainRecord(value) {
2452
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2453
+ }
2454
+ function createFrontmatterListNode(record, nested) {
2455
+ const listClassNames = nested ? [FRONTMATTER_LIST_CLASS_NAME2, FRONTMATTER_LIST_NESTED_CLASS_NAME2] : [FRONTMATTER_LIST_CLASS_NAME2];
2456
+ const listChildren = [];
2457
+ Object.entries(record).forEach(([entryKey, entryValue]) => {
2458
+ listChildren.push({
2459
+ type: "frontmatterTerm",
2460
+ data: { hName: "dt" },
2461
+ children: [{ type: "text", value: entryKey }]
2462
+ });
2463
+ listChildren.push({
2464
+ type: "frontmatterDescription",
2465
+ data: { hName: "dd" },
2466
+ children: createFrontmatterValueNodes(entryValue)
2467
+ });
1987
2468
  });
2469
+ return {
2470
+ type: "frontmatterList",
2471
+ data: {
2472
+ hName: "dl",
2473
+ hProperties: { className: listClassNames }
2474
+ },
2475
+ children: listChildren
2476
+ };
2477
+ }
2478
+ function createFrontmatterValueNodes(value) {
2479
+ if (isPlainRecord(value)) {
2480
+ return [createFrontmatterListNode(value, true)];
2481
+ }
2482
+ if (Array.isArray(value)) {
2483
+ return [
2484
+ {
2485
+ type: "text",
2486
+ value: value.map(formatFrontmatterScalar).join(FRONTMATTER_ARRAY_SEPARATOR)
2487
+ }
2488
+ ];
2489
+ }
2490
+ return [{ type: "text", value: formatFrontmatterScalar(value) }];
2491
+ }
2492
+ function formatFrontmatterScalar(value) {
2493
+ if (value === null || value === void 0) {
2494
+ return "";
2495
+ }
2496
+ if (typeof value === "object") {
2497
+ return JSON.stringify(value);
2498
+ }
2499
+ return String(value);
2500
+ }
2501
+
2502
+ // src/syntax/highlight-mark.ts
2503
+ var HIGHLIGHT_MARKER_PATTERN = /==([^=\n]+?)==/g;
2504
+ function remarkHighlightMark() {
2505
+ return (tree) => {
2506
+ transformHighlightMarks(tree);
2507
+ };
1988
2508
  }
1989
2509
  function transformHighlightMarks(node) {
1990
2510
  if (!node.children) {
@@ -2028,197 +2548,30 @@ function splitHighlightMarks(textValue) {
2028
2548
  }
2029
2549
  return segments;
2030
2550
  }
2031
- function remarkDefinitionLists() {
2032
- return (tree) => {
2033
- transformDefinitionLists(tree);
2034
- };
2035
- }
2036
- function transformDefinitionLists(node) {
2037
- if (!node.children) {
2038
- return;
2039
- }
2040
- const childNodes = node.children;
2041
- for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {
2042
- const childNode = childNodes[childIndex];
2043
- const definitionListNode = createDefinitionListNode(childNode);
2044
- if (definitionListNode) {
2045
- childNodes[childIndex] = definitionListNode;
2046
- continue;
2047
- }
2048
- transformDefinitionLists(childNode);
2049
- }
2050
- }
2051
- function createDefinitionListNode(node) {
2052
- if (node.type !== "paragraph") {
2053
- return void 0;
2054
- }
2055
- const paragraphText = extractNodeText(node);
2056
- const definitionMatch = DEFINITION_LIST_PARAGRAPH_PATTERN.exec(paragraphText);
2057
- if (!definitionMatch) {
2058
- return void 0;
2059
- }
2060
- const definitionTerm = definitionMatch[1].trim();
2061
- const definitionDescription = definitionMatch[2].trim();
2062
- if (definitionTerm.length === 0 || definitionDescription.length === 0) {
2063
- return void 0;
2064
- }
2065
- return {
2066
- type: "definitionList",
2067
- // 关键步骤:保留原段落源码位置,使 remarkSourceLine 能为 dl 标注 data-source-line。
2068
- position: node.position,
2069
- data: {
2070
- hName: "dl"
2071
- },
2072
- children: [
2073
- {
2074
- type: "definitionTerm",
2075
- data: {
2076
- hName: "dt"
2077
- },
2078
- children: [{ type: "text", value: definitionTerm }]
2079
- },
2080
- {
2081
- type: "definitionDescription",
2082
- data: {
2083
- hName: "dd"
2084
- },
2085
- children: [{ type: "text", value: definitionDescription }]
2086
- }
2087
- ]
2088
- };
2089
- }
2090
- function remarkImageFigures() {
2091
- return (tree) => {
2092
- transformImageFigures(tree);
2093
- };
2094
- }
2095
- function transformImageFigures(node) {
2096
- if (!node.children) {
2097
- return;
2098
- }
2099
- const childNodes = node.children;
2100
- for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {
2101
- const childNode = childNodes[childIndex];
2102
- const imageFigureNode = createImageFigureNode(childNode);
2103
- if (imageFigureNode) {
2104
- childNodes[childIndex] = imageFigureNode;
2105
- continue;
2106
- }
2107
- if (isImageNode(childNode)) {
2108
- decorateImageNode(childNode);
2109
- continue;
2110
- }
2111
- transformImageFigures(childNode);
2112
- }
2113
- }
2114
- function createImageFigureNode(node) {
2115
- if (node.type !== "paragraph" || !node.children || node.children.length !== 1) {
2116
- return void 0;
2117
- }
2118
- const onlyChild = node.children[0];
2119
- if (!isImageNode(onlyChild)) {
2120
- return void 0;
2121
- }
2122
- decorateImageNode(onlyChild);
2123
- return {
2124
- type: "imageFigure",
2125
- // 关键步骤:保留原段落源码位置,使 remarkSourceLine 能为 figure 标注 data-source-line。
2126
- position: node.position,
2127
- data: {
2128
- hName: "figure",
2129
- hProperties: {
2130
- className: [IMAGE_FIGURE_CLASS_NAME]
2131
- }
2132
- },
2133
- children: [createImageFrameNode(onlyChild), ...createImageCaptionNodes(onlyChild)]
2134
- };
2135
- }
2136
- function decorateImageNode(imageNode) {
2137
- imageNode.data = {
2138
- ...imageNode.data,
2139
- hProperties: {
2140
- ...imageNode.data?.hProperties,
2141
- className: [IMAGE_ELEMENT_CLASS_NAME]
2142
- }
2143
- };
2144
- }
2145
- function createImageFrameNode(imageNode) {
2146
- return {
2147
- type: "imageFrame",
2148
- data: {
2149
- hName: "span",
2150
- hProperties: {
2151
- className: [IMAGE_FRAME_CLASS_NAME]
2152
- }
2153
- },
2154
- children: [imageNode, createImageFallbackNode(imageNode)]
2155
- };
2156
- }
2157
- function createImageCaptionNodes(imageNode) {
2158
- const imageTitle = typeof imageNode.title === "string" ? imageNode.title.trim() : "";
2159
- if (imageTitle.length === 0) {
2160
- return [];
2161
- }
2162
- return [
2163
- {
2164
- type: "imageCaption",
2165
- data: {
2166
- hName: "figcaption",
2167
- hProperties: {
2168
- className: [IMAGE_CAPTION_CLASS_NAME]
2169
- }
2170
- },
2171
- children: [{ type: "text", value: imageTitle }]
2172
- }
2173
- ];
2174
- }
2175
- function createImageFallbackNode(imageNode) {
2176
- const fallbackText = imageNode.alt?.trim() || "\u56FE\u7247\u52A0\u8F7D\u5931\u8D25";
2177
- const fallbackSource = imageNode.url ?? imageNode.identifier ?? "";
2178
- return {
2179
- type: "imageFallback",
2180
- data: {
2181
- hName: "span",
2182
- hProperties: {
2183
- className: [IMAGE_FALLBACK_CLASS_NAME]
2184
- }
2185
- },
2186
- children: [
2187
- {
2188
- type: "imageFallbackIcon",
2189
- data: {
2190
- hName: "span",
2191
- hProperties: {
2192
- className: [IMAGE_FALLBACK_ICON_CLASS_NAME]
2193
- }
2194
- }
2195
- },
2196
- {
2197
- type: "imageFallbackText",
2198
- data: {
2199
- hName: "span",
2200
- hProperties: {
2201
- className: [IMAGE_FALLBACK_TEXT_CLASS_NAME]
2202
- }
2203
- },
2204
- children: [{ type: "text", value: fallbackText }]
2205
- },
2206
- {
2207
- type: "imageFallbackSource",
2208
- data: {
2209
- hName: "span",
2210
- hProperties: {
2211
- className: [IMAGE_FALLBACK_SOURCE_CLASS_NAME]
2212
- }
2213
- },
2214
- children: [{ type: "text", value: fallbackSource }]
2215
- }
2216
- ]
2217
- };
2218
- }
2219
- function isImageNode(node) {
2220
- return node.type === "image" || node.type === "imageReference";
2221
- }
2551
+
2552
+ // src/syntax/toc.ts
2553
+ import {
2554
+ FRAME_CLASS_NAME as FRAME_CLASS_NAME4,
2555
+ HEADING_MARK_CLASS_NAME as HEADING_MARK_CLASS_NAME2,
2556
+ SCRIBDOWN_MARKDOWN_CLASS_NAME,
2557
+ TOC_CLASS_NAME as TOC_CLASS_NAME3,
2558
+ TOC_ITEM_BRANCH_CLASS_NAME,
2559
+ TOC_ITEM_CLASS_PREFIX as TOC_ITEM_CLASS_PREFIX2,
2560
+ TOC_ITEM_COLLAPSED_CLASS_NAME,
2561
+ TOC_LINK_CLASS_NAME as TOC_LINK_CLASS_NAME2,
2562
+ TOC_LIST_CLASS_NAME as TOC_LIST_CLASS_NAME2,
2563
+ TOC_LIST_NESTED_CLASS_NAME as TOC_LIST_NESTED_CLASS_NAME2,
2564
+ TOC_NAV_CLASS_NAME as TOC_NAV_CLASS_NAME2,
2565
+ TOC_SUMMARY_CLASS_NAME as TOC_SUMMARY_CLASS_NAME2,
2566
+ TOC_TOGGLE_CLASS_NAME as TOC_TOGGLE_CLASS_NAME2
2567
+ } from "@scribdown/shared";
2568
+ var TOC_MARKER_PATTERN = /^\s*\[toc]\s*$/i;
2569
+ var TOC_TOGGLE_ARIA_LABEL = "\u5C55\u5F00\u6216\u6298\u53E0\u5B50\u76EE\u5F55";
2570
+ var TOC_TOGGLE_HYDRATED_DATA_KEY = "scribdownTocToggleHydrated";
2571
+ var TOC_LINK_HYDRATED_DATA_KEY = "scribdownTocLinkHydrated";
2572
+ var TOC_ARIA_LABEL = "\u76EE\u5F55";
2573
+ var TOC_SUMMARY_TEXT = "\u76EE\u5F55";
2574
+ var EMPTY_HEADING_SLUG_PREFIX = "section";
2222
2575
  function remarkTableOfContents() {
2223
2576
  return (tree) => {
2224
2577
  const tocHeadings = collectTocHeadings(tree);
@@ -2256,7 +2609,7 @@ function collectTocHeadings(tree) {
2256
2609
  data: {
2257
2610
  hName: "span",
2258
2611
  hProperties: {
2259
- className: [HEADING_MARK_CLASS_NAME]
2612
+ className: [HEADING_MARK_CLASS_NAME2]
2260
2613
  }
2261
2614
  },
2262
2615
  children: node.children ?? []
@@ -2271,16 +2624,6 @@ function collectTocHeadings(tree) {
2271
2624
  });
2272
2625
  return tocHeadings;
2273
2626
  }
2274
- function visitMarkdownNode(node, visitor) {
2275
- visitor(node);
2276
- if (!node.children) {
2277
- return;
2278
- }
2279
- const childNodes = node.children;
2280
- for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {
2281
- visitMarkdownNode(childNodes[childIndex], visitor);
2282
- }
2283
- }
2284
2627
  function replaceTocMarkers(node, tocHeadings) {
2285
2628
  if (!node.children) {
2286
2629
  return;
@@ -2312,7 +2655,7 @@ function createTocNode(tocHeadings, markerNode) {
2312
2655
  hName: "details",
2313
2656
  hProperties: {
2314
2657
  // 关键步骤:目录根 opt-in 手绘边框,再由 toc.css 叠加目录自身样式。
2315
- className: [TOC_CLASS_NAME, FRAME_CLASS_NAME]
2658
+ className: [TOC_CLASS_NAME3, FRAME_CLASS_NAME4]
2316
2659
  }
2317
2660
  },
2318
2661
  children: [
@@ -2321,7 +2664,7 @@ function createTocNode(tocHeadings, markerNode) {
2321
2664
  data: {
2322
2665
  hName: "summary",
2323
2666
  hProperties: {
2324
- className: [TOC_SUMMARY_CLASS_NAME]
2667
+ className: [TOC_SUMMARY_CLASS_NAME2]
2325
2668
  }
2326
2669
  },
2327
2670
  children: [
@@ -2337,7 +2680,7 @@ function createTocNode(tocHeadings, markerNode) {
2337
2680
  hName: "nav",
2338
2681
  hProperties: {
2339
2682
  ariaLabel: TOC_ARIA_LABEL,
2340
- className: [TOC_NAV_CLASS_NAME]
2683
+ className: [TOC_NAV_CLASS_NAME2]
2341
2684
  }
2342
2685
  },
2343
2686
  children: [createTocListNode(tocTree, false)]
@@ -2367,7 +2710,7 @@ function createTocTree(tocHeadings) {
2367
2710
  return rootItems;
2368
2711
  }
2369
2712
  function createTocListNode(tocItems, isNested) {
2370
- const tocListClassNames = isNested ? [TOC_LIST_CLASS_NAME, TOC_LIST_NESTED_CLASS_NAME] : [TOC_LIST_CLASS_NAME];
2713
+ const tocListClassNames = isNested ? [TOC_LIST_CLASS_NAME2, TOC_LIST_NESTED_CLASS_NAME2] : [TOC_LIST_CLASS_NAME2];
2371
2714
  return {
2372
2715
  type: "list",
2373
2716
  ordered: true,
@@ -2398,8 +2741,8 @@ function createTocListItem(tocItem) {
2398
2741
  }
2399
2742
  function createTocItemClassNames(depth, hasChildren) {
2400
2743
  return [
2401
- TOC_ITEM_CLASS_PREFIX,
2402
- `${TOC_ITEM_CLASS_PREFIX}--depth-${depth}`,
2744
+ TOC_ITEM_CLASS_PREFIX2,
2745
+ `${TOC_ITEM_CLASS_PREFIX2}--depth-${depth}`,
2403
2746
  ...hasChildren ? [TOC_ITEM_BRANCH_CLASS_NAME] : []
2404
2747
  ];
2405
2748
  }
@@ -2410,7 +2753,7 @@ function createTocToggleNode() {
2410
2753
  hName: "button",
2411
2754
  hProperties: {
2412
2755
  type: "button",
2413
- className: [TOC_TOGGLE_CLASS_NAME],
2756
+ className: [TOC_TOGGLE_CLASS_NAME2],
2414
2757
  ariaExpanded: "true",
2415
2758
  ariaLabel: TOC_TOGGLE_ARIA_LABEL
2416
2759
  }
@@ -2418,22 +2761,49 @@ function createTocToggleNode() {
2418
2761
  children: []
2419
2762
  };
2420
2763
  }
2421
- function createTocLinkNode(tocItem) {
2422
- return {
2423
- type: "link",
2424
- url: `#${tocItem.id}`,
2425
- data: {
2426
- hProperties: {
2427
- className: [TOC_LINK_CLASS_NAME]
2428
- }
2429
- },
2430
- children: [
2431
- {
2432
- type: "text",
2433
- value: tocItem.text
2434
- }
2435
- ]
2436
- };
2764
+ function createTocLinkNode(tocItem) {
2765
+ return {
2766
+ type: "link",
2767
+ url: `#${tocItem.id}`,
2768
+ data: {
2769
+ hProperties: {
2770
+ className: [TOC_LINK_CLASS_NAME2]
2771
+ }
2772
+ },
2773
+ children: [
2774
+ {
2775
+ type: "text",
2776
+ value: tocItem.text
2777
+ }
2778
+ ]
2779
+ };
2780
+ }
2781
+ function createUniqueSlug(headingText, slugCounts, fallbackIndex) {
2782
+ const normalizedSlug = normalizeSlugText(headingText);
2783
+ const fallbackSlug = `${EMPTY_HEADING_SLUG_PREFIX}-${fallbackIndex}`;
2784
+ const baseSlug = normalizedSlug || fallbackSlug;
2785
+ const usedCount = slugCounts.get(baseSlug) ?? 0;
2786
+ slugCounts.set(baseSlug, usedCount + 1);
2787
+ if (usedCount === 0) {
2788
+ return baseSlug;
2789
+ }
2790
+ return `${baseSlug}-${usedCount}`;
2791
+ }
2792
+ function createHeadingIndex(headingDepth, headingIndexCounts, rootHeadingDepth) {
2793
+ const startDepth = Math.min(rootHeadingDepth, headingDepth);
2794
+ headingIndexCounts[headingDepth] += 1;
2795
+ for (let depthIndex = headingDepth + 1; depthIndex < headingIndexCounts.length; depthIndex += 1) {
2796
+ headingIndexCounts[depthIndex] = 0;
2797
+ }
2798
+ for (let depthIndex = startDepth; depthIndex < headingDepth; depthIndex += 1) {
2799
+ if (headingIndexCounts[depthIndex] === 0) {
2800
+ headingIndexCounts[depthIndex] = 1;
2801
+ }
2802
+ }
2803
+ return headingIndexCounts.slice(startDepth, headingDepth + 1).join(".");
2804
+ }
2805
+ function normalizeSlugText(headingText) {
2806
+ return headingText.trim().toLowerCase().replace(/<[^>]+>/g, "").replace(/[^\p{L}\p{N}\s_-]+/gu, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
2437
2807
  }
2438
2808
  function collectTocHeadingsFromDom(rootElement) {
2439
2809
  const markdownContainer = rootElement instanceof Element && rootElement.matches(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME}`) ? rootElement : rootElement.querySelector(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME}`);
@@ -2473,13 +2843,13 @@ function collectTocHeadingsFromDom(rootElement) {
2473
2843
  function createTocNavElement(ownerDocument, tocTreeItems) {
2474
2844
  const navElement = ownerDocument.createElement("nav");
2475
2845
  navElement.setAttribute("aria-label", TOC_ARIA_LABEL);
2476
- navElement.className = TOC_NAV_CLASS_NAME;
2846
+ navElement.className = TOC_NAV_CLASS_NAME2;
2477
2847
  navElement.appendChild(createTocListElement(ownerDocument, tocTreeItems, false));
2478
2848
  return navElement;
2479
2849
  }
2480
2850
  function createTocListElement(ownerDocument, tocItems, isNested) {
2481
2851
  const listElement = ownerDocument.createElement("ol");
2482
- listElement.className = isNested ? `${TOC_LIST_CLASS_NAME} ${TOC_LIST_NESTED_CLASS_NAME}` : TOC_LIST_CLASS_NAME;
2852
+ listElement.className = isNested ? `${TOC_LIST_CLASS_NAME2} ${TOC_LIST_NESTED_CLASS_NAME2}` : TOC_LIST_CLASS_NAME2;
2483
2853
  for (const tocItem of tocItems) {
2484
2854
  listElement.appendChild(createTocListItemElement(ownerDocument, tocItem));
2485
2855
  }
@@ -2493,13 +2863,13 @@ function createTocListItemElement(ownerDocument, tocItem) {
2493
2863
  if (hasChildren) {
2494
2864
  const toggleElement = ownerDocument.createElement("button");
2495
2865
  toggleElement.type = "button";
2496
- toggleElement.className = TOC_TOGGLE_CLASS_NAME;
2866
+ toggleElement.className = TOC_TOGGLE_CLASS_NAME2;
2497
2867
  toggleElement.setAttribute("aria-expanded", "true");
2498
2868
  toggleElement.setAttribute("aria-label", TOC_TOGGLE_ARIA_LABEL);
2499
2869
  itemElement.appendChild(toggleElement);
2500
2870
  }
2501
2871
  const linkElement = ownerDocument.createElement("a");
2502
- linkElement.className = TOC_LINK_CLASS_NAME;
2872
+ linkElement.className = TOC_LINK_CLASS_NAME2;
2503
2873
  linkElement.href = `#${tocItem.id}`;
2504
2874
  linkElement.textContent = tocItem.text;
2505
2875
  itemElement.appendChild(linkElement);
@@ -2510,7 +2880,7 @@ function createTocListItemElement(ownerDocument, tocItem) {
2510
2880
  }
2511
2881
  function hydrateToc(rootElement, scrollToHeading) {
2512
2882
  const toggleElements = rootElement.querySelectorAll(
2513
- `button.${TOC_TOGGLE_CLASS_NAME}`
2883
+ `button.${TOC_TOGGLE_CLASS_NAME2}`
2514
2884
  );
2515
2885
  toggleElements.forEach((toggleElement) => {
2516
2886
  if (toggleElement.dataset[TOC_TOGGLE_HYDRATED_DATA_KEY] === "true") {
@@ -2518,7 +2888,7 @@ function hydrateToc(rootElement, scrollToHeading) {
2518
2888
  }
2519
2889
  toggleElement.dataset[TOC_TOGGLE_HYDRATED_DATA_KEY] = "true";
2520
2890
  toggleElement.addEventListener("click", () => {
2521
- const itemElement = toggleElement.closest(`.${TOC_ITEM_CLASS_PREFIX}`);
2891
+ const itemElement = toggleElement.closest(`.${TOC_ITEM_CLASS_PREFIX2}`);
2522
2892
  if (!itemElement) {
2523
2893
  return;
2524
2894
  }
@@ -2528,7 +2898,7 @@ function hydrateToc(rootElement, scrollToHeading) {
2528
2898
  });
2529
2899
  });
2530
2900
  const linkElements = rootElement.querySelectorAll(
2531
- `a.${TOC_LINK_CLASS_NAME}`
2901
+ `a.${TOC_LINK_CLASS_NAME2}`
2532
2902
  );
2533
2903
  linkElements.forEach((linkElement) => {
2534
2904
  if (linkElement.dataset[TOC_LINK_HYDRATED_DATA_KEY] === "true") {
@@ -2556,192 +2926,37 @@ function hydrateToc(rootElement, scrollToHeading) {
2556
2926
  });
2557
2927
  });
2558
2928
  }
2559
- function extractNodeText(node) {
2560
- if (node.type === "text" || node.type === "inlineCode") {
2561
- return node.value ?? "";
2562
- }
2563
- if (isImageNode(node)) {
2564
- return node.alt ?? "";
2565
- }
2566
- if (!node.children) {
2567
- return "";
2568
- }
2569
- return node.children.map(extractNodeText).join("");
2570
- }
2571
- function createUniqueSlug(headingText, slugCounts, fallbackIndex) {
2572
- const normalizedSlug = normalizeSlugText(headingText);
2573
- const fallbackSlug = `${EMPTY_HEADING_SLUG_PREFIX}-${fallbackIndex}`;
2574
- const baseSlug = normalizedSlug || fallbackSlug;
2575
- const usedCount = slugCounts.get(baseSlug) ?? 0;
2576
- slugCounts.set(baseSlug, usedCount + 1);
2577
- if (usedCount === 0) {
2578
- return baseSlug;
2579
- }
2580
- return `${baseSlug}-${usedCount}`;
2581
- }
2582
- function createHeadingIndex(headingDepth, headingIndexCounts, rootHeadingDepth) {
2583
- const startDepth = Math.min(rootHeadingDepth, headingDepth);
2584
- headingIndexCounts[headingDepth] += 1;
2585
- for (let depthIndex = headingDepth + 1; depthIndex < headingIndexCounts.length; depthIndex += 1) {
2586
- headingIndexCounts[depthIndex] = 0;
2587
- }
2588
- for (let depthIndex = startDepth; depthIndex < headingDepth; depthIndex += 1) {
2589
- if (headingIndexCounts[depthIndex] === 0) {
2590
- headingIndexCounts[depthIndex] = 1;
2591
- }
2592
- }
2593
- return headingIndexCounts.slice(startDepth, headingDepth + 1).join(".");
2594
- }
2595
- function normalizeSlugText(headingText) {
2596
- return headingText.trim().toLowerCase().replace(/<[^>]+>/g, "").replace(/[^\p{L}\p{N}\s_-]+/gu, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
2597
- }
2598
- function rehypeFrameClass() {
2599
- return (tree) => {
2600
- tagContentDetails(tree);
2601
- };
2602
- }
2603
- function tagContentDetails(node) {
2604
- if (node.type === "element" && node.tagName === "details") {
2605
- const classNames = normalizeClassNames(node.properties?.className);
2606
- const isTocDetails = classNames.includes(TOC_CLASS_NAME);
2607
- if (!isTocDetails) {
2608
- addClassNames(node, [DETAILS_CLASS_NAME, FRAME_CLASS_NAME]);
2609
- }
2610
- }
2611
- if (Array.isArray(node.children)) {
2612
- for (const childNode of node.children) {
2613
- tagContentDetails(childNode);
2614
- }
2615
- }
2616
- }
2617
- function normalizeClassNames(className) {
2618
- if (Array.isArray(className)) {
2619
- return className.filter((item) => typeof item === "string");
2620
- }
2621
- if (typeof className === "string") {
2622
- return className.split(/\s+/u).filter(Boolean);
2623
- }
2624
- return [];
2625
- }
2626
- function addClassNames(node, classNamesToAdd) {
2627
- const mergedClassNames = normalizeClassNames(node.properties?.className);
2628
- for (const classNameToAdd of classNamesToAdd) {
2629
- if (!mergedClassNames.includes(classNameToAdd)) {
2630
- mergedClassNames.push(classNameToAdd);
2631
- }
2632
- }
2633
- node.properties = { ...node.properties, className: mergedClassNames };
2634
- }
2635
- function rehypeVideoFigures() {
2636
- return (tree) => {
2637
- transformVideoFigures(tree);
2638
- };
2639
- }
2640
- function transformVideoFigures(node) {
2641
- if (!Array.isArray(node.children)) {
2642
- return;
2643
- }
2644
- const childNodes = node.children;
2645
- for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {
2646
- const childNode = childNodes[childIndex];
2647
- if (isHastVideoElement(childNode)) {
2648
- childNodes[childIndex] = createVideoFigureHast(childNode);
2649
- continue;
2650
- }
2651
- const standaloneVideo = extractStandaloneParagraphVideo(childNode);
2652
- if (standaloneVideo) {
2653
- childNodes[childIndex] = createVideoFigureHast(standaloneVideo);
2654
- continue;
2655
- }
2656
- transformVideoFigures(childNode);
2657
- }
2658
- }
2659
- function isHastVideoElement(node) {
2660
- return node.type === "element" && node.tagName === "video";
2661
- }
2662
- function extractStandaloneParagraphVideo(node) {
2663
- if (node.type !== "element" || node.tagName !== "p" || !Array.isArray(node.children)) {
2664
- return void 0;
2665
- }
2666
- const significantChildren = node.children.filter((childNode) => {
2667
- if (childNode.type === "text" && typeof childNode.value === "string") {
2668
- return childNode.value.trim().length > 0;
2669
- }
2670
- return true;
2671
- });
2672
- if (significantChildren.length === 1 && isHastVideoElement(significantChildren[0])) {
2673
- return significantChildren[0];
2674
- }
2675
- return void 0;
2676
- }
2677
- function createVideoFigureHast(videoNode) {
2678
- decorateHastVideoElement(videoNode);
2679
- const sourceUrl = readHastVideoSourceUrl(videoNode);
2680
- return {
2681
- type: "element",
2682
- tagName: "figure",
2683
- properties: { className: [VIDEO_FIGURE_CLASS_NAME] },
2684
- children: [
2685
- {
2686
- type: "element",
2687
- tagName: "span",
2688
- properties: { className: [VIDEO_FRAME_CLASS_NAME] },
2689
- children: [videoNode, createVideoFallbackHast(sourceUrl)]
2690
- }
2691
- ]
2692
- };
2693
- }
2694
- function decorateHastVideoElement(videoNode) {
2695
- const properties = videoNode.properties ?? {};
2696
- const existingClassName = properties.className;
2697
- const classList = Array.isArray(existingClassName) ? existingClassName.map(String) : typeof existingClassName === "string" ? [existingClassName] : [];
2698
- if (!classList.includes(VIDEO_ELEMENT_CLASS_NAME)) {
2699
- classList.push(VIDEO_ELEMENT_CLASS_NAME);
2700
- }
2701
- properties.className = classList;
2702
- videoNode.properties = properties;
2703
- }
2704
- function readHastVideoSourceUrl(videoNode) {
2705
- const properties = videoNode.properties ?? {};
2706
- if (typeof properties.src === "string") {
2707
- return properties.src;
2708
- }
2709
- if (Array.isArray(videoNode.children)) {
2710
- for (const childNode of videoNode.children) {
2711
- if (childNode.type === "element" && childNode.tagName === "source" && typeof childNode.properties?.src === "string") {
2712
- return childNode.properties.src;
2713
- }
2714
- }
2715
- }
2716
- return "";
2717
- }
2718
- function createVideoFallbackHast(sourceUrl) {
2719
- return {
2720
- type: "element",
2721
- tagName: "span",
2722
- properties: { className: [VIDEO_FALLBACK_CLASS_NAME] },
2723
- children: [
2724
- {
2725
- type: "element",
2726
- tagName: "span",
2727
- properties: { className: [VIDEO_FALLBACK_ICON_CLASS_NAME] },
2728
- children: []
2729
- },
2730
- {
2731
- type: "element",
2732
- tagName: "span",
2733
- properties: { className: [VIDEO_FALLBACK_TEXT_CLASS_NAME] },
2734
- children: [{ type: "text", value: VIDEO_FALLBACK_DEFAULT_TEXT }]
2735
- },
2736
- {
2737
- type: "element",
2738
- tagName: "span",
2739
- properties: { className: [VIDEO_FALLBACK_SOURCE_CLASS_NAME] },
2740
- children: [{ type: "text", value: sourceUrl }]
2741
- }
2742
- ]
2743
- };
2744
- }
2929
+
2930
+ // src/toolbar.ts
2931
+ import {
2932
+ CONTENT_WIDTH_STORAGE_KEY,
2933
+ PROJECT_HOMEPAGE_URL,
2934
+ SCRIBDOWN_CONTENT_AREA_CLASS_NAME,
2935
+ SCRIBDOWN_CONTENT_SCROLL_CLASS_NAME,
2936
+ SCRIBDOWN_MARKDOWN_CLASS_NAME as SCRIBDOWN_MARKDOWN_CLASS_NAME2,
2937
+ SCRIBDOWN_THIN_SCROLLBAR_CLASS_NAME,
2938
+ SCRIBDOWN_TOC_HOST_CLASS_NAME,
2939
+ SCRIBDOWN_TOOLBAR_BTN_CLASS_NAME,
2940
+ SCRIBDOWN_TOOLBAR_CLASS_NAME,
2941
+ SCRIBDOWN_TOOLBAR_CURRENT_CLASS_NAME,
2942
+ SCRIBDOWN_TOOLBAR_CURRENT_TEXT_CLASS_NAME,
2943
+ SCRIBDOWN_TOOLBAR_MENU_CLASS_NAME,
2944
+ SCRIBDOWN_TOOLBAR_MENU_GROUP_CHOICES_CLASS_NAME,
2945
+ SCRIBDOWN_TOOLBAR_MENU_GROUP_CLASS_NAME,
2946
+ SCRIBDOWN_TOOLBAR_MENU_ITEM_CLASS_NAME,
2947
+ SCRIBDOWN_TOOLBAR_MENU_SELECT_CHEVRON_CLASS_NAME,
2948
+ SCRIBDOWN_TOOLBAR_MENU_SELECT_LABEL_CLASS_NAME,
2949
+ SCRIBDOWN_TOOLBAR_MENU_SELECT_VALUE_CLASS_NAME,
2950
+ SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_CLASS_NAME,
2951
+ SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_LABEL_CLASS_NAME,
2952
+ SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_MARK_CLASS_NAME,
2953
+ SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME,
2954
+ SCRIBDOWN_TOOLBAR_TOC_PANEL_CLOSE_CLASS_NAME,
2955
+ SCRIBDOWN_TOOLBAR_TOC_PANEL_EMPTY_CLASS_NAME,
2956
+ SCRIBDOWN_TOOLBAR_TOC_PANEL_TITLE_CLASS_NAME,
2957
+ TOC_LINK_ACTIVE_CLASS_NAME,
2958
+ TOC_LINK_CLASS_NAME as TOC_LINK_CLASS_NAME3
2959
+ } from "@scribdown/shared";
2745
2960
  var TOOLBAR_WIDTH_PRESETS = [
2746
2961
  { label: "680", value: "680px" },
2747
2962
  { label: "840", value: "840px" },
@@ -2778,10 +2993,11 @@ var TOOLBAR_MORE_ICON_SVG = '<svg width="16" height="16" viewBox="0 0 18 18" fil
2778
2993
  var TOOLBAR_BACK_TOP_ICON_SVG = '<svg width="16" height="16" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M9 14V5M9 5L5 9M9 5l4 4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/><line x1="4" y1="3.5" x2="14" y2="3.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>';
2779
2994
  var TOOLBAR_WIDTH_ICON_SVG = '<svg width="16" height="16" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M3 6v6M15 6v6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><path d="M5.5 9H3l2.5-2.5M3 9l2.5 2.5M12.5 9H15l-2.5-2.5M15 9l-2.5 2.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>';
2780
2995
  var TOOLBAR_ABOUT_ICON_SVG = '<svg width="16" height="16" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><circle cx="9" cy="9" r="6.5" stroke="currentColor" stroke-width="1.6"/><path d="M9 8v4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><circle cx="9" cy="5.6" r="0.9" fill="currentColor"/></svg>';
2781
- var pageToolbarScrollspyTeardowns = /* @__PURE__ */ new WeakMap();
2996
+ var pageToolbarTeardowns = /* @__PURE__ */ new WeakMap();
2782
2997
  function mountPageToolbar(ownerDocument, container, scrollToHeading) {
2783
- pageToolbarScrollspyTeardowns.get(container)?.();
2784
- pageToolbarScrollspyTeardowns.delete(container);
2998
+ pageToolbarTeardowns.get(container)?.();
2999
+ pageToolbarTeardowns.delete(container);
3000
+ const teardownCallbacks = [];
2785
3001
  container.querySelector(`:scope > .${SCRIBDOWN_TOOLBAR_CLASS_NAME}`)?.remove();
2786
3002
  container.querySelector(`:scope > .${SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME}`)?.remove();
2787
3003
  container.classList.add(SCRIBDOWN_TOC_HOST_CLASS_NAME);
@@ -2815,13 +3031,13 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
2815
3031
  const tocPanel = ownerDocument.createElement("div");
2816
3032
  tocPanel.className = `${SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME} ${SCRIBDOWN_THIN_SCROLLBAR_CLASS_NAME}`;
2817
3033
  const tocTitle = ownerDocument.createElement("div");
2818
- tocTitle.className = "scribdown-toolbar-toc-panel-title";
3034
+ tocTitle.className = SCRIBDOWN_TOOLBAR_TOC_PANEL_TITLE_CLASS_NAME;
2819
3035
  const tocTitleText = ownerDocument.createElement("span");
2820
3036
  tocTitleText.textContent = "\u76EE\u5F55";
2821
3037
  tocTitle.appendChild(tocTitleText);
2822
3038
  const tocCloseBtn = ownerDocument.createElement("button");
2823
3039
  tocCloseBtn.type = "button";
2824
- tocCloseBtn.className = "scribdown-toolbar-toc-panel-close";
3040
+ tocCloseBtn.className = SCRIBDOWN_TOOLBAR_TOC_PANEL_CLOSE_CLASS_NAME;
2825
3041
  tocCloseBtn.setAttribute("aria-label", "\u5173\u95ED\u76EE\u5F55");
2826
3042
  tocCloseBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>';
2827
3043
  const setTocOpen = (open) => {
@@ -2836,7 +3052,7 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
2836
3052
  const tocHeadings = collectTocHeadingsFromDom(container);
2837
3053
  if (tocHeadings.length === 0) {
2838
3054
  const emptyElement = ownerDocument.createElement("p");
2839
- emptyElement.className = "scribdown-toolbar-toc-panel-empty";
3055
+ emptyElement.className = SCRIBDOWN_TOOLBAR_TOC_PANEL_EMPTY_CLASS_NAME;
2840
3056
  emptyElement.textContent = "\u6682\u65E0\u6807\u9898";
2841
3057
  tocPanel.appendChild(emptyElement);
2842
3058
  } else {
@@ -2866,17 +3082,17 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
2866
3082
  widthTrigger.setAttribute("aria-haspopup", "listbox");
2867
3083
  widthTrigger.setAttribute("aria-expanded", "false");
2868
3084
  const widthValueElement = ownerDocument.createElement("span");
2869
- widthValueElement.className = "scribdown-toolbar-menu-select-value";
3085
+ widthValueElement.className = SCRIBDOWN_TOOLBAR_MENU_SELECT_VALUE_CLASS_NAME;
2870
3086
  widthValueElement.textContent = TOOLBAR_WIDTH_PRESETS[widthIndex].label;
2871
- widthTrigger.innerHTML = `${TOOLBAR_WIDTH_ICON_SVG}<span class="scribdown-toolbar-menu-select-label">\u9875\u9762\u5BBD\u5EA6</span>`;
3087
+ widthTrigger.innerHTML = `${TOOLBAR_WIDTH_ICON_SVG}<span class="${SCRIBDOWN_TOOLBAR_MENU_SELECT_LABEL_CLASS_NAME}">\u9875\u9762\u5BBD\u5EA6</span>`;
2872
3088
  widthTrigger.appendChild(widthValueElement);
2873
3089
  widthTrigger.insertAdjacentHTML(
2874
3090
  "beforeend",
2875
- '<svg class="scribdown-toolbar-menu-select-chevron" width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true"><path d="M3 4.5l3 3 3-3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>'
3091
+ `<svg class="${SCRIBDOWN_TOOLBAR_MENU_SELECT_CHEVRON_CLASS_NAME}" width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true"><path d="M3 4.5l3 3 3-3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>`
2876
3092
  );
2877
3093
  widthGroup.appendChild(widthTrigger);
2878
3094
  const widthChoices = ownerDocument.createElement("div");
2879
- widthChoices.className = "scribdown-toolbar-menu-group-choices";
3095
+ widthChoices.className = SCRIBDOWN_TOOLBAR_MENU_GROUP_CHOICES_CLASS_NAME;
2880
3096
  widthChoices.setAttribute("role", "listbox");
2881
3097
  const widthSubItems = [];
2882
3098
  TOOLBAR_WIDTH_PRESETS.forEach((preset, idx) => {
@@ -2885,7 +3101,7 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
2885
3101
  subItem.className = SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_CLASS_NAME;
2886
3102
  subItem.setAttribute("role", "option");
2887
3103
  subItem.setAttribute("aria-selected", String(idx === widthIndex));
2888
- subItem.innerHTML = `<span class="scribdown-toolbar-menu-sub-item-mark" aria-hidden="true"><svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2.5 6.5l2.5 2.5 4.5-5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="scribdown-toolbar-menu-sub-item-label">${preset.label}</span>`;
3104
+ subItem.innerHTML = `<span class="${SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_MARK_CLASS_NAME}" aria-hidden="true"><svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2.5 6.5l2.5 2.5 4.5-5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="${SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_LABEL_CLASS_NAME}">${preset.label}</span>`;
2889
3105
  subItem.addEventListener("click", () => {
2890
3106
  widthIndex = idx;
2891
3107
  applyContentWidth(ownerDocument, preset.value);
@@ -2948,13 +3164,17 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
2948
3164
  const opened = menu.classList.toggle("is-open");
2949
3165
  moreBtn.setAttribute("aria-expanded", String(opened));
2950
3166
  });
2951
- ownerDocument.addEventListener("click", (e) => {
3167
+ const handleDocumentClick = (e) => {
2952
3168
  const target = e.target;
2953
3169
  if (!menu.contains(target) && target !== moreBtn) {
2954
3170
  closeMoreMenu();
2955
3171
  }
3172
+ };
3173
+ ownerDocument.addEventListener("click", handleDocumentClick);
3174
+ teardownCallbacks.push(() => {
3175
+ ownerDocument.removeEventListener("click", handleDocumentClick);
2956
3176
  });
2957
- const markdownContainer = container.matches(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME}`) ? container : container.querySelector(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME}`);
3177
+ const markdownContainer = container.matches(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME2}`) ? container : container.querySelector(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME2}`);
2958
3178
  const headingElements = markdownContainer ? Array.from(
2959
3179
  markdownContainer.querySelectorAll(
2960
3180
  "h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]"
@@ -2967,12 +3187,12 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
2967
3187
  currentHeading.setAttribute("role", "button");
2968
3188
  currentHeading.tabIndex = 0;
2969
3189
  const currentHeadingText = ownerDocument.createElement("span");
2970
- currentHeadingText.className = "scribdown-toolbar-current-text";
3190
+ currentHeadingText.className = SCRIBDOWN_TOOLBAR_CURRENT_TEXT_CLASS_NAME;
2971
3191
  currentHeading.appendChild(currentHeadingText);
2972
3192
  let lastHeadingText = "";
2973
3193
  let activeHeadingElement = headingElements[0];
2974
3194
  const tocLinkByHeading = /* @__PURE__ */ new Map();
2975
- tocPanel.querySelectorAll(`a.${TOC_LINK_CLASS_NAME}`).forEach((linkElement) => {
3195
+ tocPanel.querySelectorAll(`a.${TOC_LINK_CLASS_NAME3}`).forEach((linkElement) => {
2976
3196
  const rawTargetId = (linkElement.getAttribute("href") ?? "").slice(1);
2977
3197
  if (!rawTargetId) {
2978
3198
  return;
@@ -3050,7 +3270,7 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
3050
3270
  });
3051
3271
  ownerWindow?.addEventListener("resize", onScrollOrResize, { passive: true });
3052
3272
  updateCurrentHeading();
3053
- pageToolbarScrollspyTeardowns.set(container, () => {
3273
+ teardownCallbacks.push(() => {
3054
3274
  if (scrollRafId) {
3055
3275
  ownerWindow?.cancelAnimationFrame(scrollRafId);
3056
3276
  }
@@ -3064,6 +3284,40 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
3064
3284
  toolbar.appendChild(menu);
3065
3285
  container.appendChild(toolbar);
3066
3286
  container.insertBefore(tocPanel, contentArea);
3287
+ pageToolbarTeardowns.set(container, () => {
3288
+ teardownCallbacks.forEach((teardownCallback) => {
3289
+ teardownCallback();
3290
+ });
3291
+ });
3292
+ }
3293
+
3294
+ // src/index.ts
3295
+ async function renderMarkdown(markdownText) {
3296
+ const processor = unified().use(remarkParse).use(remarkGfm).use(remarkFrontmatter).use(remarkFrontmatterMetadata).use(remarkHighlightMark).use(remarkDefinitionLists).use(remarkTableOfContents).use(remarkImageFigures).use(remarkSourceLine).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeRaw).use(rehypeFrameClass).use(rehypeVideoFigures).use(rehypeSanitize, createScribdownSanitizeSchema()).use(rehypeStringify);
3297
+ const renderedHtml = String(await processor.process(markdownText));
3298
+ const sanitizedHtml = sanitizeHtmlWithDomPurify(renderedHtml);
3299
+ return highlightMarkdownCodeBlocks(sanitizedHtml);
3300
+ }
3301
+ function defaultScrollToHeading(targetElement) {
3302
+ targetElement.scrollIntoView({ behavior: "smooth", block: "start" });
3303
+ }
3304
+ function hydrateMarkdown(rootElement, options = {}) {
3305
+ hydrateMarkdownImages(rootElement);
3306
+ hydrateMarkdownVideos(rootElement);
3307
+ hydrateMermaidBlocks(rootElement);
3308
+ hydrateCodeBlocks(rootElement);
3309
+ hydrateToc(rootElement, options.scrollToHeading ?? defaultScrollToHeading);
3310
+ }
3311
+ function mountMarkdownToolbar(container, options = {}) {
3312
+ const ownerDocument = container.ownerDocument;
3313
+ if (!ownerDocument) {
3314
+ return;
3315
+ }
3316
+ if (typeof ownerDocument.defaultView?.scrollTo !== "function") {
3317
+ return;
3318
+ }
3319
+ applyContentWidth(ownerDocument, loadContentWidth());
3320
+ mountPageToolbar(ownerDocument, container, options.scrollToHeading ?? defaultScrollToHeading);
3067
3321
  }
3068
3322
  export {
3069
3323
  hydrateMarkdown,