@scribdown/markdown-renderer 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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);
@@ -1036,319 +785,471 @@ function applyMarkdownMermaidViewerFocalPoint(viewerState, focalPoint) {
1036
785
  viewerState.viewportElement.scrollTop = 0;
1037
786
  const postCanvasRect = viewerState.canvasElement.getBoundingClientRect();
1038
787
  if (postCanvasRect.width <= 0 || postCanvasRect.height <= 0) {
1039
- return;
1040
- }
1041
- const targetCanvasClientLeft = focalPoint.anchorClientX - focalPoint.normalizedX * postCanvasRect.width;
1042
- const targetCanvasClientTop = focalPoint.anchorClientY - focalPoint.normalizedY * postCanvasRect.height;
1043
- viewerState.viewportElement.scrollLeft = postCanvasRect.left - targetCanvasClientLeft;
1044
- viewerState.viewportElement.scrollTop = postCanvasRect.top - targetCanvasClientTop;
1045
- }
1046
- function updateMarkdownMermaidViewerCanvasSize(viewerState) {
1047
- const viewportWidth = Math.max(
1048
- viewerState.viewportElement.clientWidth * IMAGE_VIEWER_FIT_RATIO,
1049
- 1
1050
- );
1051
- const viewportHeight = Math.max(
1052
- viewerState.viewportElement.clientHeight * IMAGE_VIEWER_FIT_RATIO,
1053
- 1
1054
- );
1055
- const naturalWidth = Math.max(viewerState.naturalWidth, 1);
1056
- const naturalHeight = Math.max(viewerState.naturalHeight, 1);
1057
- const fitScale = Math.min(1, viewportWidth / naturalWidth, viewportHeight / naturalHeight);
1058
- const displayWidth = Math.max(1, Math.round(naturalWidth * fitScale * viewerState.zoomValue));
1059
- const displayHeight = Math.max(1, Math.round(naturalHeight * fitScale * viewerState.zoomValue));
1060
- viewerState.canvasElement.style.width = `${displayWidth}px`;
1061
- viewerState.canvasElement.style.height = `${displayHeight}px`;
1062
- const innerSvgElement = viewerState.canvasElement.querySelector("svg");
1063
- if (innerSvgElement) {
1064
- innerSvgElement.removeAttribute("width");
1065
- innerSvgElement.removeAttribute("height");
1066
- innerSvgElement.style.width = "100%";
1067
- innerSvgElement.style.height = "100%";
1068
- innerSvgElement.style.maxWidth = "none";
1069
- innerSvgElement.style.display = "block";
1070
- }
1071
- }
1072
- function defaultScrollToHeading(targetElement) {
1073
- targetElement.scrollIntoView({ behavior: "smooth", block: "start" });
1074
- }
1075
- function hydrateMarkdown(rootElement, options = {}) {
1076
- hydrateMarkdownImages(rootElement);
1077
- hydrateMarkdownVideos(rootElement);
1078
- hydrateMermaidBlocks(rootElement);
1079
- hydrateCodeBlocks(rootElement);
1080
- hydrateToc(rootElement, options.scrollToHeading ?? defaultScrollToHeading);
1081
- }
1082
- function mountMarkdownToolbar(container, options = {}) {
1083
- const ownerDocument = container.ownerDocument;
1084
- if (!ownerDocument) {
1085
- return;
1086
- }
1087
- if (typeof ownerDocument.defaultView?.scrollTo !== "function") {
1088
- return;
1089
- }
1090
- applyContentWidth(ownerDocument, loadContentWidth());
1091
- mountPageToolbar(ownerDocument, container, options.scrollToHeading ?? defaultScrollToHeading);
1092
- }
1093
- function decorateCodeBlock(preElement) {
1094
- if (preElement.parentElement?.classList.contains(CODE_BLOCK_CLASS_NAME)) {
1095
- return;
1096
- }
1097
- if (preElement.dataset[CODE_BLOCK_HYDRATED_DATA_KEY] === "true") {
1098
- return;
1099
- }
1100
- const codeElement = preElement.querySelector(":scope > code");
1101
- if (!codeElement) {
1102
- return;
1103
- }
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);
1131
- }
1132
- copyButtonElement.dataset.scribdownCodeSource = originalCodeText;
1133
- copyButtonElement.addEventListener("click", handleCodeBlockCopyClick);
1134
- }
1135
- function resolveCodeBlockLanguageId(codeElement) {
1136
- const languageClassName = Array.from(codeElement.classList).find(
1137
- (className) => className.startsWith("language-")
1138
- );
1139
- if (!languageClassName) {
1140
- return "";
1141
- }
1142
- return languageClassName.slice("language-".length).toLowerCase();
1143
- }
1144
- function resolveCodeBlockLanguageLabel(languageId) {
1145
- if (!languageId) {
1146
- return CODE_LANGUAGE_DEFAULT_LABEL;
1147
- }
1148
- const mappedLabel = CODE_LANGUAGE_DISPLAY_NAMES[languageId];
1149
- if (mappedLabel) {
1150
- return mappedLabel;
1151
- }
1152
- return languageId.charAt(0).toUpperCase() + languageId.slice(1);
1153
- }
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
- }
1163
- });
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
- }
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;
1184
- }
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);
788
+ return;
1194
789
  }
1195
- return gutterElement;
790
+ const targetCanvasClientLeft = focalPoint.anchorClientX - focalPoint.normalizedX * postCanvasRect.width;
791
+ const targetCanvasClientTop = focalPoint.anchorClientY - focalPoint.normalizedY * postCanvasRect.height;
792
+ viewerState.viewportElement.scrollLeft = postCanvasRect.left - targetCanvasClientLeft;
793
+ viewerState.viewportElement.scrollTop = postCanvasRect.top - targetCanvasClientTop;
1196
794
  }
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}`
795
+ function updateMarkdownMermaidViewerCanvasSize(viewerState) {
796
+ const viewportWidth = Math.max(
797
+ viewerState.viewportElement.clientWidth * VIEWER_FIT_RATIO,
798
+ 1
1208
799
  );
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}`
800
+ const viewportHeight = Math.max(
801
+ viewerState.viewportElement.clientHeight * VIEWER_FIT_RATIO,
802
+ 1
1218
803
  );
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;
804
+ const naturalWidth = Math.max(viewerState.naturalWidth, 1);
805
+ const naturalHeight = Math.max(viewerState.naturalHeight, 1);
806
+ const fitScale = Math.min(1, viewportWidth / naturalWidth, viewportHeight / naturalHeight);
807
+ const displayWidth = Math.max(1, Math.round(naturalWidth * fitScale * viewerState.zoomValue));
808
+ const displayHeight = Math.max(1, Math.round(naturalHeight * fitScale * viewerState.zoomValue));
809
+ viewerState.canvasElement.style.width = `${displayWidth}px`;
810
+ viewerState.canvasElement.style.height = `${displayHeight}px`;
811
+ const innerSvgElement = viewerState.canvasElement.querySelector("svg");
812
+ if (innerSvgElement) {
813
+ innerSvgElement.removeAttribute("width");
814
+ innerSvgElement.removeAttribute("height");
815
+ innerSvgElement.style.width = "100%";
816
+ innerSvgElement.style.height = "100%";
817
+ innerSvgElement.style.maxWidth = "none";
818
+ innerSvgElement.style.display = "block";
819
+ }
1225
820
  }
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);
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;
1232
845
  }
846
+ decorateMermaidBlock(preElement, codeElement);
1233
847
  });
848
+ kickOffPendingMermaidRenders(rootElement);
1234
849
  }
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 {
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;
1241
867
  }
868
+ figureElement.dataset[MERMAID_RENDER_STARTED_DATA_KEY] = "true";
869
+ void renderMermaidIntoCanvas(figureElement, canvasElement, mermaidSource);
870
+ });
871
+ }
872
+ function decorateMermaidBlock(preElement, codeElement) {
873
+ if (preElement.dataset[MERMAID_HYDRATED_DATA_KEY] === "true") {
874
+ return;
1242
875
  }
1243
- if (typeof document === "undefined") {
1244
- return false;
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);
1245
905
  }
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;
906
+ figureElement.dataset[MERMAID_SOURCE_DATA_KEY] = mermaidSource;
907
+ preElement.replaceWith(figureElement);
1257
908
  }
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);
909
+ async function loadMermaid() {
910
+ if (typeof window === "undefined" || typeof document === "undefined") {
911
+ return void 0;
912
+ }
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
+ });
944
+ }
945
+ return mermaidLoaderPromise;
1265
946
  }
1266
- function bindMarkdownImageState(imageElement) {
1267
- updateMarkdownImageState(imageElement);
1268
- if (imageElement.dataset[IMAGE_HYDRATED_DATA_KEY] === "true") {
1269
- return;
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);
970
+ }
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);
1270
1004
  }
1271
- imageElement.dataset[IMAGE_HYDRATED_DATA_KEY] = "true";
1272
- imageElement.addEventListener("load", handleMarkdownImageLoad);
1273
- imageElement.addEventListener("error", handleMarkdownImageError);
1274
- bindMarkdownImageViewer(imageElement);
1005
+ canvasElement.replaceWith(fallbackElement);
1275
1006
  }
1276
- function handleMarkdownImageLoad(event) {
1277
- const imageElement = event.currentTarget;
1278
- updateMarkdownImageState(imageElement);
1007
+ function extractErrorMessage(renderError) {
1008
+ if (renderError instanceof Error) {
1009
+ return renderError.message;
1010
+ }
1011
+ if (typeof renderError === "string") {
1012
+ return renderError;
1013
+ }
1014
+ return "";
1279
1015
  }
1280
- function handleMarkdownImageError(event) {
1281
- const imageElement = event.currentTarget;
1282
- updateMarkdownImageState(imageElement);
1016
+ function cleanupOrphanMermaidNodes(ownerDocument) {
1017
+ const orphanNodes = ownerDocument.querySelectorAll(
1018
+ `body > [id^="${MERMAID_RENDER_ID_PREFIX}"], body > div[id^="d"][id*="mermaid"]`
1019
+ );
1020
+ orphanNodes.forEach((orphanNode) => {
1021
+ orphanNode.remove();
1022
+ });
1283
1023
  }
1284
- function updateMarkdownImageState(imageElement) {
1285
- const frameElement = imageElement.closest(`.${IMAGE_FRAME_CLASS_NAME}`);
1286
- if (!frameElement) {
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) {
1287
1029
  return;
1288
1030
  }
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);
1293
- }
1294
- function bindMarkdownVideoState(videoElement) {
1295
- updateMarkdownVideoState(videoElement);
1296
- if (videoElement.dataset[VIDEO_HYDRATED_DATA_KEY] === "true") {
1031
+ if (figureElement.dataset[MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY] !== "true") {
1297
1032
  return;
1298
1033
  }
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);
1304
- });
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);
1305
1041
  }
1306
- function handleMarkdownVideoLoaded(event) {
1307
- const videoElement = event.currentTarget;
1308
- updateMarkdownVideoState(videoElement);
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"))
1058
+ });
1059
+ }
1060
+ return codeHighlighterPromise;
1309
1061
  }
1310
- function handleMarkdownVideoError(event) {
1311
- const videoElement = event.currentTarget;
1312
- markMarkdownVideoFailed(videoElement);
1062
+ async function highlightMarkdownCodeBlocks(html) {
1063
+ if (!CODE_BLOCK_HIGHLIGHT_PATTERN.test(html)) {
1064
+ return html;
1065
+ }
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;
1313
1085
  }
1314
- function handleMarkdownVideoSourceError(event) {
1315
- const sourceElement = event.currentTarget;
1316
- const videoElement = sourceElement.closest(
1317
- `video.${VIDEO_ELEMENT_CLASS_NAME}`
1318
- );
1319
- if (!videoElement) {
1320
- return;
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;
1321
1091
  }
1322
- if (videoElement.networkState !== videoElement.NETWORK_NO_SOURCE) {
1323
- return;
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;
1324
1104
  }
1325
- markMarkdownVideoFailed(videoElement);
1326
1105
  }
1327
- function updateMarkdownVideoState(videoElement) {
1328
- const frameElement = videoElement.closest(
1329
- `.${VIDEO_FRAME_CLASS_NAME}`
1106
+ async function ensureHighlighterLanguage(highlighter, normalizedLanguage) {
1107
+ if (normalizedLanguage.length === 0) {
1108
+ return "text";
1109
+ }
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
+ }
1330
1156
  );
1331
- if (!frameElement) {
1157
+ }
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
+ }
1167
+ }
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
+ function isBlockContainerNode(node) {
1176
+ return node.type === "blockquote" || node.type === "list" || node.type === "listItem";
1177
+ }
1178
+ function remarkSourceLine() {
1179
+ return (tree) => {
1180
+ const blockNodes = tree.children;
1181
+ blockNodes.forEach((blockNode) => {
1182
+ annotateSourceLine(blockNode);
1183
+ annotateNestedBlockSourceLines(blockNode);
1184
+ });
1185
+ };
1186
+ }
1187
+ function annotateSourceLine(node) {
1188
+ const startLine = node.position?.start.line;
1189
+ if (typeof startLine !== "number") {
1332
1190
  return;
1333
1191
  }
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
- );
1192
+ const nodeData = node.data ?? {};
1193
+ const hProperties = nodeData.hProperties ?? {};
1194
+ hProperties[SOURCE_LINE_HAST_PROPERTY] = startLine;
1195
+ nodeData.hProperties = hProperties;
1196
+ node.data = nodeData;
1341
1197
  }
1342
- function markMarkdownVideoFailed(videoElement) {
1343
- const frameElement = videoElement.closest(
1344
- `.${VIDEO_FRAME_CLASS_NAME}`
1345
- );
1346
- if (!frameElement) {
1198
+ function annotateNestedBlockSourceLines(node) {
1199
+ if (!isBlockContainerNode(node)) {
1347
1200
  return;
1348
1201
  }
1349
- frameElement.classList.add(VIDEO_FRAME_FAILED_CLASS_NAME);
1350
- frameElement.classList.remove(VIDEO_FRAME_LOADED_CLASS_NAME);
1202
+ const childNodes = node.children;
1203
+ childNodes.forEach((childNode) => {
1204
+ annotateSourceLine(childNode);
1205
+ annotateNestedBlockSourceLines(childNode);
1206
+ });
1351
1207
  }
1208
+
1209
+ // src/media/images.ts
1210
+ import {
1211
+ IMAGE_CAPTION_CLASS_NAME,
1212
+ IMAGE_ELEMENT_CLASS_NAME,
1213
+ IMAGE_FALLBACK_CLASS_NAME,
1214
+ IMAGE_FALLBACK_ICON_CLASS_NAME,
1215
+ IMAGE_FALLBACK_SOURCE_CLASS_NAME,
1216
+ IMAGE_FALLBACK_TEXT_CLASS_NAME,
1217
+ IMAGE_FIGURE_CLASS_NAME,
1218
+ IMAGE_FRAME_CLASS_NAME,
1219
+ IMAGE_FRAME_FAILED_CLASS_NAME,
1220
+ IMAGE_FRAME_LOADED_CLASS_NAME
1221
+ } from "@scribdown/shared";
1222
+ import { SKIP, visit } from "unist-util-visit";
1223
+
1224
+ // src/core/ast.ts
1225
+ import { toString as mdastToString } from "mdast-util-to-string";
1226
+ function extractNodeText(node) {
1227
+ return mdastToString(node, { includeHtml: false });
1228
+ }
1229
+ function isImageNode(node) {
1230
+ return node.type === "image" || node.type === "imageReference";
1231
+ }
1232
+
1233
+ // src/media/image-viewer.ts
1234
+ import {
1235
+ IMAGE_VIEWER_BUTTON_CLASS_NAME,
1236
+ IMAGE_VIEWER_CAPTION_CLASS_NAME,
1237
+ IMAGE_VIEWER_CAPTION_GROUP_CLASS_NAME,
1238
+ IMAGE_VIEWER_CHROME_CLASS_NAME,
1239
+ IMAGE_VIEWER_CLOSE_BUTTON_CLASS_NAME,
1240
+ IMAGE_VIEWER_CONTROLS_CLASS_NAME,
1241
+ IMAGE_VIEWER_DIALOG_CLASS_NAME,
1242
+ IMAGE_VIEWER_DRAGGING_CLASS_NAME,
1243
+ IMAGE_VIEWER_HINT_CLASS_NAME,
1244
+ IMAGE_VIEWER_IMAGE_CLASS_NAME,
1245
+ IMAGE_VIEWER_VIEWPORT_CLASS_NAME,
1246
+ IMAGE_VIEWER_ZOOMED_CLASS_NAME,
1247
+ IMAGE_VIEWER_ZOOM_VALUE_CLASS_NAME
1248
+ } from "@scribdown/shared";
1249
+ 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";
1250
+ var IMAGE_VIEWER_FALLBACK_LABEL = "\u67E5\u770B\u5168\u56FE";
1251
+ var imageViewerStateByDocument = /* @__PURE__ */ new WeakMap();
1252
+ var imageViewerStateByDialogElement = /* @__PURE__ */ new WeakMap();
1352
1253
  function bindMarkdownImageViewer(imageElement) {
1353
1254
  const linkElement = imageElement.closest("a[href]");
1354
1255
  if (linkElement) {
@@ -1402,7 +1303,7 @@ function openMarkdownImageViewer(sourceImageElement) {
1402
1303
  );
1403
1304
  viewerState.viewportElement.scrollLeft = 0;
1404
1305
  viewerState.viewportElement.scrollTop = 0;
1405
- viewerState.zoomValue = IMAGE_VIEWER_DEFAULT_ZOOM;
1306
+ viewerState.zoomValue = VIEWER_DEFAULT_ZOOM;
1406
1307
  showMarkdownImageViewerDialog(viewerState);
1407
1308
  requestMarkdownImageViewerLayout(viewerState);
1408
1309
  }
@@ -1424,21 +1325,21 @@ function createMarkdownImageViewerState(ownerDocument) {
1424
1325
  const controlsElement = ownerDocument.createElement("div");
1425
1326
  const zoomOutButtonElement = createMarkdownImageViewerButton(ownerDocument, {
1426
1327
  ariaLabel: "\u7F29\u5C0F\u56FE\u7247",
1427
- text: IMAGE_VIEWER_ZOOM_OUT_TEXT
1328
+ text: VIEWER_ZOOM_OUT_TEXT
1428
1329
  });
1429
1330
  const zoomValueElement = ownerDocument.createElement("span");
1430
1331
  const zoomInButtonElement = createMarkdownImageViewerButton(ownerDocument, {
1431
1332
  ariaLabel: "\u653E\u5927\u56FE\u7247",
1432
- text: IMAGE_VIEWER_ZOOM_IN_TEXT
1333
+ text: VIEWER_ZOOM_IN_TEXT
1433
1334
  });
1434
1335
  const resetButtonElement = createMarkdownImageViewerButton(ownerDocument, {
1435
1336
  ariaLabel: "\u91CD\u7F6E\u7F29\u653E",
1436
- text: IMAGE_VIEWER_RESET_TEXT
1337
+ text: VIEWER_RESET_TEXT
1437
1338
  });
1438
1339
  const closeButtonElement = createMarkdownImageViewerButton(ownerDocument, {
1439
1340
  ariaLabel: "\u5173\u95ED\u5168\u56FE\u67E5\u770B",
1440
1341
  className: IMAGE_VIEWER_CLOSE_BUTTON_CLASS_NAME,
1441
- text: IMAGE_VIEWER_CLOSE_TEXT
1342
+ text: VIEWER_CLOSE_TEXT
1442
1343
  });
1443
1344
  const viewportElement = ownerDocument.createElement("div");
1444
1345
  const imageElement = ownerDocument.createElement("img");
@@ -1459,7 +1360,7 @@ function createMarkdownImageViewerState(ownerDocument) {
1459
1360
  viewportElement,
1460
1361
  zoomInButtonElement,
1461
1362
  zoomOutButtonElement,
1462
- zoomValue: IMAGE_VIEWER_DEFAULT_ZOOM,
1363
+ zoomValue: VIEWER_DEFAULT_ZOOM,
1463
1364
  zoomValueElement
1464
1365
  };
1465
1366
  const ownerWindow = ownerDocument.defaultView;
@@ -1506,7 +1407,7 @@ function createMarkdownImageViewerState(ownerDocument) {
1506
1407
  if (ownerWindow) {
1507
1408
  ownerWindow.addEventListener("resize", handleMarkdownImageViewerWindowResize);
1508
1409
  }
1509
- updateMarkdownImageViewerZoom(viewerState, IMAGE_VIEWER_DEFAULT_ZOOM);
1410
+ updateMarkdownImageViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);
1510
1411
  return viewerState;
1511
1412
  }
1512
1413
  function createMarkdownImageViewerButton(ownerDocument, options) {
@@ -1564,17 +1465,17 @@ function handleMarkdownImageViewerKeyDown(event) {
1564
1465
  }
1565
1466
  if (event.key === "+" || event.key === "=") {
1566
1467
  event.preventDefault();
1567
- updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + IMAGE_VIEWER_ZOOM_STEP);
1468
+ updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + VIEWER_ZOOM_STEP);
1568
1469
  return;
1569
1470
  }
1570
1471
  if (event.key === "-") {
1571
1472
  event.preventDefault();
1572
- updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue - IMAGE_VIEWER_ZOOM_STEP);
1473
+ updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue - VIEWER_ZOOM_STEP);
1573
1474
  return;
1574
1475
  }
1575
1476
  if (event.key === "0") {
1576
1477
  event.preventDefault();
1577
- updateMarkdownImageViewerZoom(viewerState, IMAGE_VIEWER_DEFAULT_ZOOM);
1478
+ updateMarkdownImageViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);
1578
1479
  return;
1579
1480
  }
1580
1481
  if (event.key === "Escape") {
@@ -1590,10 +1491,10 @@ function handleMarkdownImageViewerWheel(event) {
1590
1491
  if (!viewerState) {
1591
1492
  return;
1592
1493
  }
1593
- const rawDelta = -event.deltaY * IMAGE_VIEWER_WHEEL_ZOOM_FACTOR;
1494
+ const rawDelta = -event.deltaY * VIEWER_WHEEL_ZOOM_FACTOR;
1594
1495
  const zoomDelta = Math.max(
1595
- -IMAGE_VIEWER_WHEEL_ZOOM_MAX_DELTA,
1596
- Math.min(IMAGE_VIEWER_WHEEL_ZOOM_MAX_DELTA, rawDelta)
1496
+ -VIEWER_WHEEL_ZOOM_MAX_DELTA,
1497
+ Math.min(VIEWER_WHEEL_ZOOM_MAX_DELTA, rawDelta)
1597
1498
  );
1598
1499
  event.preventDefault();
1599
1500
  updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + zoomDelta, {
@@ -1652,177 +1553,552 @@ function handleMarkdownImageViewerPointerUp(event) {
1652
1553
  function handleMarkdownImageViewerImageDragStart(event) {
1653
1554
  event.preventDefault();
1654
1555
  }
1655
- function handleMarkdownImageViewerImageLoad(event) {
1656
- const imageElement = event.currentTarget;
1657
- const dialogElement = imageElement.closest(
1658
- `.${IMAGE_VIEWER_DIALOG_CLASS_NAME}`
1556
+ function handleMarkdownImageViewerImageLoad(event) {
1557
+ const imageElement = event.currentTarget;
1558
+ const dialogElement = imageElement.closest(
1559
+ `.${IMAGE_VIEWER_DIALOG_CLASS_NAME}`
1560
+ );
1561
+ if (!dialogElement) {
1562
+ return;
1563
+ }
1564
+ const viewerState = imageViewerStateByDialogElement.get(dialogElement);
1565
+ if (!viewerState) {
1566
+ return;
1567
+ }
1568
+ viewerState.naturalWidth = imageElement.naturalWidth || viewerState.naturalWidth;
1569
+ viewerState.naturalHeight = imageElement.naturalHeight || viewerState.naturalHeight;
1570
+ updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);
1571
+ }
1572
+ function handleMarkdownImageViewerWindowResize(event) {
1573
+ const ownerWindow = event.currentTarget;
1574
+ const viewerState = imageViewerStateByDocument.get(ownerWindow.document);
1575
+ if (!viewerState || !isMarkdownImageViewerOpen(viewerState)) {
1576
+ return;
1577
+ }
1578
+ updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);
1579
+ }
1580
+ function handleMarkdownImageViewerZoomOutClick(event) {
1581
+ const viewerState = getMarkdownImageViewerStateFromEvent(event);
1582
+ if (!viewerState) {
1583
+ return;
1584
+ }
1585
+ updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue - VIEWER_ZOOM_STEP);
1586
+ }
1587
+ function handleMarkdownImageViewerZoomInClick(event) {
1588
+ const viewerState = getMarkdownImageViewerStateFromEvent(event);
1589
+ if (!viewerState) {
1590
+ return;
1591
+ }
1592
+ updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + VIEWER_ZOOM_STEP);
1593
+ }
1594
+ function handleMarkdownImageViewerResetClick(event) {
1595
+ const viewerState = getMarkdownImageViewerStateFromEvent(event);
1596
+ if (!viewerState) {
1597
+ return;
1598
+ }
1599
+ updateMarkdownImageViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);
1600
+ }
1601
+ function handleMarkdownImageViewerCloseClick(event) {
1602
+ const viewerState = getMarkdownImageViewerStateFromEvent(event);
1603
+ if (!viewerState) {
1604
+ return;
1605
+ }
1606
+ closeMarkdownImageViewer(viewerState);
1607
+ }
1608
+ function getMarkdownImageViewerStateFromEvent(event) {
1609
+ const currentTargetElement = event.currentTarget;
1610
+ if (!currentTargetElement) {
1611
+ return void 0;
1612
+ }
1613
+ const dialogElement = currentTargetElement.closest(
1614
+ `.${IMAGE_VIEWER_DIALOG_CLASS_NAME}`
1615
+ );
1616
+ if (!dialogElement) {
1617
+ return void 0;
1618
+ }
1619
+ return imageViewerStateByDialogElement.get(dialogElement);
1620
+ }
1621
+ function updateMarkdownImageViewerZoom(viewerState, nextZoom, zoomAnchor) {
1622
+ const normalizedZoom = clampMarkdownViewerZoom(nextZoom);
1623
+ const focalPoint = isMarkdownImageViewerOpen(viewerState) ? captureMarkdownImageViewerFocalPoint(viewerState, zoomAnchor) : void 0;
1624
+ viewerState.zoomValue = normalizedZoom;
1625
+ viewerState.dialogElement.classList.toggle(
1626
+ IMAGE_VIEWER_ZOOMED_CLASS_NAME,
1627
+ normalizedZoom > VIEWER_DEFAULT_ZOOM
1628
+ );
1629
+ viewerState.zoomValueElement.textContent = `${Math.round(normalizedZoom * 100)}%`;
1630
+ viewerState.zoomOutButtonElement.disabled = normalizedZoom <= VIEWER_MIN_ZOOM;
1631
+ viewerState.zoomInButtonElement.disabled = normalizedZoom >= VIEWER_MAX_ZOOM;
1632
+ viewerState.resetButtonElement.disabled = normalizedZoom === VIEWER_DEFAULT_ZOOM;
1633
+ updateMarkdownImageViewerImageSize(viewerState);
1634
+ if (focalPoint) {
1635
+ applyMarkdownImageViewerFocalPoint(viewerState, focalPoint);
1636
+ }
1637
+ }
1638
+ function captureMarkdownImageViewerFocalPoint(viewerState, zoomAnchor) {
1639
+ const preImageRect = viewerState.imageElement.getBoundingClientRect();
1640
+ if (preImageRect.width <= 0 || preImageRect.height <= 0) {
1641
+ return void 0;
1642
+ }
1643
+ const viewportRect = viewerState.viewportElement.getBoundingClientRect();
1644
+ const anchorClientX = zoomAnchor?.x ?? viewportRect.left + viewportRect.width / 2;
1645
+ const anchorClientY = zoomAnchor?.y ?? viewportRect.top + viewportRect.height / 2;
1646
+ const normalizedX = (anchorClientX - preImageRect.left) / preImageRect.width;
1647
+ const normalizedY = (anchorClientY - preImageRect.top) / preImageRect.height;
1648
+ return {
1649
+ anchorClientX,
1650
+ anchorClientY,
1651
+ normalizedX,
1652
+ normalizedY
1653
+ };
1654
+ }
1655
+ function applyMarkdownImageViewerFocalPoint(viewerState, focalPoint) {
1656
+ viewerState.viewportElement.scrollLeft = 0;
1657
+ viewerState.viewportElement.scrollTop = 0;
1658
+ const postImageRect = viewerState.imageElement.getBoundingClientRect();
1659
+ if (postImageRect.width <= 0 || postImageRect.height <= 0) {
1660
+ return;
1661
+ }
1662
+ const targetImageClientLeft = focalPoint.anchorClientX - focalPoint.normalizedX * postImageRect.width;
1663
+ const targetImageClientTop = focalPoint.anchorClientY - focalPoint.normalizedY * postImageRect.height;
1664
+ viewerState.viewportElement.scrollLeft = postImageRect.left - targetImageClientLeft;
1665
+ viewerState.viewportElement.scrollTop = postImageRect.top - targetImageClientTop;
1666
+ }
1667
+ function updateMarkdownImageViewerImageSize(viewerState) {
1668
+ const viewportWidth = Math.max(
1669
+ viewerState.viewportElement.clientWidth * VIEWER_FIT_RATIO,
1670
+ 1
1671
+ );
1672
+ const viewportHeight = Math.max(
1673
+ viewerState.viewportElement.clientHeight * VIEWER_FIT_RATIO,
1674
+ 1
1675
+ );
1676
+ const naturalWidth = Math.max(viewerState.naturalWidth, 1);
1677
+ const naturalHeight = Math.max(viewerState.naturalHeight, 1);
1678
+ const fitScale = Math.min(1, viewportWidth / naturalWidth, viewportHeight / naturalHeight);
1679
+ const displayWidth = Math.max(1, Math.round(naturalWidth * fitScale * viewerState.zoomValue));
1680
+ const displayHeight = Math.max(1, Math.round(naturalHeight * fitScale * viewerState.zoomValue));
1681
+ viewerState.imageElement.style.width = `${displayWidth}px`;
1682
+ viewerState.imageElement.style.height = `${displayHeight}px`;
1683
+ }
1684
+ function closeMarkdownImageViewer(viewerState) {
1685
+ if (typeof viewerState.dialogElement.close === "function" && viewerState.dialogElement.open) {
1686
+ viewerState.dialogElement.close();
1687
+ return;
1688
+ }
1689
+ viewerState.dialogElement.removeAttribute("open");
1690
+ resetMarkdownImageViewerAfterClose(viewerState);
1691
+ }
1692
+ function resetMarkdownImageViewerAfterClose(viewerState) {
1693
+ viewerState.dialogElement.classList.remove(IMAGE_VIEWER_ZOOMED_CLASS_NAME);
1694
+ viewerState.dialogElement.classList.remove(IMAGE_VIEWER_DRAGGING_CLASS_NAME);
1695
+ viewerState.isDragging = false;
1696
+ viewerState.imageElement.removeAttribute("src");
1697
+ viewerState.imageElement.removeAttribute("alt");
1698
+ viewerState.imageElement.removeAttribute("style");
1699
+ viewerState.captionElement.textContent = "";
1700
+ viewerState.viewportElement.scrollLeft = 0;
1701
+ viewerState.viewportElement.scrollTop = 0;
1702
+ viewerState.zoomValue = VIEWER_DEFAULT_ZOOM;
1703
+ if (viewerState.sourceImageElement?.isConnected) {
1704
+ viewerState.sourceImageElement.focus();
1705
+ }
1706
+ }
1707
+ function isMarkdownImageViewerOpen(viewerState) {
1708
+ return viewerState.dialogElement.open || viewerState.dialogElement.hasAttribute("open");
1709
+ }
1710
+ function getMarkdownImageSource(imageElement) {
1711
+ return imageElement.currentSrc || imageElement.getAttribute("src") || imageElement.src || "";
1712
+ }
1713
+ function createMarkdownImageViewerAriaLabel(imageElement) {
1714
+ const imageAltText = imageElement.alt.trim();
1715
+ if (imageAltText.length === 0) {
1716
+ return IMAGE_VIEWER_FALLBACK_LABEL;
1717
+ }
1718
+ return `${IMAGE_VIEWER_FALLBACK_LABEL}\uFF1A${imageAltText}`;
1719
+ }
1720
+ function createMarkdownImageViewerCaption(imageElement, imageSource) {
1721
+ const imageAltText = imageElement.alt.trim();
1722
+ const imageTitleText = imageElement.title.trim();
1723
+ return imageAltText || imageTitleText || imageSource;
1724
+ }
1725
+
1726
+ // src/media/images.ts
1727
+ var IMAGE_HYDRATED_DATA_KEY = "scribdownImageHydrated";
1728
+ function remarkImageFigures() {
1729
+ return (tree) => {
1730
+ visit(tree, "paragraph", (node, index, parent) => {
1731
+ if (parent === void 0 || index === void 0) {
1732
+ return;
1733
+ }
1734
+ const imageFigureNode = createImageFigureNode(node);
1735
+ if (imageFigureNode) {
1736
+ parent.children[index] = imageFigureNode;
1737
+ return SKIP;
1738
+ }
1739
+ });
1740
+ visit(tree, isImageNode, (node) => {
1741
+ decorateImageNode(node);
1742
+ });
1743
+ };
1744
+ }
1745
+ function createImageFigureNode(node) {
1746
+ if (node.children.length !== 1) {
1747
+ return void 0;
1748
+ }
1749
+ const onlyChild = node.children[0];
1750
+ if (!isImageNode(onlyChild)) {
1751
+ return void 0;
1752
+ }
1753
+ decorateImageNode(onlyChild);
1754
+ return {
1755
+ type: "imageFigure",
1756
+ // 关键步骤:保留原段落源码位置,使 remarkSourceLine 能为 figure 标注 data-source-line。
1757
+ position: node.position,
1758
+ data: {
1759
+ hName: "figure",
1760
+ hProperties: {
1761
+ className: [IMAGE_FIGURE_CLASS_NAME]
1762
+ }
1763
+ },
1764
+ children: [createImageFrameNode(onlyChild), ...createImageCaptionNodes(onlyChild)]
1765
+ };
1766
+ }
1767
+ function decorateImageNode(imageNode) {
1768
+ imageNode.data = {
1769
+ ...imageNode.data,
1770
+ hProperties: {
1771
+ ...imageNode.data?.hProperties,
1772
+ className: [IMAGE_ELEMENT_CLASS_NAME]
1773
+ }
1774
+ };
1775
+ }
1776
+ function createImageFrameNode(imageNode) {
1777
+ return {
1778
+ type: "imageFrame",
1779
+ data: {
1780
+ hName: "span",
1781
+ hProperties: {
1782
+ className: [IMAGE_FRAME_CLASS_NAME]
1783
+ }
1784
+ },
1785
+ children: [imageNode, createImageFallbackNode(imageNode)]
1786
+ };
1787
+ }
1788
+ function createImageCaptionNodes(imageNode) {
1789
+ const imageTitle = imageNode.type === "image" && typeof imageNode.title === "string" ? imageNode.title.trim() : "";
1790
+ if (imageTitle.length === 0) {
1791
+ return [];
1792
+ }
1793
+ return [
1794
+ {
1795
+ type: "imageCaption",
1796
+ data: {
1797
+ hName: "figcaption",
1798
+ hProperties: {
1799
+ className: [IMAGE_CAPTION_CLASS_NAME]
1800
+ }
1801
+ },
1802
+ children: [{ type: "text", value: imageTitle }]
1803
+ }
1804
+ ];
1805
+ }
1806
+ function createImageFallbackNode(imageNode) {
1807
+ const fallbackText = imageNode.alt?.trim() || "\u56FE\u7247\u52A0\u8F7D\u5931\u8D25";
1808
+ const fallbackSource = imageNode.type === "image" ? imageNode.url : imageNode.identifier;
1809
+ return {
1810
+ type: "imageFallback",
1811
+ data: {
1812
+ hName: "span",
1813
+ hProperties: {
1814
+ className: [IMAGE_FALLBACK_CLASS_NAME]
1815
+ }
1816
+ },
1817
+ children: [
1818
+ {
1819
+ type: "imageFallbackIcon",
1820
+ data: {
1821
+ hName: "span",
1822
+ hProperties: {
1823
+ className: [IMAGE_FALLBACK_ICON_CLASS_NAME]
1824
+ }
1825
+ }
1826
+ },
1827
+ {
1828
+ type: "imageFallbackText",
1829
+ data: {
1830
+ hName: "span",
1831
+ hProperties: {
1832
+ className: [IMAGE_FALLBACK_TEXT_CLASS_NAME]
1833
+ }
1834
+ },
1835
+ children: [{ type: "text", value: fallbackText }]
1836
+ },
1837
+ {
1838
+ type: "imageFallbackSource",
1839
+ data: {
1840
+ hName: "span",
1841
+ hProperties: {
1842
+ className: [IMAGE_FALLBACK_SOURCE_CLASS_NAME]
1843
+ }
1844
+ },
1845
+ children: [{ type: "text", value: fallbackSource }]
1846
+ }
1847
+ ]
1848
+ };
1849
+ }
1850
+ function hydrateMarkdownImages(rootElement) {
1851
+ const imageElements = rootElement.querySelectorAll(
1852
+ `img.${IMAGE_ELEMENT_CLASS_NAME}`
1659
1853
  );
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);
1854
+ imageElements.forEach(bindMarkdownImageState);
1670
1855
  }
1671
- function handleMarkdownImageViewerWindowResize(event) {
1672
- const ownerWindow = event.currentTarget;
1673
- const viewerState = imageViewerStateByDocument.get(ownerWindow.document);
1674
- if (!viewerState || !isMarkdownImageViewerOpen(viewerState)) {
1856
+ function bindMarkdownImageState(imageElement) {
1857
+ updateMarkdownImageState(imageElement);
1858
+ if (imageElement.dataset[IMAGE_HYDRATED_DATA_KEY] === "true") {
1675
1859
  return;
1676
1860
  }
1677
- updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);
1861
+ imageElement.dataset[IMAGE_HYDRATED_DATA_KEY] = "true";
1862
+ imageElement.addEventListener("load", handleMarkdownImageLoad);
1863
+ imageElement.addEventListener("error", handleMarkdownImageError);
1864
+ bindMarkdownImageViewer(imageElement);
1678
1865
  }
1679
- function handleMarkdownImageViewerZoomOutClick(event) {
1680
- const viewerState = getMarkdownImageViewerStateFromEvent(event);
1681
- if (!viewerState) {
1682
- return;
1683
- }
1684
- updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue - IMAGE_VIEWER_ZOOM_STEP);
1866
+ function handleMarkdownImageLoad(event) {
1867
+ const imageElement = event.currentTarget;
1868
+ updateMarkdownImageState(imageElement);
1685
1869
  }
1686
- function handleMarkdownImageViewerZoomInClick(event) {
1687
- const viewerState = getMarkdownImageViewerStateFromEvent(event);
1688
- if (!viewerState) {
1689
- return;
1690
- }
1691
- updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + IMAGE_VIEWER_ZOOM_STEP);
1870
+ function handleMarkdownImageError(event) {
1871
+ const imageElement = event.currentTarget;
1872
+ updateMarkdownImageState(imageElement);
1692
1873
  }
1693
- function handleMarkdownImageViewerResetClick(event) {
1694
- const viewerState = getMarkdownImageViewerStateFromEvent(event);
1695
- if (!viewerState) {
1874
+ function updateMarkdownImageState(imageElement) {
1875
+ const frameElement = imageElement.closest(`.${IMAGE_FRAME_CLASS_NAME}`);
1876
+ if (!frameElement) {
1696
1877
  return;
1697
1878
  }
1698
- updateMarkdownImageViewerZoom(viewerState, IMAGE_VIEWER_DEFAULT_ZOOM);
1879
+ const isFailed = imageElement.complete && imageElement.naturalWidth === 0;
1880
+ const isLoaded = imageElement.complete && imageElement.naturalWidth > 0;
1881
+ frameElement.classList.toggle(IMAGE_FRAME_FAILED_CLASS_NAME, isFailed);
1882
+ frameElement.classList.toggle(IMAGE_FRAME_LOADED_CLASS_NAME, isLoaded);
1699
1883
  }
1700
- function handleMarkdownImageViewerCloseClick(event) {
1701
- const viewerState = getMarkdownImageViewerStateFromEvent(event);
1702
- if (!viewerState) {
1703
- return;
1704
- }
1705
- closeMarkdownImageViewer(viewerState);
1884
+
1885
+ // src/media/videos.ts
1886
+ import {
1887
+ VIDEO_ELEMENT_CLASS_NAME,
1888
+ VIDEO_FALLBACK_CLASS_NAME,
1889
+ VIDEO_FALLBACK_ICON_CLASS_NAME,
1890
+ VIDEO_FALLBACK_SOURCE_CLASS_NAME,
1891
+ VIDEO_FALLBACK_TEXT_CLASS_NAME,
1892
+ VIDEO_FIGURE_CLASS_NAME,
1893
+ VIDEO_FRAME_CLASS_NAME,
1894
+ VIDEO_FRAME_FAILED_CLASS_NAME,
1895
+ VIDEO_FRAME_LOADED_CLASS_NAME
1896
+ } from "@scribdown/shared";
1897
+ import { classnames } from "hast-util-classnames";
1898
+ import { SKIP as SKIP2, visit as visit2 } from "unist-util-visit";
1899
+ var VIDEO_HYDRATED_DATA_KEY = "scribdownVideoHydrated";
1900
+ var VIDEO_FALLBACK_DEFAULT_TEXT = "\u89C6\u9891\u52A0\u8F7D\u5931\u8D25";
1901
+ function rehypeVideoFigures() {
1902
+ return (tree) => {
1903
+ visit2(tree, "element", (node, index, parent) => {
1904
+ if (parent === void 0 || index === void 0) {
1905
+ return;
1906
+ }
1907
+ if (isHastVideoElement(node)) {
1908
+ parent.children[index] = createVideoFigureHast(node);
1909
+ return SKIP2;
1910
+ }
1911
+ const standaloneVideo = extractStandaloneParagraphVideo(node);
1912
+ if (standaloneVideo) {
1913
+ parent.children[index] = createVideoFigureHast(standaloneVideo);
1914
+ return SKIP2;
1915
+ }
1916
+ });
1917
+ };
1706
1918
  }
1707
- function getMarkdownImageViewerStateFromEvent(event) {
1708
- const currentTargetElement = event.currentTarget;
1709
- if (!currentTargetElement) {
1919
+ function isHastVideoElement(node) {
1920
+ return node.type === "element" && node.tagName === "video";
1921
+ }
1922
+ function extractStandaloneParagraphVideo(node) {
1923
+ if (node.tagName !== "p") {
1710
1924
  return void 0;
1711
1925
  }
1712
- const dialogElement = currentTargetElement.closest(
1713
- `.${IMAGE_VIEWER_DIALOG_CLASS_NAME}`
1714
- );
1715
- if (!dialogElement) {
1716
- return void 0;
1926
+ const significantChildren = node.children.filter((childNode) => {
1927
+ if (childNode.type === "text") {
1928
+ return childNode.value.trim().length > 0;
1929
+ }
1930
+ return true;
1931
+ });
1932
+ if (significantChildren.length === 1 && isHastVideoElement(significantChildren[0])) {
1933
+ return significantChildren[0];
1717
1934
  }
1718
- return imageViewerStateByDialogElement.get(dialogElement);
1935
+ return void 0;
1719
1936
  }
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);
1735
- }
1937
+ function createVideoFigureHast(videoNode) {
1938
+ decorateHastVideoElement(videoNode);
1939
+ const sourceUrl = readHastVideoSourceUrl(videoNode);
1940
+ return {
1941
+ type: "element",
1942
+ tagName: "figure",
1943
+ properties: { className: [VIDEO_FIGURE_CLASS_NAME] },
1944
+ children: [
1945
+ {
1946
+ type: "element",
1947
+ tagName: "span",
1948
+ properties: { className: [VIDEO_FRAME_CLASS_NAME] },
1949
+ children: [videoNode, createVideoFallbackHast(sourceUrl)]
1950
+ }
1951
+ ]
1952
+ };
1736
1953
  }
1737
- function captureMarkdownImageViewerFocalPoint(viewerState, zoomAnchor) {
1738
- const preImageRect = viewerState.imageElement.getBoundingClientRect();
1739
- if (preImageRect.width <= 0 || preImageRect.height <= 0) {
1740
- return void 0;
1954
+ function decorateHastVideoElement(videoNode) {
1955
+ classnames(videoNode, VIDEO_ELEMENT_CLASS_NAME);
1956
+ }
1957
+ function readHastVideoSourceUrl(videoNode) {
1958
+ if (typeof videoNode.properties.src === "string") {
1959
+ return videoNode.properties.src;
1741
1960
  }
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;
1961
+ for (const childNode of videoNode.children) {
1962
+ if (childNode.type === "element" && childNode.tagName === "source" && typeof childNode.properties.src === "string") {
1963
+ return childNode.properties.src;
1964
+ }
1965
+ }
1966
+ return "";
1967
+ }
1968
+ function createVideoFallbackHast(sourceUrl) {
1747
1969
  return {
1748
- anchorClientX,
1749
- anchorClientY,
1750
- normalizedX,
1751
- normalizedY
1970
+ type: "element",
1971
+ tagName: "span",
1972
+ properties: { className: [VIDEO_FALLBACK_CLASS_NAME] },
1973
+ children: [
1974
+ {
1975
+ type: "element",
1976
+ tagName: "span",
1977
+ properties: { className: [VIDEO_FALLBACK_ICON_CLASS_NAME] },
1978
+ children: []
1979
+ },
1980
+ {
1981
+ type: "element",
1982
+ tagName: "span",
1983
+ properties: { className: [VIDEO_FALLBACK_TEXT_CLASS_NAME] },
1984
+ children: [{ type: "text", value: VIDEO_FALLBACK_DEFAULT_TEXT }]
1985
+ },
1986
+ {
1987
+ type: "element",
1988
+ tagName: "span",
1989
+ properties: { className: [VIDEO_FALLBACK_SOURCE_CLASS_NAME] },
1990
+ children: [{ type: "text", value: sourceUrl }]
1991
+ }
1992
+ ]
1752
1993
  };
1753
1994
  }
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) {
1995
+ function hydrateMarkdownVideos(rootElement) {
1996
+ const videoElements = rootElement.querySelectorAll(
1997
+ `video.${VIDEO_ELEMENT_CLASS_NAME}`
1998
+ );
1999
+ videoElements.forEach(bindMarkdownVideoState);
2000
+ }
2001
+ function bindMarkdownVideoState(videoElement) {
2002
+ updateMarkdownVideoState(videoElement);
2003
+ if (videoElement.dataset[VIDEO_HYDRATED_DATA_KEY] === "true") {
1759
2004
  return;
1760
2005
  }
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;
2006
+ videoElement.dataset[VIDEO_HYDRATED_DATA_KEY] = "true";
2007
+ videoElement.addEventListener("loadeddata", handleMarkdownVideoLoaded);
2008
+ videoElement.addEventListener("error", handleMarkdownVideoError);
2009
+ videoElement.querySelectorAll("source").forEach((sourceElement) => {
2010
+ sourceElement.addEventListener("error", handleMarkdownVideoSourceError);
2011
+ });
1765
2012
  }
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
1774
- );
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`;
2013
+ function handleMarkdownVideoLoaded(event) {
2014
+ const videoElement = event.currentTarget;
2015
+ updateMarkdownVideoState(videoElement);
1782
2016
  }
1783
- function clampMarkdownImageViewerZoom(zoomValue) {
1784
- return Math.min(IMAGE_VIEWER_MAX_ZOOM, Math.max(IMAGE_VIEWER_MIN_ZOOM, zoomValue));
2017
+ function handleMarkdownVideoError(event) {
2018
+ const videoElement = event.currentTarget;
2019
+ markMarkdownVideoFailed(videoElement);
1785
2020
  }
1786
- function closeMarkdownImageViewer(viewerState) {
1787
- if (typeof viewerState.dialogElement.close === "function" && viewerState.dialogElement.open) {
1788
- viewerState.dialogElement.close();
2021
+ function handleMarkdownVideoSourceError(event) {
2022
+ const sourceElement = event.currentTarget;
2023
+ const videoElement = sourceElement.closest(
2024
+ `video.${VIDEO_ELEMENT_CLASS_NAME}`
2025
+ );
2026
+ if (!videoElement) {
1789
2027
  return;
1790
2028
  }
1791
- viewerState.dialogElement.removeAttribute("open");
1792
- resetMarkdownImageViewerAfterClose(viewerState);
1793
- }
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();
2029
+ if (videoElement.networkState !== videoElement.NETWORK_NO_SOURCE) {
2030
+ return;
1807
2031
  }
2032
+ markMarkdownVideoFailed(videoElement);
1808
2033
  }
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 || "";
2034
+ function updateMarkdownVideoState(videoElement) {
2035
+ const frameElement = videoElement.closest(
2036
+ `.${VIDEO_FRAME_CLASS_NAME}`
2037
+ );
2038
+ if (!frameElement) {
2039
+ return;
2040
+ }
2041
+ const isFailed = videoElement.error !== null || videoElement.networkState === videoElement.NETWORK_NO_SOURCE;
2042
+ const isLoaded = videoElement.readyState >= videoElement.HAVE_CURRENT_DATA;
2043
+ frameElement.classList.toggle(VIDEO_FRAME_FAILED_CLASS_NAME, isFailed);
2044
+ frameElement.classList.toggle(
2045
+ VIDEO_FRAME_LOADED_CLASS_NAME,
2046
+ isLoaded && !isFailed
2047
+ );
1814
2048
  }
1815
- function createMarkdownImageViewerAriaLabel(imageElement) {
1816
- const imageAltText = imageElement.alt.trim();
1817
- if (imageAltText.length === 0) {
1818
- return IMAGE_VIEWER_FALLBACK_LABEL;
2049
+ function markMarkdownVideoFailed(videoElement) {
2050
+ const frameElement = videoElement.closest(
2051
+ `.${VIDEO_FRAME_CLASS_NAME}`
2052
+ );
2053
+ if (!frameElement) {
2054
+ return;
1819
2055
  }
1820
- return `${IMAGE_VIEWER_FALLBACK_LABEL}\uFF1A${imageAltText}`;
2056
+ frameElement.classList.add(VIDEO_FRAME_FAILED_CLASS_NAME);
2057
+ frameElement.classList.remove(VIDEO_FRAME_LOADED_CLASS_NAME);
1821
2058
  }
1822
- function createMarkdownImageViewerCaption(imageElement, imageSource) {
1823
- const imageAltText = imageElement.alt.trim();
1824
- const imageTitleText = imageElement.title.trim();
1825
- return imageAltText || imageTitleText || imageSource;
2059
+
2060
+ // src/sanitize.ts
2061
+ import {
2062
+ DETAILS_CLASS_NAME,
2063
+ FRAME_CLASS_NAME,
2064
+ FRONTMATTER_CHROME_CLASS_NAME,
2065
+ FRONTMATTER_CLASS_NAME,
2066
+ FRONTMATTER_LABEL_CLASS_NAME,
2067
+ FRONTMATTER_LIST_CLASS_NAME,
2068
+ FRONTMATTER_LIST_NESTED_CLASS_NAME,
2069
+ HEADING_MARK_CLASS_NAME,
2070
+ IMAGE_CAPTION_CLASS_NAME as IMAGE_CAPTION_CLASS_NAME2,
2071
+ IMAGE_ELEMENT_CLASS_NAME as IMAGE_ELEMENT_CLASS_NAME2,
2072
+ IMAGE_FALLBACK_CLASS_NAME as IMAGE_FALLBACK_CLASS_NAME2,
2073
+ IMAGE_FALLBACK_ICON_CLASS_NAME as IMAGE_FALLBACK_ICON_CLASS_NAME2,
2074
+ IMAGE_FALLBACK_SOURCE_CLASS_NAME as IMAGE_FALLBACK_SOURCE_CLASS_NAME2,
2075
+ IMAGE_FALLBACK_TEXT_CLASS_NAME as IMAGE_FALLBACK_TEXT_CLASS_NAME2,
2076
+ IMAGE_FIGURE_CLASS_NAME as IMAGE_FIGURE_CLASS_NAME2,
2077
+ IMAGE_FRAME_CLASS_NAME as IMAGE_FRAME_CLASS_NAME2,
2078
+ TOC_CLASS_NAME,
2079
+ TOC_ITEM_CLASS_PREFIX,
2080
+ TOC_LINK_CLASS_NAME,
2081
+ TOC_LIST_CLASS_NAME,
2082
+ TOC_LIST_NESTED_CLASS_NAME,
2083
+ TOC_NAV_CLASS_NAME,
2084
+ TOC_SUMMARY_CLASS_NAME,
2085
+ TOC_TOGGLE_CLASS_NAME,
2086
+ VIDEO_ELEMENT_CLASS_NAME as VIDEO_ELEMENT_CLASS_NAME2,
2087
+ VIDEO_FALLBACK_CLASS_NAME as VIDEO_FALLBACK_CLASS_NAME2,
2088
+ VIDEO_FALLBACK_ICON_CLASS_NAME as VIDEO_FALLBACK_ICON_CLASS_NAME2,
2089
+ VIDEO_FALLBACK_SOURCE_CLASS_NAME as VIDEO_FALLBACK_SOURCE_CLASS_NAME2,
2090
+ VIDEO_FALLBACK_TEXT_CLASS_NAME as VIDEO_FALLBACK_TEXT_CLASS_NAME2,
2091
+ VIDEO_FIGURE_CLASS_NAME as VIDEO_FIGURE_CLASS_NAME2,
2092
+ VIDEO_FRAME_CLASS_NAME as VIDEO_FRAME_CLASS_NAME2
2093
+ } from "@scribdown/shared";
2094
+ import DOMPurify from "dompurify";
2095
+ import { defaultSchema } from "rehype-sanitize";
2096
+ function sanitizeHtmlWithDomPurify(unsafeHtml) {
2097
+ const domPurify = DOMPurify;
2098
+ if (typeof domPurify.sanitize !== "function") {
2099
+ return unsafeHtml;
2100
+ }
2101
+ return domPurify.sanitize(unsafeHtml);
1826
2102
  }
1827
2103
  function createScribdownSanitizeSchema() {
1828
2104
  const defaultTagNames = defaultSchema.tagNames ?? [];
@@ -1835,7 +2111,7 @@ function createScribdownSanitizeSchema() {
1835
2111
  ...defaultAttributes.summary ?? [],
1836
2112
  ["className", TOC_SUMMARY_CLASS_NAME]
1837
2113
  ];
1838
- const navAttributes = ["ariaLabel", ["className", /^scribdown-toc-nav$/u]];
2114
+ const navAttributes = ["ariaLabel", ["className", TOC_NAV_CLASS_NAME]];
1839
2115
  const linkAttributes = [
1840
2116
  "ariaDescribedBy",
1841
2117
  "ariaLabel",
@@ -1855,32 +2131,40 @@ function createScribdownSanitizeSchema() {
1855
2131
  "ariaDescribedBy",
1856
2132
  "ariaLabel",
1857
2133
  "ariaLabelledBy",
1858
- ["className", "contains-task-list", /^scribdown-toc-list(?:--nested)?$/u]
2134
+ ["className", "contains-task-list", TOC_LIST_CLASS_NAME, TOC_LIST_NESTED_CLASS_NAME]
2135
+ ];
2136
+ const divAttributes = [
2137
+ ...defaultAttributes.div ?? [],
2138
+ ["className", FRONTMATTER_CLASS_NAME, FRAME_CLASS_NAME, FRONTMATTER_CHROME_CLASS_NAME]
2139
+ ];
2140
+ const definitionListAttributes = [
2141
+ ["className", FRONTMATTER_LIST_CLASS_NAME, FRONTMATTER_LIST_NESTED_CLASS_NAME]
1859
2142
  ];
1860
2143
  const spanAttributes = [
1861
2144
  ...defaultAttributes.span ?? [],
1862
2145
  [
1863
2146
  "className",
1864
2147
  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
2148
+ FRONTMATTER_LABEL_CLASS_NAME,
2149
+ IMAGE_FRAME_CLASS_NAME2,
2150
+ IMAGE_FALLBACK_CLASS_NAME2,
2151
+ IMAGE_FALLBACK_ICON_CLASS_NAME2,
2152
+ IMAGE_FALLBACK_TEXT_CLASS_NAME2,
2153
+ IMAGE_FALLBACK_SOURCE_CLASS_NAME2,
2154
+ VIDEO_FRAME_CLASS_NAME2,
2155
+ VIDEO_FALLBACK_CLASS_NAME2,
2156
+ VIDEO_FALLBACK_ICON_CLASS_NAME2,
2157
+ VIDEO_FALLBACK_TEXT_CLASS_NAME2,
2158
+ VIDEO_FALLBACK_SOURCE_CLASS_NAME2
1875
2159
  ]
1876
2160
  ];
1877
2161
  const figureAttributes = [
1878
- ["className", IMAGE_FIGURE_CLASS_NAME, VIDEO_FIGURE_CLASS_NAME]
2162
+ ["className", IMAGE_FIGURE_CLASS_NAME2, VIDEO_FIGURE_CLASS_NAME2]
1879
2163
  ];
1880
- const figcaptionAttributes = [["className", IMAGE_CAPTION_CLASS_NAME]];
2164
+ const figcaptionAttributes = [["className", IMAGE_CAPTION_CLASS_NAME2]];
1881
2165
  const imageAttributes = [
1882
2166
  ...defaultAttributes.img ?? [],
1883
- ["className", IMAGE_ELEMENT_CLASS_NAME]
2167
+ ["className", IMAGE_ELEMENT_CLASS_NAME2]
1884
2168
  ];
1885
2169
  const videoAttributes = [
1886
2170
  "src",
@@ -1895,13 +2179,17 @@ function createScribdownSanitizeSchema() {
1895
2179
  "loop",
1896
2180
  "autoPlay",
1897
2181
  "crossOrigin",
1898
- ["className", VIDEO_ELEMENT_CLASS_NAME]
2182
+ ["className", VIDEO_ELEMENT_CLASS_NAME2]
1899
2183
  ];
1900
2184
  const sourceAttributes = ["src", "type", "media"];
1901
2185
  const abbrAttributes = ["title"];
1902
2186
  const listItemAttributes = [
1903
2187
  "dataTocIndex",
1904
- ["className", "task-list-item", /^scribdown-toc-item(?:--(?:branch|depth-[1-6]))?$/u]
2188
+ [
2189
+ "className",
2190
+ "task-list-item",
2191
+ new RegExp(`^${TOC_ITEM_CLASS_PREFIX}(?:--(?:branch|depth-[1-6]))?$`, "u")
2192
+ ]
1905
2193
  ];
1906
2194
  const wildcardAttributes = [...defaultAttributes["*"] ?? [], SOURCE_LINE_HAST_PROPERTY];
1907
2195
  return {
@@ -1940,6 +2228,8 @@ function createScribdownSanitizeSchema() {
1940
2228
  a: linkAttributes,
1941
2229
  ol: orderedListAttributes,
1942
2230
  li: listItemAttributes,
2231
+ div: divAttributes,
2232
+ dl: definitionListAttributes,
1943
2233
  span: spanAttributes,
1944
2234
  abbr: abbrAttributes,
1945
2235
  figure: figureAttributes,
@@ -1950,108 +2240,24 @@ function createScribdownSanitizeSchema() {
1950
2240
  }
1951
2241
  };
1952
2242
  }
1953
- function remarkHighlightMark() {
1954
- return (tree) => {
1955
- transformHighlightMarks(tree);
1956
- };
1957
- }
1958
- var BLOCK_CONTAINER_NODE_TYPES = /* @__PURE__ */ new Set(["list", "listItem", "blockquote"]);
1959
- function remarkSourceLine() {
1960
- return (tree) => {
1961
- const blockNodes = tree.children ?? [];
1962
- blockNodes.forEach((blockNode) => {
1963
- annotateSourceLine(blockNode);
1964
- annotateNestedBlockSourceLines(blockNode);
1965
- });
1966
- };
1967
- }
1968
- function annotateSourceLine(node) {
1969
- const startLine = node.position?.start.line;
1970
- if (typeof startLine !== "number") {
1971
- return;
1972
- }
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
- }
1979
- function annotateNestedBlockSourceLines(node) {
1980
- if (!BLOCK_CONTAINER_NODE_TYPES.has(node.type)) {
1981
- return;
1982
- }
1983
- const childNodes = node.children ?? [];
1984
- childNodes.forEach((childNode) => {
1985
- annotateSourceLine(childNode);
1986
- annotateNestedBlockSourceLines(childNode);
1987
- });
1988
- }
1989
- function transformHighlightMarks(node) {
1990
- if (!node.children) {
1991
- return;
1992
- }
1993
- const nextChildren = [];
1994
- for (let childIndex = 0; childIndex < node.children.length; childIndex += 1) {
1995
- const childNode = node.children[childIndex];
1996
- if (childNode.type === "text" && typeof childNode.value === "string" && childNode.value.includes("==")) {
1997
- nextChildren.push(...splitHighlightMarks(childNode.value));
1998
- continue;
1999
- }
2000
- transformHighlightMarks(childNode);
2001
- nextChildren.push(childNode);
2002
- }
2003
- node.children = nextChildren;
2004
- }
2005
- function splitHighlightMarks(textValue) {
2006
- const segments = [];
2007
- let cursor = 0;
2008
- HIGHLIGHT_MARKER_PATTERN.lastIndex = 0;
2009
- let match = HIGHLIGHT_MARKER_PATTERN.exec(textValue);
2010
- while (match !== null) {
2011
- const leadingText = textValue.slice(cursor, match.index);
2012
- if (leadingText.length > 0) {
2013
- segments.push({ type: "text", value: leadingText });
2014
- }
2015
- segments.push({
2016
- type: "highlightMark",
2017
- data: {
2018
- hName: "mark"
2019
- },
2020
- children: [{ type: "text", value: match[1] }]
2021
- });
2022
- cursor = match.index + match[0].length;
2023
- match = HIGHLIGHT_MARKER_PATTERN.exec(textValue);
2024
- }
2025
- const trailingText = textValue.slice(cursor);
2026
- if (trailingText.length > 0) {
2027
- segments.push({ type: "text", value: trailingText });
2028
- }
2029
- return segments;
2030
- }
2243
+
2244
+ // src/syntax/definition-lists.ts
2245
+ import { visit as visit3 } from "unist-util-visit";
2246
+ var DEFINITION_LIST_PARAGRAPH_PATTERN = /^\s*([^\n:][^\n]*)\n\s*:\s+([^\n]+)\s*$/u;
2031
2247
  function remarkDefinitionLists() {
2032
2248
  return (tree) => {
2033
- transformDefinitionLists(tree);
2249
+ visit3(tree, "paragraph", (node, index, parent) => {
2250
+ if (parent === void 0 || index === void 0) {
2251
+ return;
2252
+ }
2253
+ const definitionListNode = createDefinitionListNode(node);
2254
+ if (definitionListNode) {
2255
+ parent.children[index] = definitionListNode;
2256
+ }
2257
+ });
2034
2258
  };
2035
2259
  }
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
2260
  function createDefinitionListNode(node) {
2052
- if (node.type !== "paragraph") {
2053
- return void 0;
2054
- }
2055
2261
  const paragraphText = extractNodeText(node);
2056
2262
  const definitionMatch = DEFINITION_LIST_PARAGRAPH_PATTERN.exec(paragraphText);
2057
2263
  if (!definitionMatch) {
@@ -2087,138 +2293,221 @@ function createDefinitionListNode(node) {
2087
2293
  ]
2088
2294
  };
2089
2295
  }
2090
- function remarkImageFigures() {
2296
+
2297
+ // src/syntax/frame.ts
2298
+ 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";
2299
+ import { classnames as classnames2 } from "hast-util-classnames";
2300
+ import { visit as visit4 } from "unist-util-visit";
2301
+ function rehypeFrameClass() {
2091
2302
  return (tree) => {
2092
- transformImageFigures(tree);
2303
+ visit4(tree, "element", (node) => {
2304
+ if (node.tagName !== "details") {
2305
+ return;
2306
+ }
2307
+ const classNames = normalizeClassNames(node.properties.className);
2308
+ if (!classNames.includes(TOC_CLASS_NAME2)) {
2309
+ classnames2(node, DETAILS_CLASS_NAME2, FRAME_CLASS_NAME2);
2310
+ }
2311
+ });
2093
2312
  };
2094
2313
  }
2095
- function transformImageFigures(node) {
2096
- if (!node.children) {
2097
- return;
2314
+ function normalizeClassNames(className) {
2315
+ if (Array.isArray(className)) {
2316
+ return className.filter((item) => typeof item === "string");
2098
2317
  }
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);
2318
+ if (typeof className === "string") {
2319
+ return className.split(/\s+/u).filter(Boolean);
2112
2320
  }
2321
+ return [];
2113
2322
  }
2114
- function createImageFigureNode(node) {
2115
- if (node.type !== "paragraph" || !node.children || node.children.length !== 1) {
2116
- return void 0;
2323
+
2324
+ // src/syntax/frontmatter.ts
2325
+ import {
2326
+ FRAME_CLASS_NAME as FRAME_CLASS_NAME3,
2327
+ FRONTMATTER_CHROME_CLASS_NAME as FRONTMATTER_CHROME_CLASS_NAME2,
2328
+ FRONTMATTER_CLASS_NAME as FRONTMATTER_CLASS_NAME2,
2329
+ FRONTMATTER_LABEL_CLASS_NAME as FRONTMATTER_LABEL_CLASS_NAME2,
2330
+ FRONTMATTER_LIST_CLASS_NAME as FRONTMATTER_LIST_CLASS_NAME2,
2331
+ FRONTMATTER_LIST_NESTED_CLASS_NAME as FRONTMATTER_LIST_NESTED_CLASS_NAME2
2332
+ } from "@scribdown/shared";
2333
+ import { parse as parseYamlSource } from "yaml";
2334
+ var FRONTMATTER_LABEL_TEXT = "\u5143\u6570\u636E";
2335
+ var FRONTMATTER_ARRAY_SEPARATOR = ", ";
2336
+ var FRONTMATTER_FALLBACK_LANGUAGE_ID = "yaml";
2337
+ function remarkFrontmatterMetadata() {
2338
+ return (tree) => {
2339
+ const childNodes = tree.children;
2340
+ const yamlNode = childNodes[0];
2341
+ if (!yamlNode || yamlNode.type !== "yaml") {
2342
+ return;
2343
+ }
2344
+ childNodes[0] = createFrontmatterNode(yamlNode);
2345
+ };
2346
+ }
2347
+ function createFrontmatterNode(yamlNode) {
2348
+ const yamlSource = yamlNode.value;
2349
+ let parsedValue;
2350
+ try {
2351
+ parsedValue = parseYamlSource(yamlSource);
2352
+ } catch {
2353
+ parsedValue = void 0;
2117
2354
  }
2118
- const onlyChild = node.children[0];
2119
- if (!isImageNode(onlyChild)) {
2120
- return void 0;
2355
+ if (!isPlainRecord(parsedValue)) {
2356
+ return {
2357
+ type: "code",
2358
+ lang: FRONTMATTER_FALLBACK_LANGUAGE_ID,
2359
+ position: yamlNode.position,
2360
+ value: yamlSource
2361
+ };
2121
2362
  }
2122
- decorateImageNode(onlyChild);
2123
2363
  return {
2124
- type: "imageFigure",
2125
- // 关键步骤:保留原段落源码位置,使 remarkSourceLine 能为 figure 标注 data-source-line。
2126
- position: node.position,
2364
+ type: "frontmatterMetadata",
2365
+ // 关键步骤:保留原节点源码位置,使 remarkSourceLine 能标注 data-source-line。
2366
+ position: yamlNode.position,
2127
2367
  data: {
2128
- hName: "figure",
2129
- hProperties: {
2130
- className: [IMAGE_FIGURE_CLASS_NAME]
2131
- }
2368
+ hName: "div",
2369
+ hProperties: { className: [FRONTMATTER_CLASS_NAME2, FRAME_CLASS_NAME3] }
2132
2370
  },
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
- }
2371
+ children: [
2372
+ {
2373
+ type: "frontmatterChrome",
2374
+ data: {
2375
+ hName: "div",
2376
+ hProperties: { className: [FRONTMATTER_CHROME_CLASS_NAME2] }
2377
+ },
2378
+ children: [
2379
+ {
2380
+ type: "frontmatterLabel",
2381
+ data: {
2382
+ hName: "span",
2383
+ hProperties: { className: [FRONTMATTER_LABEL_CLASS_NAME2] }
2384
+ },
2385
+ children: [{ type: "text", value: FRONTMATTER_LABEL_TEXT }]
2386
+ }
2387
+ ]
2388
+ },
2389
+ createFrontmatterListNode(parsedValue, false)
2390
+ ]
2143
2391
  };
2144
2392
  }
2145
- function createImageFrameNode(imageNode) {
2393
+ function isPlainRecord(value) {
2394
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2395
+ }
2396
+ function createFrontmatterListNode(record, nested) {
2397
+ const listClassNames = nested ? [FRONTMATTER_LIST_CLASS_NAME2, FRONTMATTER_LIST_NESTED_CLASS_NAME2] : [FRONTMATTER_LIST_CLASS_NAME2];
2398
+ const listChildren = [];
2399
+ Object.entries(record).forEach(([entryKey, entryValue]) => {
2400
+ listChildren.push({
2401
+ type: "frontmatterTerm",
2402
+ data: { hName: "dt" },
2403
+ children: [{ type: "text", value: entryKey }]
2404
+ });
2405
+ listChildren.push({
2406
+ type: "frontmatterDescription",
2407
+ data: { hName: "dd" },
2408
+ children: createFrontmatterValueNodes(entryValue)
2409
+ });
2410
+ });
2146
2411
  return {
2147
- type: "imageFrame",
2412
+ type: "frontmatterList",
2148
2413
  data: {
2149
- hName: "span",
2150
- hProperties: {
2151
- className: [IMAGE_FRAME_CLASS_NAME]
2152
- }
2414
+ hName: "dl",
2415
+ hProperties: { className: listClassNames }
2153
2416
  },
2154
- children: [imageNode, createImageFallbackNode(imageNode)]
2417
+ children: listChildren
2155
2418
  };
2156
2419
  }
2157
- function createImageCaptionNodes(imageNode) {
2158
- const imageTitle = typeof imageNode.title === "string" ? imageNode.title.trim() : "";
2159
- if (imageTitle.length === 0) {
2160
- return [];
2420
+ function createFrontmatterValueNodes(value) {
2421
+ if (isPlainRecord(value)) {
2422
+ return [createFrontmatterListNode(value, true)];
2161
2423
  }
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
- },
2424
+ if (Array.isArray(value)) {
2425
+ return [
2206
2426
  {
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 }]
2427
+ type: "text",
2428
+ value: value.map(formatFrontmatterScalar).join(FRONTMATTER_ARRAY_SEPARATOR)
2429
+ }
2430
+ ];
2431
+ }
2432
+ return [{ type: "text", value: formatFrontmatterScalar(value) }];
2433
+ }
2434
+ function formatFrontmatterScalar(value) {
2435
+ if (value === null || value === void 0) {
2436
+ return "";
2437
+ }
2438
+ if (typeof value === "object") {
2439
+ return JSON.stringify(value);
2440
+ }
2441
+ return String(value);
2442
+ }
2443
+
2444
+ // src/syntax/highlight-mark.ts
2445
+ import { SKIP as SKIP3, visit as visit5 } from "unist-util-visit";
2446
+ var HIGHLIGHT_MARKER_PATTERN = /==([^=\n]+?)==/g;
2447
+ function remarkHighlightMark() {
2448
+ return (tree) => {
2449
+ visit5(tree, "text", (node, index, parent) => {
2450
+ if (parent === void 0 || index === void 0 || !node.value.includes("==")) {
2451
+ return;
2215
2452
  }
2216
- ]
2453
+ const segments = splitHighlightMarks(node.value);
2454
+ parent.children.splice(index, 1, ...segments);
2455
+ return [SKIP3, index + segments.length];
2456
+ });
2217
2457
  };
2218
2458
  }
2219
- function isImageNode(node) {
2220
- return node.type === "image" || node.type === "imageReference";
2459
+ function splitHighlightMarks(textValue) {
2460
+ const segments = [];
2461
+ let cursor = 0;
2462
+ HIGHLIGHT_MARKER_PATTERN.lastIndex = 0;
2463
+ let match = HIGHLIGHT_MARKER_PATTERN.exec(textValue);
2464
+ while (match !== null) {
2465
+ const leadingText = textValue.slice(cursor, match.index);
2466
+ if (leadingText.length > 0) {
2467
+ segments.push({ type: "text", value: leadingText });
2468
+ }
2469
+ segments.push({
2470
+ type: "highlightMark",
2471
+ data: {
2472
+ hName: "mark"
2473
+ },
2474
+ children: [{ type: "text", value: match[1] }]
2475
+ });
2476
+ cursor = match.index + match[0].length;
2477
+ match = HIGHLIGHT_MARKER_PATTERN.exec(textValue);
2478
+ }
2479
+ const trailingText = textValue.slice(cursor);
2480
+ if (trailingText.length > 0) {
2481
+ segments.push({ type: "text", value: trailingText });
2482
+ }
2483
+ return segments;
2221
2484
  }
2485
+
2486
+ // src/syntax/toc.ts
2487
+ import {
2488
+ FRAME_CLASS_NAME as FRAME_CLASS_NAME4,
2489
+ HEADING_MARK_CLASS_NAME as HEADING_MARK_CLASS_NAME2,
2490
+ SCRIBDOWN_MARKDOWN_CLASS_NAME,
2491
+ TOC_CLASS_NAME as TOC_CLASS_NAME3,
2492
+ TOC_ITEM_BRANCH_CLASS_NAME,
2493
+ TOC_ITEM_CLASS_PREFIX as TOC_ITEM_CLASS_PREFIX2,
2494
+ TOC_ITEM_COLLAPSED_CLASS_NAME,
2495
+ TOC_LINK_CLASS_NAME as TOC_LINK_CLASS_NAME2,
2496
+ TOC_LIST_CLASS_NAME as TOC_LIST_CLASS_NAME2,
2497
+ TOC_LIST_NESTED_CLASS_NAME as TOC_LIST_NESTED_CLASS_NAME2,
2498
+ TOC_NAV_CLASS_NAME as TOC_NAV_CLASS_NAME2,
2499
+ TOC_SUMMARY_CLASS_NAME as TOC_SUMMARY_CLASS_NAME2,
2500
+ TOC_TOGGLE_CLASS_NAME as TOC_TOGGLE_CLASS_NAME2
2501
+ } from "@scribdown/shared";
2502
+ import GithubSlugger from "github-slugger";
2503
+ import { visit as visit6 } from "unist-util-visit";
2504
+ var TOC_MARKER_PATTERN = /^\s*\[toc]\s*$/i;
2505
+ var TOC_TOGGLE_ARIA_LABEL = "\u5C55\u5F00\u6216\u6298\u53E0\u5B50\u76EE\u5F55";
2506
+ var TOC_TOGGLE_HYDRATED_DATA_KEY = "scribdownTocToggleHydrated";
2507
+ var TOC_LINK_HYDRATED_DATA_KEY = "scribdownTocLinkHydrated";
2508
+ var TOC_ARIA_LABEL = "\u76EE\u5F55";
2509
+ var TOC_SUMMARY_TEXT = "\u76EE\u5F55";
2510
+ var EMPTY_HEADING_SLUG_PREFIX = "section";
2222
2511
  function remarkTableOfContents() {
2223
2512
  return (tree) => {
2224
2513
  const tocHeadings = collectTocHeadings(tree);
@@ -2226,17 +2515,14 @@ function remarkTableOfContents() {
2226
2515
  };
2227
2516
  }
2228
2517
  function collectTocHeadings(tree) {
2229
- const headingSlugCounts = /* @__PURE__ */ new Map();
2518
+ const headingSlugger = new GithubSlugger();
2230
2519
  const tocHeadings = [];
2231
2520
  const headingIndexCounts = Array.from({ length: 7 }, () => 0);
2232
2521
  let rootHeadingDepth;
2233
- visitMarkdownNode(tree, (node) => {
2234
- if (node.type !== "heading") {
2235
- return;
2236
- }
2237
- const headingDepth = node.depth ?? 2;
2522
+ visit6(tree, "heading", (node) => {
2523
+ const headingDepth = node.depth;
2238
2524
  const headingText = extractNodeText(node).trim();
2239
- const headingId = createUniqueSlug(headingText, headingSlugCounts, tocHeadings.length + 1);
2525
+ const headingId = createHeadingId(headingSlugger, headingText, tocHeadings.length + 1);
2240
2526
  const headingIndex = createHeadingIndex(
2241
2527
  headingDepth,
2242
2528
  headingIndexCounts,
@@ -2256,10 +2542,10 @@ function collectTocHeadings(tree) {
2256
2542
  data: {
2257
2543
  hName: "span",
2258
2544
  hProperties: {
2259
- className: [HEADING_MARK_CLASS_NAME]
2545
+ className: [HEADING_MARK_CLASS_NAME2]
2260
2546
  }
2261
2547
  },
2262
- children: node.children ?? []
2548
+ children: node.children
2263
2549
  }
2264
2550
  ];
2265
2551
  tocHeadings.push({
@@ -2271,36 +2557,20 @@ function collectTocHeadings(tree) {
2271
2557
  });
2272
2558
  return tocHeadings;
2273
2559
  }
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
- function replaceTocMarkers(node, tocHeadings) {
2285
- if (!node.children) {
2286
- return;
2287
- }
2288
- const childNodes = node.children;
2289
- for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {
2290
- const childNode = childNodes[childIndex];
2291
- if (isTocMarkerParagraph(childNode)) {
2292
- childNodes[childIndex] = createTocNode(tocHeadings, childNode);
2293
- continue;
2560
+ function replaceTocMarkers(tree, tocHeadings) {
2561
+ visit6(tree, "paragraph", (node, index, parent) => {
2562
+ if (parent === void 0 || index === void 0 || !isTocMarkerParagraph(node)) {
2563
+ return;
2294
2564
  }
2295
- replaceTocMarkers(childNode, tocHeadings);
2296
- }
2565
+ parent.children[index] = createTocNode(tocHeadings, node);
2566
+ });
2297
2567
  }
2298
2568
  function isTocMarkerParagraph(node) {
2299
- if (node.type !== "paragraph" || !node.children || node.children.length !== 1) {
2569
+ if (node.children.length !== 1) {
2300
2570
  return false;
2301
2571
  }
2302
2572
  const onlyChild = node.children[0];
2303
- return onlyChild.type === "text" && TOC_MARKER_PATTERN.test(onlyChild.value ?? "");
2573
+ return onlyChild.type === "text" && TOC_MARKER_PATTERN.test(onlyChild.value);
2304
2574
  }
2305
2575
  function createTocNode(tocHeadings, markerNode) {
2306
2576
  const tocTree = createTocTree(tocHeadings);
@@ -2312,7 +2582,7 @@ function createTocNode(tocHeadings, markerNode) {
2312
2582
  hName: "details",
2313
2583
  hProperties: {
2314
2584
  // 关键步骤:目录根 opt-in 手绘边框,再由 toc.css 叠加目录自身样式。
2315
- className: [TOC_CLASS_NAME, FRAME_CLASS_NAME]
2585
+ className: [TOC_CLASS_NAME3, FRAME_CLASS_NAME4]
2316
2586
  }
2317
2587
  },
2318
2588
  children: [
@@ -2321,7 +2591,7 @@ function createTocNode(tocHeadings, markerNode) {
2321
2591
  data: {
2322
2592
  hName: "summary",
2323
2593
  hProperties: {
2324
- className: [TOC_SUMMARY_CLASS_NAME]
2594
+ className: [TOC_SUMMARY_CLASS_NAME2]
2325
2595
  }
2326
2596
  },
2327
2597
  children: [
@@ -2337,7 +2607,7 @@ function createTocNode(tocHeadings, markerNode) {
2337
2607
  hName: "nav",
2338
2608
  hProperties: {
2339
2609
  ariaLabel: TOC_ARIA_LABEL,
2340
- className: [TOC_NAV_CLASS_NAME]
2610
+ className: [TOC_NAV_CLASS_NAME2]
2341
2611
  }
2342
2612
  },
2343
2613
  children: [createTocListNode(tocTree, false)]
@@ -2367,12 +2637,11 @@ function createTocTree(tocHeadings) {
2367
2637
  return rootItems;
2368
2638
  }
2369
2639
  function createTocListNode(tocItems, isNested) {
2370
- const tocListClassNames = isNested ? [TOC_LIST_CLASS_NAME, TOC_LIST_NESTED_CLASS_NAME] : [TOC_LIST_CLASS_NAME];
2640
+ const tocListClassNames = isNested ? [TOC_LIST_CLASS_NAME2, TOC_LIST_NESTED_CLASS_NAME2] : [TOC_LIST_CLASS_NAME2];
2371
2641
  return {
2372
- type: "list",
2373
- ordered: true,
2374
- spread: false,
2642
+ type: "tocList",
2375
2643
  data: {
2644
+ hName: "ol",
2376
2645
  hProperties: {
2377
2646
  className: tocListClassNames
2378
2647
  }
@@ -2385,9 +2654,9 @@ function createTocListItem(tocItem) {
2385
2654
  const tocItemClassNames = createTocItemClassNames(tocItem.depth, hasChildren);
2386
2655
  const itemChildren = hasChildren ? [createTocToggleNode(), createTocLinkNode(tocItem), createTocListNode(tocItem.children, true)] : [createTocLinkNode(tocItem)];
2387
2656
  return {
2388
- type: "listItem",
2389
- spread: false,
2657
+ type: "tocItem",
2390
2658
  data: {
2659
+ hName: "li",
2391
2660
  hProperties: {
2392
2661
  dataTocIndex: tocItem.index,
2393
2662
  className: tocItemClassNames
@@ -2398,8 +2667,8 @@ function createTocListItem(tocItem) {
2398
2667
  }
2399
2668
  function createTocItemClassNames(depth, hasChildren) {
2400
2669
  return [
2401
- TOC_ITEM_CLASS_PREFIX,
2402
- `${TOC_ITEM_CLASS_PREFIX}--depth-${depth}`,
2670
+ TOC_ITEM_CLASS_PREFIX2,
2671
+ `${TOC_ITEM_CLASS_PREFIX2}--depth-${depth}`,
2403
2672
  ...hasChildren ? [TOC_ITEM_BRANCH_CLASS_NAME] : []
2404
2673
  ];
2405
2674
  }
@@ -2410,7 +2679,7 @@ function createTocToggleNode() {
2410
2679
  hName: "button",
2411
2680
  hProperties: {
2412
2681
  type: "button",
2413
- className: [TOC_TOGGLE_CLASS_NAME],
2682
+ className: [TOC_TOGGLE_CLASS_NAME2],
2414
2683
  ariaExpanded: "true",
2415
2684
  ariaLabel: TOC_TOGGLE_ARIA_LABEL
2416
2685
  }
@@ -2424,7 +2693,7 @@ function createTocLinkNode(tocItem) {
2424
2693
  url: `#${tocItem.id}`,
2425
2694
  data: {
2426
2695
  hProperties: {
2427
- className: [TOC_LINK_CLASS_NAME]
2696
+ className: [TOC_LINK_CLASS_NAME2]
2428
2697
  }
2429
2698
  },
2430
2699
  children: [
@@ -2435,6 +2704,26 @@ function createTocLinkNode(tocItem) {
2435
2704
  ]
2436
2705
  };
2437
2706
  }
2707
+ function createHeadingId(headingSlugger, headingText, fallbackIndex) {
2708
+ const baseSlug = headingSlugger.slug(headingText);
2709
+ if (baseSlug) {
2710
+ return baseSlug;
2711
+ }
2712
+ return headingSlugger.slug(`${EMPTY_HEADING_SLUG_PREFIX}-${fallbackIndex}`);
2713
+ }
2714
+ function createHeadingIndex(headingDepth, headingIndexCounts, rootHeadingDepth) {
2715
+ const startDepth = Math.min(rootHeadingDepth, headingDepth);
2716
+ headingIndexCounts[headingDepth] += 1;
2717
+ for (let depthIndex = headingDepth + 1; depthIndex < headingIndexCounts.length; depthIndex += 1) {
2718
+ headingIndexCounts[depthIndex] = 0;
2719
+ }
2720
+ for (let depthIndex = startDepth; depthIndex < headingDepth; depthIndex += 1) {
2721
+ if (headingIndexCounts[depthIndex] === 0) {
2722
+ headingIndexCounts[depthIndex] = 1;
2723
+ }
2724
+ }
2725
+ return headingIndexCounts.slice(startDepth, headingDepth + 1).join(".");
2726
+ }
2438
2727
  function collectTocHeadingsFromDom(rootElement) {
2439
2728
  const markdownContainer = rootElement instanceof Element && rootElement.matches(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME}`) ? rootElement : rootElement.querySelector(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME}`);
2440
2729
  if (!markdownContainer) {
@@ -2473,13 +2762,13 @@ function collectTocHeadingsFromDom(rootElement) {
2473
2762
  function createTocNavElement(ownerDocument, tocTreeItems) {
2474
2763
  const navElement = ownerDocument.createElement("nav");
2475
2764
  navElement.setAttribute("aria-label", TOC_ARIA_LABEL);
2476
- navElement.className = TOC_NAV_CLASS_NAME;
2765
+ navElement.className = TOC_NAV_CLASS_NAME2;
2477
2766
  navElement.appendChild(createTocListElement(ownerDocument, tocTreeItems, false));
2478
2767
  return navElement;
2479
2768
  }
2480
2769
  function createTocListElement(ownerDocument, tocItems, isNested) {
2481
2770
  const listElement = ownerDocument.createElement("ol");
2482
- listElement.className = isNested ? `${TOC_LIST_CLASS_NAME} ${TOC_LIST_NESTED_CLASS_NAME}` : TOC_LIST_CLASS_NAME;
2771
+ listElement.className = isNested ? `${TOC_LIST_CLASS_NAME2} ${TOC_LIST_NESTED_CLASS_NAME2}` : TOC_LIST_CLASS_NAME2;
2483
2772
  for (const tocItem of tocItems) {
2484
2773
  listElement.appendChild(createTocListItemElement(ownerDocument, tocItem));
2485
2774
  }
@@ -2493,13 +2782,13 @@ function createTocListItemElement(ownerDocument, tocItem) {
2493
2782
  if (hasChildren) {
2494
2783
  const toggleElement = ownerDocument.createElement("button");
2495
2784
  toggleElement.type = "button";
2496
- toggleElement.className = TOC_TOGGLE_CLASS_NAME;
2785
+ toggleElement.className = TOC_TOGGLE_CLASS_NAME2;
2497
2786
  toggleElement.setAttribute("aria-expanded", "true");
2498
2787
  toggleElement.setAttribute("aria-label", TOC_TOGGLE_ARIA_LABEL);
2499
2788
  itemElement.appendChild(toggleElement);
2500
2789
  }
2501
2790
  const linkElement = ownerDocument.createElement("a");
2502
- linkElement.className = TOC_LINK_CLASS_NAME;
2791
+ linkElement.className = TOC_LINK_CLASS_NAME2;
2503
2792
  linkElement.href = `#${tocItem.id}`;
2504
2793
  linkElement.textContent = tocItem.text;
2505
2794
  itemElement.appendChild(linkElement);
@@ -2510,7 +2799,7 @@ function createTocListItemElement(ownerDocument, tocItem) {
2510
2799
  }
2511
2800
  function hydrateToc(rootElement, scrollToHeading) {
2512
2801
  const toggleElements = rootElement.querySelectorAll(
2513
- `button.${TOC_TOGGLE_CLASS_NAME}`
2802
+ `button.${TOC_TOGGLE_CLASS_NAME2}`
2514
2803
  );
2515
2804
  toggleElements.forEach((toggleElement) => {
2516
2805
  if (toggleElement.dataset[TOC_TOGGLE_HYDRATED_DATA_KEY] === "true") {
@@ -2518,7 +2807,7 @@ function hydrateToc(rootElement, scrollToHeading) {
2518
2807
  }
2519
2808
  toggleElement.dataset[TOC_TOGGLE_HYDRATED_DATA_KEY] = "true";
2520
2809
  toggleElement.addEventListener("click", () => {
2521
- const itemElement = toggleElement.closest(`.${TOC_ITEM_CLASS_PREFIX}`);
2810
+ const itemElement = toggleElement.closest(`.${TOC_ITEM_CLASS_PREFIX2}`);
2522
2811
  if (!itemElement) {
2523
2812
  return;
2524
2813
  }
@@ -2528,7 +2817,7 @@ function hydrateToc(rootElement, scrollToHeading) {
2528
2817
  });
2529
2818
  });
2530
2819
  const linkElements = rootElement.querySelectorAll(
2531
- `a.${TOC_LINK_CLASS_NAME}`
2820
+ `a.${TOC_LINK_CLASS_NAME2}`
2532
2821
  );
2533
2822
  linkElements.forEach((linkElement) => {
2534
2823
  if (linkElement.dataset[TOC_LINK_HYDRATED_DATA_KEY] === "true") {
@@ -2556,192 +2845,37 @@ function hydrateToc(rootElement, scrollToHeading) {
2556
2845
  });
2557
2846
  });
2558
2847
  }
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
- }
2848
+
2849
+ // src/toolbar.ts
2850
+ import {
2851
+ CONTENT_WIDTH_STORAGE_KEY,
2852
+ PROJECT_HOMEPAGE_URL,
2853
+ SCRIBDOWN_CONTENT_AREA_CLASS_NAME,
2854
+ SCRIBDOWN_CONTENT_SCROLL_CLASS_NAME,
2855
+ SCRIBDOWN_MARKDOWN_CLASS_NAME as SCRIBDOWN_MARKDOWN_CLASS_NAME2,
2856
+ SCRIBDOWN_THIN_SCROLLBAR_CLASS_NAME,
2857
+ SCRIBDOWN_TOC_HOST_CLASS_NAME,
2858
+ SCRIBDOWN_TOOLBAR_BTN_CLASS_NAME,
2859
+ SCRIBDOWN_TOOLBAR_CLASS_NAME,
2860
+ SCRIBDOWN_TOOLBAR_CURRENT_CLASS_NAME,
2861
+ SCRIBDOWN_TOOLBAR_CURRENT_TEXT_CLASS_NAME,
2862
+ SCRIBDOWN_TOOLBAR_MENU_CLASS_NAME,
2863
+ SCRIBDOWN_TOOLBAR_MENU_GROUP_CHOICES_CLASS_NAME,
2864
+ SCRIBDOWN_TOOLBAR_MENU_GROUP_CLASS_NAME,
2865
+ SCRIBDOWN_TOOLBAR_MENU_ITEM_CLASS_NAME,
2866
+ SCRIBDOWN_TOOLBAR_MENU_SELECT_CHEVRON_CLASS_NAME,
2867
+ SCRIBDOWN_TOOLBAR_MENU_SELECT_LABEL_CLASS_NAME,
2868
+ SCRIBDOWN_TOOLBAR_MENU_SELECT_VALUE_CLASS_NAME,
2869
+ SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_CLASS_NAME,
2870
+ SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_LABEL_CLASS_NAME,
2871
+ SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_MARK_CLASS_NAME,
2872
+ SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME,
2873
+ SCRIBDOWN_TOOLBAR_TOC_PANEL_CLOSE_CLASS_NAME,
2874
+ SCRIBDOWN_TOOLBAR_TOC_PANEL_EMPTY_CLASS_NAME,
2875
+ SCRIBDOWN_TOOLBAR_TOC_PANEL_TITLE_CLASS_NAME,
2876
+ TOC_LINK_ACTIVE_CLASS_NAME,
2877
+ TOC_LINK_CLASS_NAME as TOC_LINK_CLASS_NAME3
2878
+ } from "@scribdown/shared";
2745
2879
  var TOOLBAR_WIDTH_PRESETS = [
2746
2880
  { label: "680", value: "680px" },
2747
2881
  { label: "840", value: "840px" },
@@ -2778,10 +2912,11 @@ var TOOLBAR_MORE_ICON_SVG = '<svg width="16" height="16" viewBox="0 0 18 18" fil
2778
2912
  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
2913
  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
2914
  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();
2915
+ var pageToolbarTeardowns = /* @__PURE__ */ new WeakMap();
2782
2916
  function mountPageToolbar(ownerDocument, container, scrollToHeading) {
2783
- pageToolbarScrollspyTeardowns.get(container)?.();
2784
- pageToolbarScrollspyTeardowns.delete(container);
2917
+ pageToolbarTeardowns.get(container)?.();
2918
+ pageToolbarTeardowns.delete(container);
2919
+ const teardownCallbacks = [];
2785
2920
  container.querySelector(`:scope > .${SCRIBDOWN_TOOLBAR_CLASS_NAME}`)?.remove();
2786
2921
  container.querySelector(`:scope > .${SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME}`)?.remove();
2787
2922
  container.classList.add(SCRIBDOWN_TOC_HOST_CLASS_NAME);
@@ -2815,13 +2950,13 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
2815
2950
  const tocPanel = ownerDocument.createElement("div");
2816
2951
  tocPanel.className = `${SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME} ${SCRIBDOWN_THIN_SCROLLBAR_CLASS_NAME}`;
2817
2952
  const tocTitle = ownerDocument.createElement("div");
2818
- tocTitle.className = "scribdown-toolbar-toc-panel-title";
2953
+ tocTitle.className = SCRIBDOWN_TOOLBAR_TOC_PANEL_TITLE_CLASS_NAME;
2819
2954
  const tocTitleText = ownerDocument.createElement("span");
2820
2955
  tocTitleText.textContent = "\u76EE\u5F55";
2821
2956
  tocTitle.appendChild(tocTitleText);
2822
2957
  const tocCloseBtn = ownerDocument.createElement("button");
2823
2958
  tocCloseBtn.type = "button";
2824
- tocCloseBtn.className = "scribdown-toolbar-toc-panel-close";
2959
+ tocCloseBtn.className = SCRIBDOWN_TOOLBAR_TOC_PANEL_CLOSE_CLASS_NAME;
2825
2960
  tocCloseBtn.setAttribute("aria-label", "\u5173\u95ED\u76EE\u5F55");
2826
2961
  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
2962
  const setTocOpen = (open) => {
@@ -2836,7 +2971,7 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
2836
2971
  const tocHeadings = collectTocHeadingsFromDom(container);
2837
2972
  if (tocHeadings.length === 0) {
2838
2973
  const emptyElement = ownerDocument.createElement("p");
2839
- emptyElement.className = "scribdown-toolbar-toc-panel-empty";
2974
+ emptyElement.className = SCRIBDOWN_TOOLBAR_TOC_PANEL_EMPTY_CLASS_NAME;
2840
2975
  emptyElement.textContent = "\u6682\u65E0\u6807\u9898";
2841
2976
  tocPanel.appendChild(emptyElement);
2842
2977
  } else {
@@ -2866,17 +3001,17 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
2866
3001
  widthTrigger.setAttribute("aria-haspopup", "listbox");
2867
3002
  widthTrigger.setAttribute("aria-expanded", "false");
2868
3003
  const widthValueElement = ownerDocument.createElement("span");
2869
- widthValueElement.className = "scribdown-toolbar-menu-select-value";
3004
+ widthValueElement.className = SCRIBDOWN_TOOLBAR_MENU_SELECT_VALUE_CLASS_NAME;
2870
3005
  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>`;
3006
+ widthTrigger.innerHTML = `${TOOLBAR_WIDTH_ICON_SVG}<span class="${SCRIBDOWN_TOOLBAR_MENU_SELECT_LABEL_CLASS_NAME}">\u9875\u9762\u5BBD\u5EA6</span>`;
2872
3007
  widthTrigger.appendChild(widthValueElement);
2873
3008
  widthTrigger.insertAdjacentHTML(
2874
3009
  "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>'
3010
+ `<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
3011
  );
2877
3012
  widthGroup.appendChild(widthTrigger);
2878
3013
  const widthChoices = ownerDocument.createElement("div");
2879
- widthChoices.className = "scribdown-toolbar-menu-group-choices";
3014
+ widthChoices.className = SCRIBDOWN_TOOLBAR_MENU_GROUP_CHOICES_CLASS_NAME;
2880
3015
  widthChoices.setAttribute("role", "listbox");
2881
3016
  const widthSubItems = [];
2882
3017
  TOOLBAR_WIDTH_PRESETS.forEach((preset, idx) => {
@@ -2885,7 +3020,7 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
2885
3020
  subItem.className = SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_CLASS_NAME;
2886
3021
  subItem.setAttribute("role", "option");
2887
3022
  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>`;
3023
+ 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
3024
  subItem.addEventListener("click", () => {
2890
3025
  widthIndex = idx;
2891
3026
  applyContentWidth(ownerDocument, preset.value);
@@ -2923,13 +3058,14 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
2923
3058
  ownerWindow?.scrollTo({ top: 0, behavior: "smooth" });
2924
3059
  closeMoreMenu();
2925
3060
  });
2926
- const aboutItem = ownerDocument.createElement("button");
2927
- aboutItem.type = "button";
3061
+ const aboutItem = ownerDocument.createElement("a");
3062
+ aboutItem.href = PROJECT_HOMEPAGE_URL;
3063
+ aboutItem.target = "_blank";
3064
+ aboutItem.rel = "noopener noreferrer";
2928
3065
  aboutItem.className = SCRIBDOWN_TOOLBAR_MENU_ITEM_CLASS_NAME;
2929
3066
  aboutItem.setAttribute("role", "menuitem");
2930
3067
  aboutItem.innerHTML = `${TOOLBAR_ABOUT_ICON_SVG}<span>\u5173\u4E8E</span>`;
2931
3068
  aboutItem.addEventListener("click", () => {
2932
- ownerWindow?.open(PROJECT_HOMEPAGE_URL, "_blank", "noopener,noreferrer");
2933
3069
  closeMoreMenu();
2934
3070
  });
2935
3071
  menu.appendChild(backTopItem);
@@ -2948,13 +3084,17 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
2948
3084
  const opened = menu.classList.toggle("is-open");
2949
3085
  moreBtn.setAttribute("aria-expanded", String(opened));
2950
3086
  });
2951
- ownerDocument.addEventListener("click", (e) => {
3087
+ const handleDocumentClick = (e) => {
2952
3088
  const target = e.target;
2953
3089
  if (!menu.contains(target) && target !== moreBtn) {
2954
3090
  closeMoreMenu();
2955
3091
  }
3092
+ };
3093
+ ownerDocument.addEventListener("click", handleDocumentClick);
3094
+ teardownCallbacks.push(() => {
3095
+ ownerDocument.removeEventListener("click", handleDocumentClick);
2956
3096
  });
2957
- const markdownContainer = container.matches(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME}`) ? container : container.querySelector(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME}`);
3097
+ const markdownContainer = container.matches(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME2}`) ? container : container.querySelector(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME2}`);
2958
3098
  const headingElements = markdownContainer ? Array.from(
2959
3099
  markdownContainer.querySelectorAll(
2960
3100
  "h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]"
@@ -2967,12 +3107,12 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
2967
3107
  currentHeading.setAttribute("role", "button");
2968
3108
  currentHeading.tabIndex = 0;
2969
3109
  const currentHeadingText = ownerDocument.createElement("span");
2970
- currentHeadingText.className = "scribdown-toolbar-current-text";
3110
+ currentHeadingText.className = SCRIBDOWN_TOOLBAR_CURRENT_TEXT_CLASS_NAME;
2971
3111
  currentHeading.appendChild(currentHeadingText);
2972
3112
  let lastHeadingText = "";
2973
3113
  let activeHeadingElement = headingElements[0];
2974
3114
  const tocLinkByHeading = /* @__PURE__ */ new Map();
2975
- tocPanel.querySelectorAll(`a.${TOC_LINK_CLASS_NAME}`).forEach((linkElement) => {
3115
+ tocPanel.querySelectorAll(`a.${TOC_LINK_CLASS_NAME3}`).forEach((linkElement) => {
2976
3116
  const rawTargetId = (linkElement.getAttribute("href") ?? "").slice(1);
2977
3117
  if (!rawTargetId) {
2978
3118
  return;
@@ -3050,7 +3190,7 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
3050
3190
  });
3051
3191
  ownerWindow?.addEventListener("resize", onScrollOrResize, { passive: true });
3052
3192
  updateCurrentHeading();
3053
- pageToolbarScrollspyTeardowns.set(container, () => {
3193
+ teardownCallbacks.push(() => {
3054
3194
  if (scrollRafId) {
3055
3195
  ownerWindow?.cancelAnimationFrame(scrollRafId);
3056
3196
  }
@@ -3064,6 +3204,40 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
3064
3204
  toolbar.appendChild(menu);
3065
3205
  container.appendChild(toolbar);
3066
3206
  container.insertBefore(tocPanel, contentArea);
3207
+ pageToolbarTeardowns.set(container, () => {
3208
+ teardownCallbacks.forEach((teardownCallback) => {
3209
+ teardownCallback();
3210
+ });
3211
+ });
3212
+ }
3213
+
3214
+ // src/index.ts
3215
+ async function renderMarkdown(markdownText) {
3216
+ 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);
3217
+ const renderedHtml = String(await processor.process(markdownText));
3218
+ const sanitizedHtml = sanitizeHtmlWithDomPurify(renderedHtml);
3219
+ return highlightMarkdownCodeBlocks(sanitizedHtml);
3220
+ }
3221
+ function defaultScrollToHeading(targetElement) {
3222
+ targetElement.scrollIntoView({ behavior: "smooth", block: "start" });
3223
+ }
3224
+ function hydrateMarkdown(rootElement, options = {}) {
3225
+ hydrateMarkdownImages(rootElement);
3226
+ hydrateMarkdownVideos(rootElement);
3227
+ hydrateMermaidBlocks(rootElement);
3228
+ hydrateCodeBlocks(rootElement);
3229
+ hydrateToc(rootElement, options.scrollToHeading ?? defaultScrollToHeading);
3230
+ }
3231
+ function mountMarkdownToolbar(container, options = {}) {
3232
+ const ownerDocument = container.ownerDocument;
3233
+ if (!ownerDocument) {
3234
+ return;
3235
+ }
3236
+ if (typeof ownerDocument.defaultView?.scrollTo !== "function") {
3237
+ return;
3238
+ }
3239
+ applyContentWidth(ownerDocument, loadContentWidth());
3240
+ mountPageToolbar(ownerDocument, container, options.scrollToHeading ?? defaultScrollToHeading);
3067
3241
  }
3068
3242
  export {
3069
3243
  hydrateMarkdown,