@scribdown/markdown-renderer 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +5 -0
- package/dist/index.js +1774 -1520
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
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
|
|
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/
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
var
|
|
176
|
-
var
|
|
177
|
-
var
|
|
178
|
-
var
|
|
179
|
-
var
|
|
180
|
-
var
|
|
181
|
-
var
|
|
182
|
-
var
|
|
183
|
-
var
|
|
184
|
-
var
|
|
185
|
-
var
|
|
186
|
-
|
|
187
|
-
|
|
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 =
|
|
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:
|
|
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:
|
|
476
|
+
text: VIEWER_ZOOM_IN_TEXT
|
|
728
477
|
});
|
|
729
478
|
const resetButtonElement = createMarkdownMermaidViewerButton(ownerDocument, {
|
|
730
479
|
ariaLabel: "\u91CD\u7F6E\u7F29\u653E",
|
|
731
|
-
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:
|
|
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:
|
|
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,
|
|
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 =
|
|
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 +
|
|
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 -
|
|
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,
|
|
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 *
|
|
640
|
+
const rawDelta = -event.deltaY * VIEWER_WHEEL_ZOOM_FACTOR;
|
|
892
641
|
const zoomDelta = Math.max(
|
|
893
|
-
-
|
|
894
|
-
Math.min(
|
|
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 -
|
|
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 +
|
|
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,
|
|
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 =
|
|
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 >
|
|
760
|
+
normalizedZoom > VIEWER_DEFAULT_ZOOM
|
|
1012
761
|
);
|
|
1013
762
|
viewerState.zoomValueElement.textContent = `${Math.round(normalizedZoom * 100)}%`;
|
|
1014
|
-
viewerState.zoomOutButtonElement.disabled = normalizedZoom <=
|
|
1015
|
-
viewerState.zoomInButtonElement.disabled = normalizedZoom >=
|
|
1016
|
-
viewerState.resetButtonElement.disabled = normalizedZoom ===
|
|
763
|
+
viewerState.zoomOutButtonElement.disabled = normalizedZoom <= VIEWER_MIN_ZOOM;
|
|
764
|
+
viewerState.zoomInButtonElement.disabled = normalizedZoom >= VIEWER_MAX_ZOOM;
|
|
765
|
+
viewerState.resetButtonElement.disabled = normalizedZoom === VIEWER_DEFAULT_ZOOM;
|
|
1017
766
|
updateMarkdownMermaidViewerCanvasSize(viewerState);
|
|
1018
767
|
if (focalPoint) {
|
|
1019
768
|
applyMarkdownMermaidViewerFocalPoint(viewerState, focalPoint);
|
|
@@ -1045,11 +794,11 @@ function applyMarkdownMermaidViewerFocalPoint(viewerState, focalPoint) {
|
|
|
1045
794
|
}
|
|
1046
795
|
function updateMarkdownMermaidViewerCanvasSize(viewerState) {
|
|
1047
796
|
const viewportWidth = Math.max(
|
|
1048
|
-
viewerState.viewportElement.clientWidth *
|
|
797
|
+
viewerState.viewportElement.clientWidth * VIEWER_FIT_RATIO,
|
|
1049
798
|
1
|
|
1050
799
|
);
|
|
1051
800
|
const viewportHeight = Math.max(
|
|
1052
|
-
viewerState.viewportElement.clientHeight *
|
|
801
|
+
viewerState.viewportElement.clientHeight * VIEWER_FIT_RATIO,
|
|
1053
802
|
1
|
|
1054
803
|
);
|
|
1055
804
|
const naturalWidth = Math.max(viewerState.naturalWidth, 1);
|
|
@@ -1069,286 +818,453 @@ function updateMarkdownMermaidViewerCanvasSize(viewerState) {
|
|
|
1069
818
|
innerSvgElement.style.display = "block";
|
|
1070
819
|
}
|
|
1071
820
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
821
|
+
|
|
822
|
+
// src/code/mermaid.ts
|
|
823
|
+
var MERMAID_LANGUAGE_ID = "mermaid";
|
|
824
|
+
var MERMAID_HYDRATED_DATA_KEY = "scribdownMermaidHydrated";
|
|
825
|
+
var MERMAID_RENDER_STARTED_DATA_KEY = "scribdownMermaidRenderStarted";
|
|
826
|
+
var MERMAID_SOURCE_DATA_KEY = "scribdownMermaidSourceText";
|
|
827
|
+
var MERMAID_FALLBACK_DEFAULT_TEXT = "\u56FE\u8868\u6E32\u67D3\u5931\u8D25";
|
|
828
|
+
var MERMAID_RENDER_ID_PREFIX = "scribdown-mermaid-";
|
|
829
|
+
var mermaidRenderIdCounter = 0;
|
|
830
|
+
var MERMAID_FULLSCREEN_BUTTON_ARIA_LABEL = "\u5168\u5C4F\u67E5\u770B\u56FE\u8868";
|
|
831
|
+
var MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY = "scribdownMermaidFullscreenReady";
|
|
832
|
+
var MERMAID_VIEWER_SOURCE_DATA_KEY = "scribdownMermaidSource";
|
|
833
|
+
var mermaidLoaderPromise;
|
|
834
|
+
function hydrateMermaidBlocks(rootElement) {
|
|
835
|
+
const mermaidCodeElements = rootElement.querySelectorAll(
|
|
836
|
+
`pre > code.language-${MERMAID_LANGUAGE_ID}`
|
|
837
|
+
);
|
|
838
|
+
mermaidCodeElements.forEach((codeElement) => {
|
|
839
|
+
const preElement = codeElement.parentElement;
|
|
840
|
+
if (!preElement) {
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
if (preElement.dataset[CODE_BLOCK_HYDRATED_DATA_KEY] === "true") {
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
decorateMermaidBlock(preElement, codeElement);
|
|
847
|
+
});
|
|
848
|
+
kickOffPendingMermaidRenders(rootElement);
|
|
1074
849
|
}
|
|
1075
|
-
function
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
850
|
+
function kickOffPendingMermaidRenders(rootElement) {
|
|
851
|
+
const pendingFigures = rootElement.querySelectorAll(
|
|
852
|
+
`.${MERMAID_FIGURE_CLASS_NAME}.${MERMAID_FIGURE_LOADING_CLASS_NAME}`
|
|
853
|
+
);
|
|
854
|
+
pendingFigures.forEach((figureElement) => {
|
|
855
|
+
if (!figureElement.isConnected) {
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
if (figureElement.dataset[MERMAID_RENDER_STARTED_DATA_KEY] === "true") {
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
const canvasElement = figureElement.querySelector(
|
|
862
|
+
`.${MERMAID_CANVAS_CLASS_NAME}`
|
|
863
|
+
);
|
|
864
|
+
const mermaidSource = figureElement.dataset[MERMAID_SOURCE_DATA_KEY] ?? "";
|
|
865
|
+
if (!canvasElement || mermaidSource.length === 0) {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
figureElement.dataset[MERMAID_RENDER_STARTED_DATA_KEY] = "true";
|
|
869
|
+
void renderMermaidIntoCanvas(figureElement, canvasElement, mermaidSource);
|
|
870
|
+
});
|
|
1081
871
|
}
|
|
1082
|
-
function
|
|
1083
|
-
|
|
1084
|
-
if (!ownerDocument) {
|
|
872
|
+
function decorateMermaidBlock(preElement, codeElement) {
|
|
873
|
+
if (preElement.dataset[MERMAID_HYDRATED_DATA_KEY] === "true") {
|
|
1085
874
|
return;
|
|
1086
875
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
876
|
+
preElement.dataset[MERMAID_HYDRATED_DATA_KEY] = "true";
|
|
877
|
+
const ownerDocument = preElement.ownerDocument;
|
|
878
|
+
const mermaidSource = (codeElement.textContent ?? "").replace(/\n+$/u, "");
|
|
879
|
+
const figureElement = ownerDocument.createElement("figure");
|
|
880
|
+
figureElement.className = `${MERMAID_FIGURE_CLASS_NAME} ${MERMAID_FIGURE_LOADING_CLASS_NAME}`;
|
|
881
|
+
const chromeElement = ownerDocument.createElement("div");
|
|
882
|
+
chromeElement.className = MERMAID_CHROME_CLASS_NAME;
|
|
883
|
+
const labelElement = ownerDocument.createElement("span");
|
|
884
|
+
labelElement.className = MERMAID_LABEL_CLASS_NAME;
|
|
885
|
+
labelElement.textContent = MERMAID_LABEL_TEXT;
|
|
886
|
+
chromeElement.append(labelElement);
|
|
887
|
+
const bodyElement = ownerDocument.createElement("div");
|
|
888
|
+
bodyElement.className = MERMAID_BODY_CLASS_NAME;
|
|
889
|
+
const canvasElement = ownerDocument.createElement("div");
|
|
890
|
+
canvasElement.className = MERMAID_CANVAS_CLASS_NAME;
|
|
891
|
+
canvasElement.setAttribute("role", "img");
|
|
892
|
+
canvasElement.setAttribute("aria-label", MERMAID_LABEL_TEXT);
|
|
893
|
+
const fullscreenButtonElement = ownerDocument.createElement("button");
|
|
894
|
+
fullscreenButtonElement.type = "button";
|
|
895
|
+
fullscreenButtonElement.className = MERMAID_FULLSCREEN_BUTTON_CLASS_NAME;
|
|
896
|
+
fullscreenButtonElement.setAttribute("aria-label", MERMAID_FULLSCREEN_BUTTON_ARIA_LABEL);
|
|
897
|
+
fullscreenButtonElement.disabled = true;
|
|
898
|
+
fullscreenButtonElement.innerHTML = MERMAID_FULLSCREEN_ICON_SVG;
|
|
899
|
+
fullscreenButtonElement.addEventListener("click", handleMermaidFullscreenButtonClick);
|
|
900
|
+
bodyElement.append(canvasElement, fullscreenButtonElement);
|
|
901
|
+
figureElement.append(chromeElement, bodyElement);
|
|
902
|
+
const sourceLine = codeElement.getAttribute(SOURCE_LINE_DATA_ATTRIBUTE2);
|
|
903
|
+
if (sourceLine !== null) {
|
|
904
|
+
figureElement.setAttribute(SOURCE_LINE_DATA_ATTRIBUTE2, sourceLine);
|
|
1089
905
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
906
|
+
figureElement.dataset[MERMAID_SOURCE_DATA_KEY] = mermaidSource;
|
|
907
|
+
preElement.replaceWith(figureElement);
|
|
1092
908
|
}
|
|
1093
|
-
function
|
|
1094
|
-
if (
|
|
1095
|
-
return;
|
|
909
|
+
async function loadMermaid() {
|
|
910
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
911
|
+
return void 0;
|
|
1096
912
|
}
|
|
1097
|
-
if (
|
|
1098
|
-
|
|
913
|
+
if (!mermaidLoaderPromise) {
|
|
914
|
+
mermaidLoaderPromise = (async () => {
|
|
915
|
+
const mermaidModule = await import("mermaid");
|
|
916
|
+
const mermaidApi = mermaidModule.default;
|
|
917
|
+
mermaidApi.initialize({
|
|
918
|
+
startOnLoad: false,
|
|
919
|
+
securityLevel: "strict",
|
|
920
|
+
theme: "default",
|
|
921
|
+
flowchart: { useMaxWidth: false },
|
|
922
|
+
sequence: { useMaxWidth: false },
|
|
923
|
+
class: { useMaxWidth: false },
|
|
924
|
+
state: { useMaxWidth: false },
|
|
925
|
+
gantt: { useMaxWidth: false },
|
|
926
|
+
er: { useMaxWidth: false },
|
|
927
|
+
journey: { useMaxWidth: false },
|
|
928
|
+
pie: { useMaxWidth: false },
|
|
929
|
+
requirement: { useMaxWidth: false },
|
|
930
|
+
c4: { useMaxWidth: false },
|
|
931
|
+
mindmap: { useMaxWidth: false },
|
|
932
|
+
timeline: { useMaxWidth: false },
|
|
933
|
+
gitGraph: { useMaxWidth: false },
|
|
934
|
+
quadrantChart: { useMaxWidth: false },
|
|
935
|
+
xyChart: { useMaxWidth: false },
|
|
936
|
+
sankey: { useMaxWidth: false },
|
|
937
|
+
block: { useMaxWidth: false }
|
|
938
|
+
});
|
|
939
|
+
return mermaidApi;
|
|
940
|
+
})().catch((loadError) => {
|
|
941
|
+
mermaidLoaderPromise = void 0;
|
|
942
|
+
throw loadError;
|
|
943
|
+
});
|
|
1099
944
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
945
|
+
return mermaidLoaderPromise;
|
|
946
|
+
}
|
|
947
|
+
async function renderMermaidIntoCanvas(figureElement, canvasElement, mermaidSource) {
|
|
948
|
+
try {
|
|
949
|
+
const mermaidApi = await loadMermaid();
|
|
950
|
+
if (!mermaidApi) {
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
mermaidRenderIdCounter += 1;
|
|
954
|
+
const renderId = `${MERMAID_RENDER_ID_PREFIX}${mermaidRenderIdCounter}`;
|
|
955
|
+
const { svg, bindFunctions } = await mermaidApi.render(renderId, mermaidSource);
|
|
956
|
+
canvasElement.innerHTML = svg;
|
|
957
|
+
bindFunctions?.(canvasElement);
|
|
958
|
+
figureElement.classList.remove(MERMAID_FIGURE_LOADING_CLASS_NAME);
|
|
959
|
+
figureElement.classList.add(MERMAID_FIGURE_LOADED_CLASS_NAME);
|
|
960
|
+
figureElement.dataset[MERMAID_VIEWER_SOURCE_DATA_KEY] = svg;
|
|
961
|
+
figureElement.dataset[MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY] = "true";
|
|
962
|
+
const fullscreenButtonElement = figureElement.querySelector(
|
|
963
|
+
`.${MERMAID_FULLSCREEN_BUTTON_CLASS_NAME}`
|
|
964
|
+
);
|
|
965
|
+
if (fullscreenButtonElement) {
|
|
966
|
+
fullscreenButtonElement.disabled = false;
|
|
967
|
+
}
|
|
968
|
+
} catch (renderError) {
|
|
969
|
+
showMermaidFallback(figureElement, canvasElement, mermaidSource, renderError);
|
|
1103
970
|
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
const
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
const
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
971
|
+
}
|
|
972
|
+
function showMermaidFallback(figureElement, canvasElement, mermaidSource, renderError) {
|
|
973
|
+
const ownerDocument = figureElement.ownerDocument;
|
|
974
|
+
figureElement.classList.remove(MERMAID_FIGURE_LOADING_CLASS_NAME);
|
|
975
|
+
figureElement.classList.add(MERMAID_FIGURE_FAILED_CLASS_NAME);
|
|
976
|
+
const fullscreenButtonElement = figureElement.querySelector(
|
|
977
|
+
`.${MERMAID_FULLSCREEN_BUTTON_CLASS_NAME}`
|
|
978
|
+
);
|
|
979
|
+
fullscreenButtonElement?.remove();
|
|
980
|
+
canvasElement.replaceChildren();
|
|
981
|
+
cleanupOrphanMermaidNodes(ownerDocument);
|
|
982
|
+
const fallbackElement = ownerDocument.createElement("div");
|
|
983
|
+
fallbackElement.className = MERMAID_FALLBACK_CLASS_NAME;
|
|
984
|
+
const iconElement = ownerDocument.createElement("span");
|
|
985
|
+
iconElement.className = MERMAID_FALLBACK_ICON_CLASS_NAME;
|
|
986
|
+
iconElement.setAttribute("aria-hidden", "true");
|
|
987
|
+
fallbackElement.append(iconElement);
|
|
988
|
+
const textElement = ownerDocument.createElement("p");
|
|
989
|
+
textElement.className = MERMAID_FALLBACK_TEXT_CLASS_NAME;
|
|
990
|
+
textElement.textContent = MERMAID_FALLBACK_DEFAULT_TEXT;
|
|
991
|
+
fallbackElement.append(textElement);
|
|
992
|
+
const errorMessage = extractErrorMessage(renderError);
|
|
993
|
+
if (errorMessage) {
|
|
994
|
+
const messageElement = ownerDocument.createElement("p");
|
|
995
|
+
messageElement.className = MERMAID_FALLBACK_SOURCE_CLASS_NAME;
|
|
996
|
+
messageElement.textContent = errorMessage;
|
|
997
|
+
fallbackElement.append(messageElement);
|
|
998
|
+
}
|
|
999
|
+
if (mermaidSource.length > 0) {
|
|
1000
|
+
const sourceElement = ownerDocument.createElement("pre");
|
|
1001
|
+
sourceElement.className = MERMAID_FALLBACK_SOURCE_CLASS_NAME;
|
|
1002
|
+
sourceElement.textContent = mermaidSource;
|
|
1003
|
+
fallbackElement.append(sourceElement);
|
|
1004
|
+
}
|
|
1005
|
+
canvasElement.replaceWith(fallbackElement);
|
|
1006
|
+
}
|
|
1007
|
+
function extractErrorMessage(renderError) {
|
|
1008
|
+
if (renderError instanceof Error) {
|
|
1009
|
+
return renderError.message;
|
|
1131
1010
|
}
|
|
1132
|
-
|
|
1133
|
-
|
|
1011
|
+
if (typeof renderError === "string") {
|
|
1012
|
+
return renderError;
|
|
1013
|
+
}
|
|
1014
|
+
return "";
|
|
1134
1015
|
}
|
|
1135
|
-
function
|
|
1136
|
-
const
|
|
1137
|
-
|
|
1016
|
+
function cleanupOrphanMermaidNodes(ownerDocument) {
|
|
1017
|
+
const orphanNodes = ownerDocument.querySelectorAll(
|
|
1018
|
+
`body > [id^="${MERMAID_RENDER_ID_PREFIX}"], body > div[id^="d"][id*="mermaid"]`
|
|
1138
1019
|
);
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
}
|
|
1142
|
-
return languageClassName.slice("language-".length).toLowerCase();
|
|
1020
|
+
orphanNodes.forEach((orphanNode) => {
|
|
1021
|
+
orphanNode.remove();
|
|
1022
|
+
});
|
|
1143
1023
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1024
|
+
var MERMAID_FULLSCREEN_ICON_SVG = '<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" focusable="false"><path d="M4 9 V4 H9 M15 4 H20 V9 M20 15 V20 H15 M9 20 H4 V15" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
1025
|
+
function handleMermaidFullscreenButtonClick(event) {
|
|
1026
|
+
const buttonElement = event.currentTarget;
|
|
1027
|
+
const figureElement = buttonElement.closest(`.${MERMAID_FIGURE_CLASS_NAME}`);
|
|
1028
|
+
if (!figureElement) {
|
|
1029
|
+
return;
|
|
1147
1030
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
return mappedLabel;
|
|
1031
|
+
if (figureElement.dataset[MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY] !== "true") {
|
|
1032
|
+
return;
|
|
1151
1033
|
}
|
|
1152
|
-
|
|
1034
|
+
const svgSource = figureElement.dataset[MERMAID_VIEWER_SOURCE_DATA_KEY] ?? "";
|
|
1035
|
+
if (svgSource.length === 0) {
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
event.preventDefault();
|
|
1039
|
+
event.stopPropagation();
|
|
1040
|
+
openMarkdownMermaidViewer(figureElement.ownerDocument, svgSource);
|
|
1153
1041
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1042
|
+
|
|
1043
|
+
// src/code/code-highlight.ts
|
|
1044
|
+
var CODE_HIGHLIGHTER_THEME = "github-light";
|
|
1045
|
+
var codeHighlighterPromise;
|
|
1046
|
+
var codeHighlighterLazyLoadPromises = /* @__PURE__ */ new Map();
|
|
1047
|
+
var CODE_BLOCK_HIGHLIGHT_PATTERN = /<pre([^>]*)><code class="language-([\w-]+)"([^>]*)>([\s\S]*?)<\/code><\/pre>/g;
|
|
1048
|
+
var SHIKI_CODE_INNER_PATTERN = /<code[^>]*>([\s\S]*?)<\/code>/u;
|
|
1049
|
+
async function getCodeHighlighter() {
|
|
1050
|
+
if (!codeHighlighterPromise) {
|
|
1051
|
+
codeHighlighterPromise = createHighlighterCore({
|
|
1052
|
+
themes: [githubLightTheme],
|
|
1053
|
+
// eager loaders 通过动态 import 拆为独立 chunk,初始化时并发拉取;
|
|
1054
|
+
// 主 bundle 不再内联 grammar JSON,体积更小,多个 grammar 也可并行加载。
|
|
1055
|
+
langs: CODE_HIGHLIGHTER_EAGER_LOADERS,
|
|
1056
|
+
// oniguruma 引擎 + wasm;shiki/wasm 内部走 base64 内联,扩展环境无需额外资源声明。
|
|
1057
|
+
engine: createOnigurumaEngine(import("shiki/wasm"))
|
|
1163
1058
|
});
|
|
1164
|
-
codeElement.replaceChildren(...shikiLineNodes);
|
|
1165
|
-
return shikiLineNodes.length;
|
|
1166
|
-
}
|
|
1167
|
-
const codeText = codeElement.textContent ?? "";
|
|
1168
|
-
const trimmedCodeText = codeText.replace(/\n+$/u, "");
|
|
1169
|
-
const codeLines = trimmedCodeText.split("\n");
|
|
1170
|
-
while (codeElement.firstChild) {
|
|
1171
|
-
codeElement.removeChild(codeElement.firstChild);
|
|
1172
1059
|
}
|
|
1173
|
-
|
|
1174
|
-
const lineElement = ownerDocument.createElement("span");
|
|
1175
|
-
lineElement.className = CODE_BLOCK_LINE_CLASS_NAME;
|
|
1176
|
-
if (lineText.length === 0) {
|
|
1177
|
-
lineElement.textContent = CODE_BLOCK_EMPTY_LINE_PLACEHOLDER;
|
|
1178
|
-
} else {
|
|
1179
|
-
lineElement.textContent = lineText;
|
|
1180
|
-
}
|
|
1181
|
-
codeElement.append(lineElement);
|
|
1182
|
-
});
|
|
1183
|
-
return codeLines.length;
|
|
1060
|
+
return codeHighlighterPromise;
|
|
1184
1061
|
}
|
|
1185
|
-
function
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
for (let lineIndex = 0; lineIndex < lineCount; lineIndex += 1) {
|
|
1189
|
-
const lineNumberText = String(lineIndex + 1);
|
|
1190
|
-
const lineNumberElement = ownerDocument.createElement("span");
|
|
1191
|
-
lineNumberElement.className = CODE_BLOCK_GUTTER_LINE_CLASS_NAME;
|
|
1192
|
-
lineNumberElement.textContent = lineNumberText;
|
|
1193
|
-
gutterElement.append(lineNumberElement);
|
|
1062
|
+
async function highlightMarkdownCodeBlocks(html) {
|
|
1063
|
+
if (!CODE_BLOCK_HIGHLIGHT_PATTERN.test(html)) {
|
|
1064
|
+
return html;
|
|
1194
1065
|
}
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
const checkIconElement = ownerDocument.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1215
|
-
checkIconElement.setAttribute(
|
|
1216
|
-
"class",
|
|
1217
|
-
`${CODE_BLOCK_COPY_ICON_CLASS_NAME} ${CODE_BLOCK_COPY_ICON_CHECK_CLASS_NAME}`
|
|
1218
|
-
);
|
|
1219
|
-
checkIconElement.setAttribute("viewBox", "0 0 20 20");
|
|
1220
|
-
checkIconElement.setAttribute("aria-hidden", "true");
|
|
1221
|
-
checkIconElement.setAttribute("focusable", "false");
|
|
1222
|
-
checkIconElement.innerHTML = CODE_BLOCK_COPY_ICON_CHECK_PATHS;
|
|
1223
|
-
copyButtonElement.append(checkIconElement);
|
|
1224
|
-
return copyButtonElement;
|
|
1225
|
-
}
|
|
1226
|
-
function handleCodeBlockCopyClick(event) {
|
|
1227
|
-
const copyButtonElement = event.currentTarget;
|
|
1228
|
-
const codeText = copyButtonElement.dataset.scribdownCodeSource ?? "";
|
|
1229
|
-
void writeCodeBlockTextToClipboard(codeText).then((isSucceeded) => {
|
|
1230
|
-
if (isSucceeded) {
|
|
1231
|
-
flashCodeBlockCopyButton(copyButtonElement);
|
|
1232
|
-
}
|
|
1233
|
-
});
|
|
1066
|
+
CODE_BLOCK_HIGHLIGHT_PATTERN.lastIndex = 0;
|
|
1067
|
+
const highlighter = await getCodeHighlighter();
|
|
1068
|
+
let resultHtml = "";
|
|
1069
|
+
let lastIndex = 0;
|
|
1070
|
+
for (const match of html.matchAll(CODE_BLOCK_HIGHLIGHT_PATTERN)) {
|
|
1071
|
+
const [matched, preAttributes, rawLanguage, codeAttributes, encodedCode] = match;
|
|
1072
|
+
const matchStart = match.index ?? 0;
|
|
1073
|
+
resultHtml += html.slice(lastIndex, matchStart);
|
|
1074
|
+
resultHtml += await highlightSingleCodeBlock(highlighter, {
|
|
1075
|
+
rawLanguage,
|
|
1076
|
+
encodedCode,
|
|
1077
|
+
matchedHtml: matched,
|
|
1078
|
+
preAttributes,
|
|
1079
|
+
codeAttributes
|
|
1080
|
+
});
|
|
1081
|
+
lastIndex = matchStart + matched.length;
|
|
1082
|
+
}
|
|
1083
|
+
resultHtml += html.slice(lastIndex);
|
|
1084
|
+
return resultHtml;
|
|
1234
1085
|
}
|
|
1235
|
-
async function
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
} catch {
|
|
1241
|
-
}
|
|
1086
|
+
async function highlightSingleCodeBlock(highlighter, input) {
|
|
1087
|
+
const { rawLanguage, encodedCode, matchedHtml, preAttributes, codeAttributes } = input;
|
|
1088
|
+
const normalizedLanguage = rawLanguage.toLowerCase();
|
|
1089
|
+
if (normalizedLanguage === MERMAID_LANGUAGE_ID) {
|
|
1090
|
+
return matchedHtml;
|
|
1242
1091
|
}
|
|
1243
|
-
|
|
1244
|
-
|
|
1092
|
+
const resolvedLanguage = await ensureHighlighterLanguage(highlighter, normalizedLanguage);
|
|
1093
|
+
const codeText = decodeHtmlEntities(encodedCode);
|
|
1094
|
+
try {
|
|
1095
|
+
const highlightedHtml = highlighter.codeToHtml(codeText, {
|
|
1096
|
+
lang: resolvedLanguage,
|
|
1097
|
+
theme: CODE_HIGHLIGHTER_THEME
|
|
1098
|
+
});
|
|
1099
|
+
const innerMatch = highlightedHtml.match(SHIKI_CODE_INNER_PATTERN);
|
|
1100
|
+
const innerHtml = innerMatch?.[1] ?? encodedCode;
|
|
1101
|
+
return `<pre${preAttributes}><code class="language-${rawLanguage}"${codeAttributes}>${innerHtml}</code></pre>`;
|
|
1102
|
+
} catch {
|
|
1103
|
+
return matchedHtml;
|
|
1245
1104
|
}
|
|
1246
|
-
const fallbackTextarea = document.createElement("textarea");
|
|
1247
|
-
fallbackTextarea.value = codeText;
|
|
1248
|
-
fallbackTextarea.setAttribute("readonly", "");
|
|
1249
|
-
fallbackTextarea.style.position = "fixed";
|
|
1250
|
-
fallbackTextarea.style.opacity = "0";
|
|
1251
|
-
fallbackTextarea.style.pointerEvents = "none";
|
|
1252
|
-
document.body.append(fallbackTextarea);
|
|
1253
|
-
fallbackTextarea.select();
|
|
1254
|
-
const isSucceeded = document.execCommand("copy");
|
|
1255
|
-
fallbackTextarea.remove();
|
|
1256
|
-
return isSucceeded;
|
|
1257
|
-
}
|
|
1258
|
-
function flashCodeBlockCopyButton(copyButtonElement) {
|
|
1259
|
-
copyButtonElement.dataset.scribdownCodeCopied = "true";
|
|
1260
|
-
copyButtonElement.setAttribute("aria-label", CODE_BLOCK_COPY_ARIA_LABEL_COPIED);
|
|
1261
|
-
window.setTimeout(() => {
|
|
1262
|
-
delete copyButtonElement.dataset.scribdownCodeCopied;
|
|
1263
|
-
copyButtonElement.setAttribute("aria-label", CODE_BLOCK_COPY_ARIA_LABEL);
|
|
1264
|
-
}, CODE_BLOCK_COPY_RESTORE_DELAY_MS);
|
|
1265
1105
|
}
|
|
1266
|
-
function
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
return;
|
|
1106
|
+
async function ensureHighlighterLanguage(highlighter, normalizedLanguage) {
|
|
1107
|
+
if (normalizedLanguage.length === 0) {
|
|
1108
|
+
return "text";
|
|
1270
1109
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1110
|
+
if (highlighter.getLoadedLanguages().includes(normalizedLanguage)) {
|
|
1111
|
+
return normalizedLanguage;
|
|
1112
|
+
}
|
|
1113
|
+
const lazyLoader = CODE_HIGHLIGHTER_LAZY_LANGS[normalizedLanguage];
|
|
1114
|
+
if (!lazyLoader) {
|
|
1115
|
+
return "text";
|
|
1116
|
+
}
|
|
1117
|
+
let pending = codeHighlighterLazyLoadPromises.get(normalizedLanguage);
|
|
1118
|
+
if (!pending) {
|
|
1119
|
+
pending = (async () => {
|
|
1120
|
+
const grammarModule = await lazyLoader();
|
|
1121
|
+
await highlighter.loadLanguage(grammarModule.default);
|
|
1122
|
+
})();
|
|
1123
|
+
codeHighlighterLazyLoadPromises.set(normalizedLanguage, pending);
|
|
1124
|
+
}
|
|
1125
|
+
try {
|
|
1126
|
+
await pending;
|
|
1127
|
+
return normalizedLanguage;
|
|
1128
|
+
} catch {
|
|
1129
|
+
codeHighlighterLazyLoadPromises.delete(normalizedLanguage);
|
|
1130
|
+
return "text";
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
var HTML_NAMED_ENTITIES = {
|
|
1134
|
+
lt: "<",
|
|
1135
|
+
gt: ">",
|
|
1136
|
+
quot: '"',
|
|
1137
|
+
apos: "'",
|
|
1138
|
+
amp: "&"
|
|
1139
|
+
};
|
|
1140
|
+
var HTML_ENTITY_PATTERN = /&(?:(lt|gt|quot|apos|amp)|#x([0-9a-fA-F]+)|#(\d+));/g;
|
|
1141
|
+
function decodeHtmlEntities(encodedText) {
|
|
1142
|
+
return encodedText.replace(
|
|
1143
|
+
HTML_ENTITY_PATTERN,
|
|
1144
|
+
(matched, namedEntity, hexCodePoint, decimalCodePoint) => {
|
|
1145
|
+
if (namedEntity) {
|
|
1146
|
+
return HTML_NAMED_ENTITIES[namedEntity] ?? matched;
|
|
1147
|
+
}
|
|
1148
|
+
if (hexCodePoint) {
|
|
1149
|
+
return safeFromCodePoint(parseInt(hexCodePoint, 16), matched);
|
|
1150
|
+
}
|
|
1151
|
+
if (decimalCodePoint) {
|
|
1152
|
+
return safeFromCodePoint(parseInt(decimalCodePoint, 10), matched);
|
|
1153
|
+
}
|
|
1154
|
+
return matched;
|
|
1155
|
+
}
|
|
1156
|
+
);
|
|
1275
1157
|
}
|
|
1276
|
-
function
|
|
1277
|
-
|
|
1278
|
-
|
|
1158
|
+
function safeFromCodePoint(codePoint, fallback) {
|
|
1159
|
+
if (!Number.isFinite(codePoint) || codePoint < 0 || codePoint > 1114111) {
|
|
1160
|
+
return fallback;
|
|
1161
|
+
}
|
|
1162
|
+
try {
|
|
1163
|
+
return String.fromCodePoint(codePoint);
|
|
1164
|
+
} catch {
|
|
1165
|
+
return fallback;
|
|
1166
|
+
}
|
|
1279
1167
|
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1168
|
+
|
|
1169
|
+
// src/core/source-line.ts
|
|
1170
|
+
import { SOURCE_LINE_DATA_ATTRIBUTE as SOURCE_LINE_DATA_ATTRIBUTE3 } from "@scribdown/shared";
|
|
1171
|
+
var SOURCE_LINE_HAST_PROPERTY = SOURCE_LINE_DATA_ATTRIBUTE3.replace(
|
|
1172
|
+
/-([a-z])/gu,
|
|
1173
|
+
(_match, letter) => letter.toUpperCase()
|
|
1174
|
+
);
|
|
1175
|
+
var BLOCK_CONTAINER_NODE_TYPES = /* @__PURE__ */ new Set(["list", "listItem", "blockquote"]);
|
|
1176
|
+
function remarkSourceLine() {
|
|
1177
|
+
return (tree) => {
|
|
1178
|
+
const blockNodes = tree.children ?? [];
|
|
1179
|
+
blockNodes.forEach((blockNode) => {
|
|
1180
|
+
annotateSourceLine(blockNode);
|
|
1181
|
+
annotateNestedBlockSourceLines(blockNode);
|
|
1182
|
+
});
|
|
1183
|
+
};
|
|
1283
1184
|
}
|
|
1284
|
-
function
|
|
1285
|
-
const
|
|
1286
|
-
if (
|
|
1185
|
+
function annotateSourceLine(node) {
|
|
1186
|
+
const startLine = node.position?.start.line;
|
|
1187
|
+
if (typeof startLine !== "number") {
|
|
1287
1188
|
return;
|
|
1288
1189
|
}
|
|
1289
|
-
const
|
|
1290
|
-
const
|
|
1291
|
-
|
|
1292
|
-
|
|
1190
|
+
const nodeData = node.data ?? {};
|
|
1191
|
+
const hProperties = nodeData.hProperties ?? {};
|
|
1192
|
+
hProperties[SOURCE_LINE_HAST_PROPERTY] = startLine;
|
|
1193
|
+
nodeData.hProperties = hProperties;
|
|
1194
|
+
node.data = nodeData;
|
|
1293
1195
|
}
|
|
1294
|
-
function
|
|
1295
|
-
|
|
1296
|
-
if (videoElement.dataset[VIDEO_HYDRATED_DATA_KEY] === "true") {
|
|
1196
|
+
function annotateNestedBlockSourceLines(node) {
|
|
1197
|
+
if (!BLOCK_CONTAINER_NODE_TYPES.has(node.type)) {
|
|
1297
1198
|
return;
|
|
1298
1199
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
sourceElement.addEventListener("error", handleMarkdownVideoSourceError);
|
|
1200
|
+
const childNodes = node.children ?? [];
|
|
1201
|
+
childNodes.forEach((childNode) => {
|
|
1202
|
+
annotateSourceLine(childNode);
|
|
1203
|
+
annotateNestedBlockSourceLines(childNode);
|
|
1304
1204
|
});
|
|
1305
1205
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1206
|
+
|
|
1207
|
+
// src/media/images.ts
|
|
1208
|
+
import {
|
|
1209
|
+
IMAGE_CAPTION_CLASS_NAME,
|
|
1210
|
+
IMAGE_ELEMENT_CLASS_NAME,
|
|
1211
|
+
IMAGE_FALLBACK_CLASS_NAME,
|
|
1212
|
+
IMAGE_FALLBACK_ICON_CLASS_NAME,
|
|
1213
|
+
IMAGE_FALLBACK_SOURCE_CLASS_NAME,
|
|
1214
|
+
IMAGE_FALLBACK_TEXT_CLASS_NAME,
|
|
1215
|
+
IMAGE_FIGURE_CLASS_NAME,
|
|
1216
|
+
IMAGE_FRAME_CLASS_NAME,
|
|
1217
|
+
IMAGE_FRAME_FAILED_CLASS_NAME,
|
|
1218
|
+
IMAGE_FRAME_LOADED_CLASS_NAME
|
|
1219
|
+
} from "@scribdown/shared";
|
|
1220
|
+
|
|
1221
|
+
// src/core/ast.ts
|
|
1222
|
+
function visitMarkdownNode(node, visitor) {
|
|
1223
|
+
visitor(node);
|
|
1224
|
+
if (!node.children) {
|
|
1320
1225
|
return;
|
|
1321
1226
|
}
|
|
1322
|
-
|
|
1323
|
-
|
|
1227
|
+
const childNodes = node.children;
|
|
1228
|
+
for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {
|
|
1229
|
+
visitMarkdownNode(childNodes[childIndex], visitor);
|
|
1324
1230
|
}
|
|
1325
|
-
markMarkdownVideoFailed(videoElement);
|
|
1326
1231
|
}
|
|
1327
|
-
function
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
);
|
|
1331
|
-
if (!frameElement) {
|
|
1332
|
-
return;
|
|
1232
|
+
function extractNodeText(node) {
|
|
1233
|
+
if (node.type === "text" || node.type === "inlineCode") {
|
|
1234
|
+
return node.value ?? "";
|
|
1333
1235
|
}
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
frameElement.classList.toggle(VIDEO_FRAME_FAILED_CLASS_NAME, isFailed);
|
|
1337
|
-
frameElement.classList.toggle(
|
|
1338
|
-
VIDEO_FRAME_LOADED_CLASS_NAME,
|
|
1339
|
-
isLoaded && !isFailed
|
|
1340
|
-
);
|
|
1341
|
-
}
|
|
1342
|
-
function markMarkdownVideoFailed(videoElement) {
|
|
1343
|
-
const frameElement = videoElement.closest(
|
|
1344
|
-
`.${VIDEO_FRAME_CLASS_NAME}`
|
|
1345
|
-
);
|
|
1346
|
-
if (!frameElement) {
|
|
1347
|
-
return;
|
|
1236
|
+
if (isImageNode(node)) {
|
|
1237
|
+
return node.alt ?? "";
|
|
1348
1238
|
}
|
|
1349
|
-
|
|
1350
|
-
|
|
1239
|
+
if (!node.children) {
|
|
1240
|
+
return "";
|
|
1241
|
+
}
|
|
1242
|
+
return node.children.map(extractNodeText).join("");
|
|
1351
1243
|
}
|
|
1244
|
+
function isImageNode(node) {
|
|
1245
|
+
return node.type === "image" || node.type === "imageReference";
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// src/media/image-viewer.ts
|
|
1249
|
+
import {
|
|
1250
|
+
IMAGE_VIEWER_BUTTON_CLASS_NAME,
|
|
1251
|
+
IMAGE_VIEWER_CAPTION_CLASS_NAME,
|
|
1252
|
+
IMAGE_VIEWER_CAPTION_GROUP_CLASS_NAME,
|
|
1253
|
+
IMAGE_VIEWER_CHROME_CLASS_NAME,
|
|
1254
|
+
IMAGE_VIEWER_CLOSE_BUTTON_CLASS_NAME,
|
|
1255
|
+
IMAGE_VIEWER_CONTROLS_CLASS_NAME,
|
|
1256
|
+
IMAGE_VIEWER_DIALOG_CLASS_NAME,
|
|
1257
|
+
IMAGE_VIEWER_DRAGGING_CLASS_NAME,
|
|
1258
|
+
IMAGE_VIEWER_HINT_CLASS_NAME,
|
|
1259
|
+
IMAGE_VIEWER_IMAGE_CLASS_NAME,
|
|
1260
|
+
IMAGE_VIEWER_VIEWPORT_CLASS_NAME,
|
|
1261
|
+
IMAGE_VIEWER_ZOOMED_CLASS_NAME,
|
|
1262
|
+
IMAGE_VIEWER_ZOOM_VALUE_CLASS_NAME
|
|
1263
|
+
} from "@scribdown/shared";
|
|
1264
|
+
var IMAGE_VIEWER_HINT_TEXT = "\u5FEB\u6377\u952E\uFF1A+/= \u653E\u5927 \xB7 - \u7F29\u5C0F \xB7 0 \u91CD\u7F6E \xB7 Esc \u5173\u95ED \xB7 \u9F20\u6807\u62D6\u62FD\u53EF\u5E73\u79FB \xB7 Ctrl/\u2318 + \u6EDA\u8F6E\u7F29\u653E";
|
|
1265
|
+
var IMAGE_VIEWER_FALLBACK_LABEL = "\u67E5\u770B\u5168\u56FE";
|
|
1266
|
+
var imageViewerStateByDocument = /* @__PURE__ */ new WeakMap();
|
|
1267
|
+
var imageViewerStateByDialogElement = /* @__PURE__ */ new WeakMap();
|
|
1352
1268
|
function bindMarkdownImageViewer(imageElement) {
|
|
1353
1269
|
const linkElement = imageElement.closest("a[href]");
|
|
1354
1270
|
if (linkElement) {
|
|
@@ -1402,7 +1318,7 @@ function openMarkdownImageViewer(sourceImageElement) {
|
|
|
1402
1318
|
);
|
|
1403
1319
|
viewerState.viewportElement.scrollLeft = 0;
|
|
1404
1320
|
viewerState.viewportElement.scrollTop = 0;
|
|
1405
|
-
viewerState.zoomValue =
|
|
1321
|
+
viewerState.zoomValue = VIEWER_DEFAULT_ZOOM;
|
|
1406
1322
|
showMarkdownImageViewerDialog(viewerState);
|
|
1407
1323
|
requestMarkdownImageViewerLayout(viewerState);
|
|
1408
1324
|
}
|
|
@@ -1424,21 +1340,21 @@ function createMarkdownImageViewerState(ownerDocument) {
|
|
|
1424
1340
|
const controlsElement = ownerDocument.createElement("div");
|
|
1425
1341
|
const zoomOutButtonElement = createMarkdownImageViewerButton(ownerDocument, {
|
|
1426
1342
|
ariaLabel: "\u7F29\u5C0F\u56FE\u7247",
|
|
1427
|
-
text:
|
|
1343
|
+
text: VIEWER_ZOOM_OUT_TEXT
|
|
1428
1344
|
});
|
|
1429
1345
|
const zoomValueElement = ownerDocument.createElement("span");
|
|
1430
1346
|
const zoomInButtonElement = createMarkdownImageViewerButton(ownerDocument, {
|
|
1431
1347
|
ariaLabel: "\u653E\u5927\u56FE\u7247",
|
|
1432
|
-
text:
|
|
1348
|
+
text: VIEWER_ZOOM_IN_TEXT
|
|
1433
1349
|
});
|
|
1434
1350
|
const resetButtonElement = createMarkdownImageViewerButton(ownerDocument, {
|
|
1435
1351
|
ariaLabel: "\u91CD\u7F6E\u7F29\u653E",
|
|
1436
|
-
text:
|
|
1352
|
+
text: VIEWER_RESET_TEXT
|
|
1437
1353
|
});
|
|
1438
1354
|
const closeButtonElement = createMarkdownImageViewerButton(ownerDocument, {
|
|
1439
1355
|
ariaLabel: "\u5173\u95ED\u5168\u56FE\u67E5\u770B",
|
|
1440
1356
|
className: IMAGE_VIEWER_CLOSE_BUTTON_CLASS_NAME,
|
|
1441
|
-
text:
|
|
1357
|
+
text: VIEWER_CLOSE_TEXT
|
|
1442
1358
|
});
|
|
1443
1359
|
const viewportElement = ownerDocument.createElement("div");
|
|
1444
1360
|
const imageElement = ownerDocument.createElement("img");
|
|
@@ -1459,7 +1375,7 @@ function createMarkdownImageViewerState(ownerDocument) {
|
|
|
1459
1375
|
viewportElement,
|
|
1460
1376
|
zoomInButtonElement,
|
|
1461
1377
|
zoomOutButtonElement,
|
|
1462
|
-
zoomValue:
|
|
1378
|
+
zoomValue: VIEWER_DEFAULT_ZOOM,
|
|
1463
1379
|
zoomValueElement
|
|
1464
1380
|
};
|
|
1465
1381
|
const ownerWindow = ownerDocument.defaultView;
|
|
@@ -1506,7 +1422,7 @@ function createMarkdownImageViewerState(ownerDocument) {
|
|
|
1506
1422
|
if (ownerWindow) {
|
|
1507
1423
|
ownerWindow.addEventListener("resize", handleMarkdownImageViewerWindowResize);
|
|
1508
1424
|
}
|
|
1509
|
-
updateMarkdownImageViewerZoom(viewerState,
|
|
1425
|
+
updateMarkdownImageViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);
|
|
1510
1426
|
return viewerState;
|
|
1511
1427
|
}
|
|
1512
1428
|
function createMarkdownImageViewerButton(ownerDocument, options) {
|
|
@@ -1555,274 +1471,670 @@ function handleMarkdownImageViewerClose(event) {
|
|
|
1555
1471
|
if (!viewerState) {
|
|
1556
1472
|
return;
|
|
1557
1473
|
}
|
|
1558
|
-
resetMarkdownImageViewerAfterClose(viewerState);
|
|
1474
|
+
resetMarkdownImageViewerAfterClose(viewerState);
|
|
1475
|
+
}
|
|
1476
|
+
function handleMarkdownImageViewerKeyDown(event) {
|
|
1477
|
+
const viewerState = getMarkdownImageViewerStateFromEvent(event);
|
|
1478
|
+
if (!viewerState) {
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
if (event.key === "+" || event.key === "=") {
|
|
1482
|
+
event.preventDefault();
|
|
1483
|
+
updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + VIEWER_ZOOM_STEP);
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
if (event.key === "-") {
|
|
1487
|
+
event.preventDefault();
|
|
1488
|
+
updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue - VIEWER_ZOOM_STEP);
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
if (event.key === "0") {
|
|
1492
|
+
event.preventDefault();
|
|
1493
|
+
updateMarkdownImageViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1496
|
+
if (event.key === "Escape") {
|
|
1497
|
+
event.preventDefault();
|
|
1498
|
+
closeMarkdownImageViewer(viewerState);
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
function handleMarkdownImageViewerWheel(event) {
|
|
1502
|
+
if (!event.ctrlKey && !event.metaKey) {
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
const viewerState = getMarkdownImageViewerStateFromEvent(event);
|
|
1506
|
+
if (!viewerState) {
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
const rawDelta = -event.deltaY * VIEWER_WHEEL_ZOOM_FACTOR;
|
|
1510
|
+
const zoomDelta = Math.max(
|
|
1511
|
+
-VIEWER_WHEEL_ZOOM_MAX_DELTA,
|
|
1512
|
+
Math.min(VIEWER_WHEEL_ZOOM_MAX_DELTA, rawDelta)
|
|
1513
|
+
);
|
|
1514
|
+
event.preventDefault();
|
|
1515
|
+
updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + zoomDelta, {
|
|
1516
|
+
x: event.clientX,
|
|
1517
|
+
y: event.clientY
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1520
|
+
function handleMarkdownImageViewerPointerDown(event) {
|
|
1521
|
+
if (event.button !== 0) {
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
const viewportElement = event.currentTarget;
|
|
1525
|
+
const viewerState = getMarkdownImageViewerStateFromEvent(event);
|
|
1526
|
+
if (!viewerState) {
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
const canScrollHorizontally = viewportElement.scrollWidth > viewportElement.clientWidth;
|
|
1530
|
+
const canScrollVertically = viewportElement.scrollHeight > viewportElement.clientHeight;
|
|
1531
|
+
if (!canScrollHorizontally && !canScrollVertically) {
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
viewerState.isDragging = true;
|
|
1535
|
+
viewerState.dragStartClientX = event.clientX;
|
|
1536
|
+
viewerState.dragStartClientY = event.clientY;
|
|
1537
|
+
viewerState.dragStartScrollLeft = viewportElement.scrollLeft;
|
|
1538
|
+
viewerState.dragStartScrollTop = viewportElement.scrollTop;
|
|
1539
|
+
viewerState.dialogElement.classList.add(IMAGE_VIEWER_DRAGGING_CLASS_NAME);
|
|
1540
|
+
if (typeof viewportElement.setPointerCapture === "function") {
|
|
1541
|
+
viewportElement.setPointerCapture(event.pointerId);
|
|
1542
|
+
}
|
|
1543
|
+
event.preventDefault();
|
|
1544
|
+
}
|
|
1545
|
+
function handleMarkdownImageViewerPointerMove(event) {
|
|
1546
|
+
const viewportElement = event.currentTarget;
|
|
1547
|
+
const viewerState = getMarkdownImageViewerStateFromEvent(event);
|
|
1548
|
+
if (!viewerState || !viewerState.isDragging) {
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
const deltaX = event.clientX - viewerState.dragStartClientX;
|
|
1552
|
+
const deltaY = event.clientY - viewerState.dragStartClientY;
|
|
1553
|
+
viewportElement.scrollLeft = viewerState.dragStartScrollLeft - deltaX;
|
|
1554
|
+
viewportElement.scrollTop = viewerState.dragStartScrollTop - deltaY;
|
|
1555
|
+
}
|
|
1556
|
+
function handleMarkdownImageViewerPointerUp(event) {
|
|
1557
|
+
const viewportElement = event.currentTarget;
|
|
1558
|
+
const viewerState = getMarkdownImageViewerStateFromEvent(event);
|
|
1559
|
+
if (!viewerState || !viewerState.isDragging) {
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
viewerState.isDragging = false;
|
|
1563
|
+
viewerState.dialogElement.classList.remove(IMAGE_VIEWER_DRAGGING_CLASS_NAME);
|
|
1564
|
+
if (typeof viewportElement.releasePointerCapture === "function" && viewportElement.hasPointerCapture(event.pointerId)) {
|
|
1565
|
+
viewportElement.releasePointerCapture(event.pointerId);
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
function handleMarkdownImageViewerImageDragStart(event) {
|
|
1569
|
+
event.preventDefault();
|
|
1570
|
+
}
|
|
1571
|
+
function handleMarkdownImageViewerImageLoad(event) {
|
|
1572
|
+
const imageElement = event.currentTarget;
|
|
1573
|
+
const dialogElement = imageElement.closest(
|
|
1574
|
+
`.${IMAGE_VIEWER_DIALOG_CLASS_NAME}`
|
|
1575
|
+
);
|
|
1576
|
+
if (!dialogElement) {
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
const viewerState = imageViewerStateByDialogElement.get(dialogElement);
|
|
1580
|
+
if (!viewerState) {
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
viewerState.naturalWidth = imageElement.naturalWidth || viewerState.naturalWidth;
|
|
1584
|
+
viewerState.naturalHeight = imageElement.naturalHeight || viewerState.naturalHeight;
|
|
1585
|
+
updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);
|
|
1586
|
+
}
|
|
1587
|
+
function handleMarkdownImageViewerWindowResize(event) {
|
|
1588
|
+
const ownerWindow = event.currentTarget;
|
|
1589
|
+
const viewerState = imageViewerStateByDocument.get(ownerWindow.document);
|
|
1590
|
+
if (!viewerState || !isMarkdownImageViewerOpen(viewerState)) {
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);
|
|
1594
|
+
}
|
|
1595
|
+
function handleMarkdownImageViewerZoomOutClick(event) {
|
|
1596
|
+
const viewerState = getMarkdownImageViewerStateFromEvent(event);
|
|
1597
|
+
if (!viewerState) {
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue - VIEWER_ZOOM_STEP);
|
|
1601
|
+
}
|
|
1602
|
+
function handleMarkdownImageViewerZoomInClick(event) {
|
|
1603
|
+
const viewerState = getMarkdownImageViewerStateFromEvent(event);
|
|
1604
|
+
if (!viewerState) {
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + VIEWER_ZOOM_STEP);
|
|
1559
1608
|
}
|
|
1560
|
-
function
|
|
1609
|
+
function handleMarkdownImageViewerResetClick(event) {
|
|
1561
1610
|
const viewerState = getMarkdownImageViewerStateFromEvent(event);
|
|
1562
1611
|
if (!viewerState) {
|
|
1563
1612
|
return;
|
|
1564
1613
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1614
|
+
updateMarkdownImageViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);
|
|
1615
|
+
}
|
|
1616
|
+
function handleMarkdownImageViewerCloseClick(event) {
|
|
1617
|
+
const viewerState = getMarkdownImageViewerStateFromEvent(event);
|
|
1618
|
+
if (!viewerState) {
|
|
1568
1619
|
return;
|
|
1569
1620
|
}
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1621
|
+
closeMarkdownImageViewer(viewerState);
|
|
1622
|
+
}
|
|
1623
|
+
function getMarkdownImageViewerStateFromEvent(event) {
|
|
1624
|
+
const currentTargetElement = event.currentTarget;
|
|
1625
|
+
if (!currentTargetElement) {
|
|
1626
|
+
return void 0;
|
|
1574
1627
|
}
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1628
|
+
const dialogElement = currentTargetElement.closest(
|
|
1629
|
+
`.${IMAGE_VIEWER_DIALOG_CLASS_NAME}`
|
|
1630
|
+
);
|
|
1631
|
+
if (!dialogElement) {
|
|
1632
|
+
return void 0;
|
|
1579
1633
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1634
|
+
return imageViewerStateByDialogElement.get(dialogElement);
|
|
1635
|
+
}
|
|
1636
|
+
function updateMarkdownImageViewerZoom(viewerState, nextZoom, zoomAnchor) {
|
|
1637
|
+
const normalizedZoom = clampMarkdownViewerZoom(nextZoom);
|
|
1638
|
+
const focalPoint = isMarkdownImageViewerOpen(viewerState) ? captureMarkdownImageViewerFocalPoint(viewerState, zoomAnchor) : void 0;
|
|
1639
|
+
viewerState.zoomValue = normalizedZoom;
|
|
1640
|
+
viewerState.dialogElement.classList.toggle(
|
|
1641
|
+
IMAGE_VIEWER_ZOOMED_CLASS_NAME,
|
|
1642
|
+
normalizedZoom > VIEWER_DEFAULT_ZOOM
|
|
1643
|
+
);
|
|
1644
|
+
viewerState.zoomValueElement.textContent = `${Math.round(normalizedZoom * 100)}%`;
|
|
1645
|
+
viewerState.zoomOutButtonElement.disabled = normalizedZoom <= VIEWER_MIN_ZOOM;
|
|
1646
|
+
viewerState.zoomInButtonElement.disabled = normalizedZoom >= VIEWER_MAX_ZOOM;
|
|
1647
|
+
viewerState.resetButtonElement.disabled = normalizedZoom === VIEWER_DEFAULT_ZOOM;
|
|
1648
|
+
updateMarkdownImageViewerImageSize(viewerState);
|
|
1649
|
+
if (focalPoint) {
|
|
1650
|
+
applyMarkdownImageViewerFocalPoint(viewerState, focalPoint);
|
|
1583
1651
|
}
|
|
1584
1652
|
}
|
|
1585
|
-
function
|
|
1586
|
-
|
|
1587
|
-
|
|
1653
|
+
function captureMarkdownImageViewerFocalPoint(viewerState, zoomAnchor) {
|
|
1654
|
+
const preImageRect = viewerState.imageElement.getBoundingClientRect();
|
|
1655
|
+
if (preImageRect.width <= 0 || preImageRect.height <= 0) {
|
|
1656
|
+
return void 0;
|
|
1588
1657
|
}
|
|
1589
|
-
const
|
|
1590
|
-
|
|
1658
|
+
const viewportRect = viewerState.viewportElement.getBoundingClientRect();
|
|
1659
|
+
const anchorClientX = zoomAnchor?.x ?? viewportRect.left + viewportRect.width / 2;
|
|
1660
|
+
const anchorClientY = zoomAnchor?.y ?? viewportRect.top + viewportRect.height / 2;
|
|
1661
|
+
const normalizedX = (anchorClientX - preImageRect.left) / preImageRect.width;
|
|
1662
|
+
const normalizedY = (anchorClientY - preImageRect.top) / preImageRect.height;
|
|
1663
|
+
return {
|
|
1664
|
+
anchorClientX,
|
|
1665
|
+
anchorClientY,
|
|
1666
|
+
normalizedX,
|
|
1667
|
+
normalizedY
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
function applyMarkdownImageViewerFocalPoint(viewerState, focalPoint) {
|
|
1671
|
+
viewerState.viewportElement.scrollLeft = 0;
|
|
1672
|
+
viewerState.viewportElement.scrollTop = 0;
|
|
1673
|
+
const postImageRect = viewerState.imageElement.getBoundingClientRect();
|
|
1674
|
+
if (postImageRect.width <= 0 || postImageRect.height <= 0) {
|
|
1591
1675
|
return;
|
|
1592
1676
|
}
|
|
1593
|
-
const
|
|
1594
|
-
const
|
|
1595
|
-
|
|
1596
|
-
|
|
1677
|
+
const targetImageClientLeft = focalPoint.anchorClientX - focalPoint.normalizedX * postImageRect.width;
|
|
1678
|
+
const targetImageClientTop = focalPoint.anchorClientY - focalPoint.normalizedY * postImageRect.height;
|
|
1679
|
+
viewerState.viewportElement.scrollLeft = postImageRect.left - targetImageClientLeft;
|
|
1680
|
+
viewerState.viewportElement.scrollTop = postImageRect.top - targetImageClientTop;
|
|
1681
|
+
}
|
|
1682
|
+
function updateMarkdownImageViewerImageSize(viewerState) {
|
|
1683
|
+
const viewportWidth = Math.max(
|
|
1684
|
+
viewerState.viewportElement.clientWidth * VIEWER_FIT_RATIO,
|
|
1685
|
+
1
|
|
1597
1686
|
);
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1687
|
+
const viewportHeight = Math.max(
|
|
1688
|
+
viewerState.viewportElement.clientHeight * VIEWER_FIT_RATIO,
|
|
1689
|
+
1
|
|
1690
|
+
);
|
|
1691
|
+
const naturalWidth = Math.max(viewerState.naturalWidth, 1);
|
|
1692
|
+
const naturalHeight = Math.max(viewerState.naturalHeight, 1);
|
|
1693
|
+
const fitScale = Math.min(1, viewportWidth / naturalWidth, viewportHeight / naturalHeight);
|
|
1694
|
+
const displayWidth = Math.max(1, Math.round(naturalWidth * fitScale * viewerState.zoomValue));
|
|
1695
|
+
const displayHeight = Math.max(1, Math.round(naturalHeight * fitScale * viewerState.zoomValue));
|
|
1696
|
+
viewerState.imageElement.style.width = `${displayWidth}px`;
|
|
1697
|
+
viewerState.imageElement.style.height = `${displayHeight}px`;
|
|
1603
1698
|
}
|
|
1604
|
-
function
|
|
1605
|
-
if (
|
|
1699
|
+
function closeMarkdownImageViewer(viewerState) {
|
|
1700
|
+
if (typeof viewerState.dialogElement.close === "function" && viewerState.dialogElement.open) {
|
|
1701
|
+
viewerState.dialogElement.close();
|
|
1606
1702
|
return;
|
|
1607
1703
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1704
|
+
viewerState.dialogElement.removeAttribute("open");
|
|
1705
|
+
resetMarkdownImageViewerAfterClose(viewerState);
|
|
1706
|
+
}
|
|
1707
|
+
function resetMarkdownImageViewerAfterClose(viewerState) {
|
|
1708
|
+
viewerState.dialogElement.classList.remove(IMAGE_VIEWER_ZOOMED_CLASS_NAME);
|
|
1709
|
+
viewerState.dialogElement.classList.remove(IMAGE_VIEWER_DRAGGING_CLASS_NAME);
|
|
1710
|
+
viewerState.isDragging = false;
|
|
1711
|
+
viewerState.imageElement.removeAttribute("src");
|
|
1712
|
+
viewerState.imageElement.removeAttribute("alt");
|
|
1713
|
+
viewerState.imageElement.removeAttribute("style");
|
|
1714
|
+
viewerState.captionElement.textContent = "";
|
|
1715
|
+
viewerState.viewportElement.scrollLeft = 0;
|
|
1716
|
+
viewerState.viewportElement.scrollTop = 0;
|
|
1717
|
+
viewerState.zoomValue = VIEWER_DEFAULT_ZOOM;
|
|
1718
|
+
if (viewerState.sourceImageElement?.isConnected) {
|
|
1719
|
+
viewerState.sourceImageElement.focus();
|
|
1612
1720
|
}
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1721
|
+
}
|
|
1722
|
+
function isMarkdownImageViewerOpen(viewerState) {
|
|
1723
|
+
return viewerState.dialogElement.open || viewerState.dialogElement.hasAttribute("open");
|
|
1724
|
+
}
|
|
1725
|
+
function getMarkdownImageSource(imageElement) {
|
|
1726
|
+
return imageElement.currentSrc || imageElement.getAttribute("src") || imageElement.src || "";
|
|
1727
|
+
}
|
|
1728
|
+
function createMarkdownImageViewerAriaLabel(imageElement) {
|
|
1729
|
+
const imageAltText = imageElement.alt.trim();
|
|
1730
|
+
if (imageAltText.length === 0) {
|
|
1731
|
+
return IMAGE_VIEWER_FALLBACK_LABEL;
|
|
1732
|
+
}
|
|
1733
|
+
return `${IMAGE_VIEWER_FALLBACK_LABEL}\uFF1A${imageAltText}`;
|
|
1734
|
+
}
|
|
1735
|
+
function createMarkdownImageViewerCaption(imageElement, imageSource) {
|
|
1736
|
+
const imageAltText = imageElement.alt.trim();
|
|
1737
|
+
const imageTitleText = imageElement.title.trim();
|
|
1738
|
+
return imageAltText || imageTitleText || imageSource;
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
// src/media/images.ts
|
|
1742
|
+
var IMAGE_HYDRATED_DATA_KEY = "scribdownImageHydrated";
|
|
1743
|
+
function remarkImageFigures() {
|
|
1744
|
+
return (tree) => {
|
|
1745
|
+
transformImageFigures(tree);
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
function transformImageFigures(node) {
|
|
1749
|
+
if (!node.children) {
|
|
1616
1750
|
return;
|
|
1617
1751
|
}
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1752
|
+
const childNodes = node.children;
|
|
1753
|
+
for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {
|
|
1754
|
+
const childNode = childNodes[childIndex];
|
|
1755
|
+
const imageFigureNode = createImageFigureNode(childNode);
|
|
1756
|
+
if (imageFigureNode) {
|
|
1757
|
+
childNodes[childIndex] = imageFigureNode;
|
|
1758
|
+
continue;
|
|
1759
|
+
}
|
|
1760
|
+
if (isImageNode(childNode)) {
|
|
1761
|
+
decorateImageNode(childNode);
|
|
1762
|
+
continue;
|
|
1763
|
+
}
|
|
1764
|
+
transformImageFigures(childNode);
|
|
1626
1765
|
}
|
|
1627
|
-
event.preventDefault();
|
|
1628
1766
|
}
|
|
1629
|
-
function
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1767
|
+
function createImageFigureNode(node) {
|
|
1768
|
+
if (node.type !== "paragraph" || !node.children || node.children.length !== 1) {
|
|
1769
|
+
return void 0;
|
|
1770
|
+
}
|
|
1771
|
+
const onlyChild = node.children[0];
|
|
1772
|
+
if (!isImageNode(onlyChild)) {
|
|
1773
|
+
return void 0;
|
|
1774
|
+
}
|
|
1775
|
+
decorateImageNode(onlyChild);
|
|
1776
|
+
return {
|
|
1777
|
+
type: "imageFigure",
|
|
1778
|
+
// 关键步骤:保留原段落源码位置,使 remarkSourceLine 能为 figure 标注 data-source-line。
|
|
1779
|
+
position: node.position,
|
|
1780
|
+
data: {
|
|
1781
|
+
hName: "figure",
|
|
1782
|
+
hProperties: {
|
|
1783
|
+
className: [IMAGE_FIGURE_CLASS_NAME]
|
|
1784
|
+
}
|
|
1785
|
+
},
|
|
1786
|
+
children: [createImageFrameNode(onlyChild), ...createImageCaptionNodes(onlyChild)]
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
function decorateImageNode(imageNode) {
|
|
1790
|
+
imageNode.data = {
|
|
1791
|
+
...imageNode.data,
|
|
1792
|
+
hProperties: {
|
|
1793
|
+
...imageNode.data?.hProperties,
|
|
1794
|
+
className: [IMAGE_ELEMENT_CLASS_NAME]
|
|
1795
|
+
}
|
|
1796
|
+
};
|
|
1797
|
+
}
|
|
1798
|
+
function createImageFrameNode(imageNode) {
|
|
1799
|
+
return {
|
|
1800
|
+
type: "imageFrame",
|
|
1801
|
+
data: {
|
|
1802
|
+
hName: "span",
|
|
1803
|
+
hProperties: {
|
|
1804
|
+
className: [IMAGE_FRAME_CLASS_NAME]
|
|
1805
|
+
}
|
|
1806
|
+
},
|
|
1807
|
+
children: [imageNode, createImageFallbackNode(imageNode)]
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
function createImageCaptionNodes(imageNode) {
|
|
1811
|
+
const imageTitle = typeof imageNode.title === "string" ? imageNode.title.trim() : "";
|
|
1812
|
+
if (imageTitle.length === 0) {
|
|
1813
|
+
return [];
|
|
1814
|
+
}
|
|
1815
|
+
return [
|
|
1816
|
+
{
|
|
1817
|
+
type: "imageCaption",
|
|
1818
|
+
data: {
|
|
1819
|
+
hName: "figcaption",
|
|
1820
|
+
hProperties: {
|
|
1821
|
+
className: [IMAGE_CAPTION_CLASS_NAME]
|
|
1822
|
+
}
|
|
1823
|
+
},
|
|
1824
|
+
children: [{ type: "text", value: imageTitle }]
|
|
1825
|
+
}
|
|
1826
|
+
];
|
|
1827
|
+
}
|
|
1828
|
+
function createImageFallbackNode(imageNode) {
|
|
1829
|
+
const fallbackText = imageNode.alt?.trim() || "\u56FE\u7247\u52A0\u8F7D\u5931\u8D25";
|
|
1830
|
+
const fallbackSource = imageNode.url ?? imageNode.identifier ?? "";
|
|
1831
|
+
return {
|
|
1832
|
+
type: "imageFallback",
|
|
1833
|
+
data: {
|
|
1834
|
+
hName: "span",
|
|
1835
|
+
hProperties: {
|
|
1836
|
+
className: [IMAGE_FALLBACK_CLASS_NAME]
|
|
1837
|
+
}
|
|
1838
|
+
},
|
|
1839
|
+
children: [
|
|
1840
|
+
{
|
|
1841
|
+
type: "imageFallbackIcon",
|
|
1842
|
+
data: {
|
|
1843
|
+
hName: "span",
|
|
1844
|
+
hProperties: {
|
|
1845
|
+
className: [IMAGE_FALLBACK_ICON_CLASS_NAME]
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
},
|
|
1849
|
+
{
|
|
1850
|
+
type: "imageFallbackText",
|
|
1851
|
+
data: {
|
|
1852
|
+
hName: "span",
|
|
1853
|
+
hProperties: {
|
|
1854
|
+
className: [IMAGE_FALLBACK_TEXT_CLASS_NAME]
|
|
1855
|
+
}
|
|
1856
|
+
},
|
|
1857
|
+
children: [{ type: "text", value: fallbackText }]
|
|
1858
|
+
},
|
|
1859
|
+
{
|
|
1860
|
+
type: "imageFallbackSource",
|
|
1861
|
+
data: {
|
|
1862
|
+
hName: "span",
|
|
1863
|
+
hProperties: {
|
|
1864
|
+
className: [IMAGE_FALLBACK_SOURCE_CLASS_NAME]
|
|
1865
|
+
}
|
|
1866
|
+
},
|
|
1867
|
+
children: [{ type: "text", value: fallbackSource }]
|
|
1868
|
+
}
|
|
1869
|
+
]
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
function hydrateMarkdownImages(rootElement) {
|
|
1873
|
+
const imageElements = rootElement.querySelectorAll(
|
|
1874
|
+
`img.${IMAGE_ELEMENT_CLASS_NAME}`
|
|
1875
|
+
);
|
|
1876
|
+
imageElements.forEach(bindMarkdownImageState);
|
|
1639
1877
|
}
|
|
1640
|
-
function
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
if (!viewerState || !viewerState.isDragging) {
|
|
1878
|
+
function bindMarkdownImageState(imageElement) {
|
|
1879
|
+
updateMarkdownImageState(imageElement);
|
|
1880
|
+
if (imageElement.dataset[IMAGE_HYDRATED_DATA_KEY] === "true") {
|
|
1644
1881
|
return;
|
|
1645
1882
|
}
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
}
|
|
1883
|
+
imageElement.dataset[IMAGE_HYDRATED_DATA_KEY] = "true";
|
|
1884
|
+
imageElement.addEventListener("load", handleMarkdownImageLoad);
|
|
1885
|
+
imageElement.addEventListener("error", handleMarkdownImageError);
|
|
1886
|
+
bindMarkdownImageViewer(imageElement);
|
|
1651
1887
|
}
|
|
1652
|
-
function
|
|
1653
|
-
event.
|
|
1888
|
+
function handleMarkdownImageLoad(event) {
|
|
1889
|
+
const imageElement = event.currentTarget;
|
|
1890
|
+
updateMarkdownImageState(imageElement);
|
|
1654
1891
|
}
|
|
1655
|
-
function
|
|
1892
|
+
function handleMarkdownImageError(event) {
|
|
1656
1893
|
const imageElement = event.currentTarget;
|
|
1657
|
-
|
|
1658
|
-
`.${IMAGE_VIEWER_DIALOG_CLASS_NAME}`
|
|
1659
|
-
);
|
|
1660
|
-
if (!dialogElement) {
|
|
1661
|
-
return;
|
|
1662
|
-
}
|
|
1663
|
-
const viewerState = imageViewerStateByDialogElement.get(dialogElement);
|
|
1664
|
-
if (!viewerState) {
|
|
1665
|
-
return;
|
|
1666
|
-
}
|
|
1667
|
-
viewerState.naturalWidth = imageElement.naturalWidth || viewerState.naturalWidth;
|
|
1668
|
-
viewerState.naturalHeight = imageElement.naturalHeight || viewerState.naturalHeight;
|
|
1669
|
-
updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);
|
|
1894
|
+
updateMarkdownImageState(imageElement);
|
|
1670
1895
|
}
|
|
1671
|
-
function
|
|
1672
|
-
const
|
|
1673
|
-
|
|
1674
|
-
if (!viewerState || !isMarkdownImageViewerOpen(viewerState)) {
|
|
1896
|
+
function updateMarkdownImageState(imageElement) {
|
|
1897
|
+
const frameElement = imageElement.closest(`.${IMAGE_FRAME_CLASS_NAME}`);
|
|
1898
|
+
if (!frameElement) {
|
|
1675
1899
|
return;
|
|
1676
1900
|
}
|
|
1677
|
-
|
|
1901
|
+
const isFailed = imageElement.complete && imageElement.naturalWidth === 0;
|
|
1902
|
+
const isLoaded = imageElement.complete && imageElement.naturalWidth > 0;
|
|
1903
|
+
frameElement.classList.toggle(IMAGE_FRAME_FAILED_CLASS_NAME, isFailed);
|
|
1904
|
+
frameElement.classList.toggle(IMAGE_FRAME_LOADED_CLASS_NAME, isLoaded);
|
|
1678
1905
|
}
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1906
|
+
|
|
1907
|
+
// src/media/videos.ts
|
|
1908
|
+
import {
|
|
1909
|
+
VIDEO_ELEMENT_CLASS_NAME,
|
|
1910
|
+
VIDEO_FALLBACK_CLASS_NAME,
|
|
1911
|
+
VIDEO_FALLBACK_ICON_CLASS_NAME,
|
|
1912
|
+
VIDEO_FALLBACK_SOURCE_CLASS_NAME,
|
|
1913
|
+
VIDEO_FALLBACK_TEXT_CLASS_NAME,
|
|
1914
|
+
VIDEO_FIGURE_CLASS_NAME,
|
|
1915
|
+
VIDEO_FRAME_CLASS_NAME,
|
|
1916
|
+
VIDEO_FRAME_FAILED_CLASS_NAME,
|
|
1917
|
+
VIDEO_FRAME_LOADED_CLASS_NAME
|
|
1918
|
+
} from "@scribdown/shared";
|
|
1919
|
+
var VIDEO_HYDRATED_DATA_KEY = "scribdownVideoHydrated";
|
|
1920
|
+
var VIDEO_FALLBACK_DEFAULT_TEXT = "\u89C6\u9891\u52A0\u8F7D\u5931\u8D25";
|
|
1921
|
+
function rehypeVideoFigures() {
|
|
1922
|
+
return (tree) => {
|
|
1923
|
+
transformVideoFigures(tree);
|
|
1924
|
+
};
|
|
1685
1925
|
}
|
|
1686
|
-
function
|
|
1687
|
-
|
|
1688
|
-
if (!viewerState) {
|
|
1926
|
+
function transformVideoFigures(node) {
|
|
1927
|
+
if (!Array.isArray(node.children)) {
|
|
1689
1928
|
return;
|
|
1690
1929
|
}
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1930
|
+
const childNodes = node.children;
|
|
1931
|
+
for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {
|
|
1932
|
+
const childNode = childNodes[childIndex];
|
|
1933
|
+
if (isHastVideoElement(childNode)) {
|
|
1934
|
+
childNodes[childIndex] = createVideoFigureHast(childNode);
|
|
1935
|
+
continue;
|
|
1936
|
+
}
|
|
1937
|
+
const standaloneVideo = extractStandaloneParagraphVideo(childNode);
|
|
1938
|
+
if (standaloneVideo) {
|
|
1939
|
+
childNodes[childIndex] = createVideoFigureHast(standaloneVideo);
|
|
1940
|
+
continue;
|
|
1941
|
+
}
|
|
1942
|
+
transformVideoFigures(childNode);
|
|
1697
1943
|
}
|
|
1698
|
-
updateMarkdownImageViewerZoom(viewerState, IMAGE_VIEWER_DEFAULT_ZOOM);
|
|
1699
1944
|
}
|
|
1700
|
-
function
|
|
1701
|
-
|
|
1702
|
-
if (!viewerState) {
|
|
1703
|
-
return;
|
|
1704
|
-
}
|
|
1705
|
-
closeMarkdownImageViewer(viewerState);
|
|
1945
|
+
function isHastVideoElement(node) {
|
|
1946
|
+
return node.type === "element" && node.tagName === "video";
|
|
1706
1947
|
}
|
|
1707
|
-
function
|
|
1708
|
-
|
|
1709
|
-
if (!currentTargetElement) {
|
|
1948
|
+
function extractStandaloneParagraphVideo(node) {
|
|
1949
|
+
if (node.type !== "element" || node.tagName !== "p" || !Array.isArray(node.children)) {
|
|
1710
1950
|
return void 0;
|
|
1711
1951
|
}
|
|
1712
|
-
const
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
return
|
|
1952
|
+
const significantChildren = node.children.filter((childNode) => {
|
|
1953
|
+
if (childNode.type === "text" && typeof childNode.value === "string") {
|
|
1954
|
+
return childNode.value.trim().length > 0;
|
|
1955
|
+
}
|
|
1956
|
+
return true;
|
|
1957
|
+
});
|
|
1958
|
+
if (significantChildren.length === 1 && isHastVideoElement(significantChildren[0])) {
|
|
1959
|
+
return significantChildren[0];
|
|
1717
1960
|
}
|
|
1718
|
-
return
|
|
1961
|
+
return void 0;
|
|
1719
1962
|
}
|
|
1720
|
-
function
|
|
1721
|
-
|
|
1722
|
-
const
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1963
|
+
function createVideoFigureHast(videoNode) {
|
|
1964
|
+
decorateHastVideoElement(videoNode);
|
|
1965
|
+
const sourceUrl = readHastVideoSourceUrl(videoNode);
|
|
1966
|
+
return {
|
|
1967
|
+
type: "element",
|
|
1968
|
+
tagName: "figure",
|
|
1969
|
+
properties: { className: [VIDEO_FIGURE_CLASS_NAME] },
|
|
1970
|
+
children: [
|
|
1971
|
+
{
|
|
1972
|
+
type: "element",
|
|
1973
|
+
tagName: "span",
|
|
1974
|
+
properties: { className: [VIDEO_FRAME_CLASS_NAME] },
|
|
1975
|
+
children: [videoNode, createVideoFallbackHast(sourceUrl)]
|
|
1976
|
+
}
|
|
1977
|
+
]
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1980
|
+
function decorateHastVideoElement(videoNode) {
|
|
1981
|
+
const properties = videoNode.properties ?? {};
|
|
1982
|
+
const existingClassName = properties.className;
|
|
1983
|
+
const classList = Array.isArray(existingClassName) ? existingClassName.map(String) : typeof existingClassName === "string" ? [existingClassName] : [];
|
|
1984
|
+
if (!classList.includes(VIDEO_ELEMENT_CLASS_NAME)) {
|
|
1985
|
+
classList.push(VIDEO_ELEMENT_CLASS_NAME);
|
|
1735
1986
|
}
|
|
1987
|
+
properties.className = classList;
|
|
1988
|
+
videoNode.properties = properties;
|
|
1736
1989
|
}
|
|
1737
|
-
function
|
|
1738
|
-
const
|
|
1739
|
-
if (
|
|
1740
|
-
return
|
|
1990
|
+
function readHastVideoSourceUrl(videoNode) {
|
|
1991
|
+
const properties = videoNode.properties ?? {};
|
|
1992
|
+
if (typeof properties.src === "string") {
|
|
1993
|
+
return properties.src;
|
|
1741
1994
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1995
|
+
if (Array.isArray(videoNode.children)) {
|
|
1996
|
+
for (const childNode of videoNode.children) {
|
|
1997
|
+
if (childNode.type === "element" && childNode.tagName === "source" && typeof childNode.properties?.src === "string") {
|
|
1998
|
+
return childNode.properties.src;
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
return "";
|
|
2003
|
+
}
|
|
2004
|
+
function createVideoFallbackHast(sourceUrl) {
|
|
1747
2005
|
return {
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
2006
|
+
type: "element",
|
|
2007
|
+
tagName: "span",
|
|
2008
|
+
properties: { className: [VIDEO_FALLBACK_CLASS_NAME] },
|
|
2009
|
+
children: [
|
|
2010
|
+
{
|
|
2011
|
+
type: "element",
|
|
2012
|
+
tagName: "span",
|
|
2013
|
+
properties: { className: [VIDEO_FALLBACK_ICON_CLASS_NAME] },
|
|
2014
|
+
children: []
|
|
2015
|
+
},
|
|
2016
|
+
{
|
|
2017
|
+
type: "element",
|
|
2018
|
+
tagName: "span",
|
|
2019
|
+
properties: { className: [VIDEO_FALLBACK_TEXT_CLASS_NAME] },
|
|
2020
|
+
children: [{ type: "text", value: VIDEO_FALLBACK_DEFAULT_TEXT }]
|
|
2021
|
+
},
|
|
2022
|
+
{
|
|
2023
|
+
type: "element",
|
|
2024
|
+
tagName: "span",
|
|
2025
|
+
properties: { className: [VIDEO_FALLBACK_SOURCE_CLASS_NAME] },
|
|
2026
|
+
children: [{ type: "text", value: sourceUrl }]
|
|
2027
|
+
}
|
|
2028
|
+
]
|
|
1752
2029
|
};
|
|
1753
2030
|
}
|
|
1754
|
-
function
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
2031
|
+
function hydrateMarkdownVideos(rootElement) {
|
|
2032
|
+
const videoElements = rootElement.querySelectorAll(
|
|
2033
|
+
`video.${VIDEO_ELEMENT_CLASS_NAME}`
|
|
2034
|
+
);
|
|
2035
|
+
videoElements.forEach(bindMarkdownVideoState);
|
|
2036
|
+
}
|
|
2037
|
+
function bindMarkdownVideoState(videoElement) {
|
|
2038
|
+
updateMarkdownVideoState(videoElement);
|
|
2039
|
+
if (videoElement.dataset[VIDEO_HYDRATED_DATA_KEY] === "true") {
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
videoElement.dataset[VIDEO_HYDRATED_DATA_KEY] = "true";
|
|
2043
|
+
videoElement.addEventListener("loadeddata", handleMarkdownVideoLoaded);
|
|
2044
|
+
videoElement.addEventListener("error", handleMarkdownVideoError);
|
|
2045
|
+
videoElement.querySelectorAll("source").forEach((sourceElement) => {
|
|
2046
|
+
sourceElement.addEventListener("error", handleMarkdownVideoSourceError);
|
|
2047
|
+
});
|
|
2048
|
+
}
|
|
2049
|
+
function handleMarkdownVideoLoaded(event) {
|
|
2050
|
+
const videoElement = event.currentTarget;
|
|
2051
|
+
updateMarkdownVideoState(videoElement);
|
|
2052
|
+
}
|
|
2053
|
+
function handleMarkdownVideoError(event) {
|
|
2054
|
+
const videoElement = event.currentTarget;
|
|
2055
|
+
markMarkdownVideoFailed(videoElement);
|
|
2056
|
+
}
|
|
2057
|
+
function handleMarkdownVideoSourceError(event) {
|
|
2058
|
+
const sourceElement = event.currentTarget;
|
|
2059
|
+
const videoElement = sourceElement.closest(
|
|
2060
|
+
`video.${VIDEO_ELEMENT_CLASS_NAME}`
|
|
2061
|
+
);
|
|
2062
|
+
if (!videoElement) {
|
|
1759
2063
|
return;
|
|
1760
2064
|
}
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
2065
|
+
if (videoElement.networkState !== videoElement.NETWORK_NO_SOURCE) {
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
2068
|
+
markMarkdownVideoFailed(videoElement);
|
|
1765
2069
|
}
|
|
1766
|
-
function
|
|
1767
|
-
const
|
|
1768
|
-
|
|
1769
|
-
1
|
|
1770
|
-
);
|
|
1771
|
-
const viewportHeight = Math.max(
|
|
1772
|
-
viewerState.viewportElement.clientHeight * IMAGE_VIEWER_FIT_RATIO,
|
|
1773
|
-
1
|
|
2070
|
+
function updateMarkdownVideoState(videoElement) {
|
|
2071
|
+
const frameElement = videoElement.closest(
|
|
2072
|
+
`.${VIDEO_FRAME_CLASS_NAME}`
|
|
1774
2073
|
);
|
|
1775
|
-
|
|
1776
|
-
const naturalHeight = Math.max(viewerState.naturalHeight, 1);
|
|
1777
|
-
const fitScale = Math.min(1, viewportWidth / naturalWidth, viewportHeight / naturalHeight);
|
|
1778
|
-
const displayWidth = Math.max(1, Math.round(naturalWidth * fitScale * viewerState.zoomValue));
|
|
1779
|
-
const displayHeight = Math.max(1, Math.round(naturalHeight * fitScale * viewerState.zoomValue));
|
|
1780
|
-
viewerState.imageElement.style.width = `${displayWidth}px`;
|
|
1781
|
-
viewerState.imageElement.style.height = `${displayHeight}px`;
|
|
1782
|
-
}
|
|
1783
|
-
function clampMarkdownImageViewerZoom(zoomValue) {
|
|
1784
|
-
return Math.min(IMAGE_VIEWER_MAX_ZOOM, Math.max(IMAGE_VIEWER_MIN_ZOOM, zoomValue));
|
|
1785
|
-
}
|
|
1786
|
-
function closeMarkdownImageViewer(viewerState) {
|
|
1787
|
-
if (typeof viewerState.dialogElement.close === "function" && viewerState.dialogElement.open) {
|
|
1788
|
-
viewerState.dialogElement.close();
|
|
2074
|
+
if (!frameElement) {
|
|
1789
2075
|
return;
|
|
1790
2076
|
}
|
|
1791
|
-
|
|
1792
|
-
|
|
2077
|
+
const isFailed = videoElement.error !== null || videoElement.networkState === videoElement.NETWORK_NO_SOURCE;
|
|
2078
|
+
const isLoaded = videoElement.readyState >= videoElement.HAVE_CURRENT_DATA;
|
|
2079
|
+
frameElement.classList.toggle(VIDEO_FRAME_FAILED_CLASS_NAME, isFailed);
|
|
2080
|
+
frameElement.classList.toggle(
|
|
2081
|
+
VIDEO_FRAME_LOADED_CLASS_NAME,
|
|
2082
|
+
isLoaded && !isFailed
|
|
2083
|
+
);
|
|
1793
2084
|
}
|
|
1794
|
-
function
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
viewerState.imageElement.removeAttribute("style");
|
|
1801
|
-
viewerState.captionElement.textContent = "";
|
|
1802
|
-
viewerState.viewportElement.scrollLeft = 0;
|
|
1803
|
-
viewerState.viewportElement.scrollTop = 0;
|
|
1804
|
-
viewerState.zoomValue = IMAGE_VIEWER_DEFAULT_ZOOM;
|
|
1805
|
-
if (viewerState.sourceImageElement?.isConnected) {
|
|
1806
|
-
viewerState.sourceImageElement.focus();
|
|
2085
|
+
function markMarkdownVideoFailed(videoElement) {
|
|
2086
|
+
const frameElement = videoElement.closest(
|
|
2087
|
+
`.${VIDEO_FRAME_CLASS_NAME}`
|
|
2088
|
+
);
|
|
2089
|
+
if (!frameElement) {
|
|
2090
|
+
return;
|
|
1807
2091
|
}
|
|
2092
|
+
frameElement.classList.add(VIDEO_FRAME_FAILED_CLASS_NAME);
|
|
2093
|
+
frameElement.classList.remove(VIDEO_FRAME_LOADED_CLASS_NAME);
|
|
1808
2094
|
}
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
2095
|
+
|
|
2096
|
+
// src/sanitize.ts
|
|
2097
|
+
import {
|
|
2098
|
+
DETAILS_CLASS_NAME,
|
|
2099
|
+
FRAME_CLASS_NAME,
|
|
2100
|
+
FRONTMATTER_CHROME_CLASS_NAME,
|
|
2101
|
+
FRONTMATTER_CLASS_NAME,
|
|
2102
|
+
FRONTMATTER_LABEL_CLASS_NAME,
|
|
2103
|
+
FRONTMATTER_LIST_CLASS_NAME,
|
|
2104
|
+
FRONTMATTER_LIST_NESTED_CLASS_NAME,
|
|
2105
|
+
HEADING_MARK_CLASS_NAME,
|
|
2106
|
+
IMAGE_CAPTION_CLASS_NAME as IMAGE_CAPTION_CLASS_NAME2,
|
|
2107
|
+
IMAGE_ELEMENT_CLASS_NAME as IMAGE_ELEMENT_CLASS_NAME2,
|
|
2108
|
+
IMAGE_FALLBACK_CLASS_NAME as IMAGE_FALLBACK_CLASS_NAME2,
|
|
2109
|
+
IMAGE_FALLBACK_ICON_CLASS_NAME as IMAGE_FALLBACK_ICON_CLASS_NAME2,
|
|
2110
|
+
IMAGE_FALLBACK_SOURCE_CLASS_NAME as IMAGE_FALLBACK_SOURCE_CLASS_NAME2,
|
|
2111
|
+
IMAGE_FALLBACK_TEXT_CLASS_NAME as IMAGE_FALLBACK_TEXT_CLASS_NAME2,
|
|
2112
|
+
IMAGE_FIGURE_CLASS_NAME as IMAGE_FIGURE_CLASS_NAME2,
|
|
2113
|
+
IMAGE_FRAME_CLASS_NAME as IMAGE_FRAME_CLASS_NAME2,
|
|
2114
|
+
TOC_CLASS_NAME,
|
|
2115
|
+
TOC_ITEM_CLASS_PREFIX,
|
|
2116
|
+
TOC_LINK_CLASS_NAME,
|
|
2117
|
+
TOC_LIST_CLASS_NAME,
|
|
2118
|
+
TOC_LIST_NESTED_CLASS_NAME,
|
|
2119
|
+
TOC_NAV_CLASS_NAME,
|
|
2120
|
+
TOC_SUMMARY_CLASS_NAME,
|
|
2121
|
+
TOC_TOGGLE_CLASS_NAME,
|
|
2122
|
+
VIDEO_ELEMENT_CLASS_NAME as VIDEO_ELEMENT_CLASS_NAME2,
|
|
2123
|
+
VIDEO_FALLBACK_CLASS_NAME as VIDEO_FALLBACK_CLASS_NAME2,
|
|
2124
|
+
VIDEO_FALLBACK_ICON_CLASS_NAME as VIDEO_FALLBACK_ICON_CLASS_NAME2,
|
|
2125
|
+
VIDEO_FALLBACK_SOURCE_CLASS_NAME as VIDEO_FALLBACK_SOURCE_CLASS_NAME2,
|
|
2126
|
+
VIDEO_FALLBACK_TEXT_CLASS_NAME as VIDEO_FALLBACK_TEXT_CLASS_NAME2,
|
|
2127
|
+
VIDEO_FIGURE_CLASS_NAME as VIDEO_FIGURE_CLASS_NAME2,
|
|
2128
|
+
VIDEO_FRAME_CLASS_NAME as VIDEO_FRAME_CLASS_NAME2
|
|
2129
|
+
} from "@scribdown/shared";
|
|
2130
|
+
import DOMPurify from "dompurify";
|
|
2131
|
+
import { defaultSchema } from "rehype-sanitize";
|
|
2132
|
+
function sanitizeHtmlWithDomPurify(unsafeHtml) {
|
|
2133
|
+
const domPurify = DOMPurify;
|
|
2134
|
+
if (typeof domPurify.sanitize !== "function") {
|
|
2135
|
+
return unsafeHtml;
|
|
1819
2136
|
}
|
|
1820
|
-
return
|
|
1821
|
-
}
|
|
1822
|
-
function createMarkdownImageViewerCaption(imageElement, imageSource) {
|
|
1823
|
-
const imageAltText = imageElement.alt.trim();
|
|
1824
|
-
const imageTitleText = imageElement.title.trim();
|
|
1825
|
-
return imageAltText || imageTitleText || imageSource;
|
|
2137
|
+
return domPurify.sanitize(unsafeHtml);
|
|
1826
2138
|
}
|
|
1827
2139
|
function createScribdownSanitizeSchema() {
|
|
1828
2140
|
const defaultTagNames = defaultSchema.tagNames ?? [];
|
|
@@ -1835,7 +2147,7 @@ function createScribdownSanitizeSchema() {
|
|
|
1835
2147
|
...defaultAttributes.summary ?? [],
|
|
1836
2148
|
["className", TOC_SUMMARY_CLASS_NAME]
|
|
1837
2149
|
];
|
|
1838
|
-
const navAttributes = ["ariaLabel", ["className",
|
|
2150
|
+
const navAttributes = ["ariaLabel", ["className", TOC_NAV_CLASS_NAME]];
|
|
1839
2151
|
const linkAttributes = [
|
|
1840
2152
|
"ariaDescribedBy",
|
|
1841
2153
|
"ariaLabel",
|
|
@@ -1855,32 +2167,40 @@ function createScribdownSanitizeSchema() {
|
|
|
1855
2167
|
"ariaDescribedBy",
|
|
1856
2168
|
"ariaLabel",
|
|
1857
2169
|
"ariaLabelledBy",
|
|
1858
|
-
["className", "contains-task-list",
|
|
2170
|
+
["className", "contains-task-list", TOC_LIST_CLASS_NAME, TOC_LIST_NESTED_CLASS_NAME]
|
|
2171
|
+
];
|
|
2172
|
+
const divAttributes = [
|
|
2173
|
+
...defaultAttributes.div ?? [],
|
|
2174
|
+
["className", FRONTMATTER_CLASS_NAME, FRAME_CLASS_NAME, FRONTMATTER_CHROME_CLASS_NAME]
|
|
2175
|
+
];
|
|
2176
|
+
const definitionListAttributes = [
|
|
2177
|
+
["className", FRONTMATTER_LIST_CLASS_NAME, FRONTMATTER_LIST_NESTED_CLASS_NAME]
|
|
1859
2178
|
];
|
|
1860
2179
|
const spanAttributes = [
|
|
1861
2180
|
...defaultAttributes.span ?? [],
|
|
1862
2181
|
[
|
|
1863
2182
|
"className",
|
|
1864
2183
|
HEADING_MARK_CLASS_NAME,
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
2184
|
+
FRONTMATTER_LABEL_CLASS_NAME,
|
|
2185
|
+
IMAGE_FRAME_CLASS_NAME2,
|
|
2186
|
+
IMAGE_FALLBACK_CLASS_NAME2,
|
|
2187
|
+
IMAGE_FALLBACK_ICON_CLASS_NAME2,
|
|
2188
|
+
IMAGE_FALLBACK_TEXT_CLASS_NAME2,
|
|
2189
|
+
IMAGE_FALLBACK_SOURCE_CLASS_NAME2,
|
|
2190
|
+
VIDEO_FRAME_CLASS_NAME2,
|
|
2191
|
+
VIDEO_FALLBACK_CLASS_NAME2,
|
|
2192
|
+
VIDEO_FALLBACK_ICON_CLASS_NAME2,
|
|
2193
|
+
VIDEO_FALLBACK_TEXT_CLASS_NAME2,
|
|
2194
|
+
VIDEO_FALLBACK_SOURCE_CLASS_NAME2
|
|
1875
2195
|
]
|
|
1876
2196
|
];
|
|
1877
2197
|
const figureAttributes = [
|
|
1878
|
-
["className",
|
|
2198
|
+
["className", IMAGE_FIGURE_CLASS_NAME2, VIDEO_FIGURE_CLASS_NAME2]
|
|
1879
2199
|
];
|
|
1880
|
-
const figcaptionAttributes = [["className",
|
|
2200
|
+
const figcaptionAttributes = [["className", IMAGE_CAPTION_CLASS_NAME2]];
|
|
1881
2201
|
const imageAttributes = [
|
|
1882
2202
|
...defaultAttributes.img ?? [],
|
|
1883
|
-
["className",
|
|
2203
|
+
["className", IMAGE_ELEMENT_CLASS_NAME2]
|
|
1884
2204
|
];
|
|
1885
2205
|
const videoAttributes = [
|
|
1886
2206
|
"src",
|
|
@@ -1895,13 +2215,17 @@ function createScribdownSanitizeSchema() {
|
|
|
1895
2215
|
"loop",
|
|
1896
2216
|
"autoPlay",
|
|
1897
2217
|
"crossOrigin",
|
|
1898
|
-
["className",
|
|
2218
|
+
["className", VIDEO_ELEMENT_CLASS_NAME2]
|
|
1899
2219
|
];
|
|
1900
2220
|
const sourceAttributes = ["src", "type", "media"];
|
|
1901
2221
|
const abbrAttributes = ["title"];
|
|
1902
2222
|
const listItemAttributes = [
|
|
1903
2223
|
"dataTocIndex",
|
|
1904
|
-
[
|
|
2224
|
+
[
|
|
2225
|
+
"className",
|
|
2226
|
+
"task-list-item",
|
|
2227
|
+
new RegExp(`^${TOC_ITEM_CLASS_PREFIX}(?:--(?:branch|depth-[1-6]))?$`, "u")
|
|
2228
|
+
]
|
|
1905
2229
|
];
|
|
1906
2230
|
const wildcardAttributes = [...defaultAttributes["*"] ?? [], SOURCE_LINE_HAST_PROPERTY];
|
|
1907
2231
|
return {
|
|
@@ -1940,6 +2264,8 @@ function createScribdownSanitizeSchema() {
|
|
|
1940
2264
|
a: linkAttributes,
|
|
1941
2265
|
ol: orderedListAttributes,
|
|
1942
2266
|
li: listItemAttributes,
|
|
2267
|
+
div: divAttributes,
|
|
2268
|
+
dl: definitionListAttributes,
|
|
1943
2269
|
span: spanAttributes,
|
|
1944
2270
|
abbr: abbrAttributes,
|
|
1945
2271
|
figure: figureAttributes,
|
|
@@ -1950,41 +2276,235 @@ function createScribdownSanitizeSchema() {
|
|
|
1950
2276
|
}
|
|
1951
2277
|
};
|
|
1952
2278
|
}
|
|
1953
|
-
|
|
2279
|
+
|
|
2280
|
+
// src/syntax/definition-lists.ts
|
|
2281
|
+
var DEFINITION_LIST_PARAGRAPH_PATTERN = /^\s*([^\n:][^\n]*)\n\s*:\s+([^\n]+)\s*$/u;
|
|
2282
|
+
function remarkDefinitionLists() {
|
|
1954
2283
|
return (tree) => {
|
|
1955
|
-
|
|
2284
|
+
transformDefinitionLists(tree);
|
|
1956
2285
|
};
|
|
1957
2286
|
}
|
|
1958
|
-
|
|
1959
|
-
|
|
2287
|
+
function transformDefinitionLists(node) {
|
|
2288
|
+
if (!node.children) {
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
const childNodes = node.children;
|
|
2292
|
+
for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {
|
|
2293
|
+
const childNode = childNodes[childIndex];
|
|
2294
|
+
const definitionListNode = createDefinitionListNode(childNode);
|
|
2295
|
+
if (definitionListNode) {
|
|
2296
|
+
childNodes[childIndex] = definitionListNode;
|
|
2297
|
+
continue;
|
|
2298
|
+
}
|
|
2299
|
+
transformDefinitionLists(childNode);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
function createDefinitionListNode(node) {
|
|
2303
|
+
if (node.type !== "paragraph") {
|
|
2304
|
+
return void 0;
|
|
2305
|
+
}
|
|
2306
|
+
const paragraphText = extractNodeText(node);
|
|
2307
|
+
const definitionMatch = DEFINITION_LIST_PARAGRAPH_PATTERN.exec(paragraphText);
|
|
2308
|
+
if (!definitionMatch) {
|
|
2309
|
+
return void 0;
|
|
2310
|
+
}
|
|
2311
|
+
const definitionTerm = definitionMatch[1].trim();
|
|
2312
|
+
const definitionDescription = definitionMatch[2].trim();
|
|
2313
|
+
if (definitionTerm.length === 0 || definitionDescription.length === 0) {
|
|
2314
|
+
return void 0;
|
|
2315
|
+
}
|
|
2316
|
+
return {
|
|
2317
|
+
type: "definitionList",
|
|
2318
|
+
// 关键步骤:保留原段落源码位置,使 remarkSourceLine 能为 dl 标注 data-source-line。
|
|
2319
|
+
position: node.position,
|
|
2320
|
+
data: {
|
|
2321
|
+
hName: "dl"
|
|
2322
|
+
},
|
|
2323
|
+
children: [
|
|
2324
|
+
{
|
|
2325
|
+
type: "definitionTerm",
|
|
2326
|
+
data: {
|
|
2327
|
+
hName: "dt"
|
|
2328
|
+
},
|
|
2329
|
+
children: [{ type: "text", value: definitionTerm }]
|
|
2330
|
+
},
|
|
2331
|
+
{
|
|
2332
|
+
type: "definitionDescription",
|
|
2333
|
+
data: {
|
|
2334
|
+
hName: "dd"
|
|
2335
|
+
},
|
|
2336
|
+
children: [{ type: "text", value: definitionDescription }]
|
|
2337
|
+
}
|
|
2338
|
+
]
|
|
2339
|
+
};
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
// src/syntax/frame.ts
|
|
2343
|
+
import { DETAILS_CLASS_NAME as DETAILS_CLASS_NAME2, FRAME_CLASS_NAME as FRAME_CLASS_NAME2, TOC_CLASS_NAME as TOC_CLASS_NAME2 } from "@scribdown/shared";
|
|
2344
|
+
function rehypeFrameClass() {
|
|
1960
2345
|
return (tree) => {
|
|
1961
|
-
|
|
1962
|
-
blockNodes.forEach((blockNode) => {
|
|
1963
|
-
annotateSourceLine(blockNode);
|
|
1964
|
-
annotateNestedBlockSourceLines(blockNode);
|
|
1965
|
-
});
|
|
2346
|
+
tagContentDetails(tree);
|
|
1966
2347
|
};
|
|
1967
2348
|
}
|
|
1968
|
-
function
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
2349
|
+
function tagContentDetails(node) {
|
|
2350
|
+
if (node.type === "element" && node.tagName === "details") {
|
|
2351
|
+
const classNames = normalizeClassNames(node.properties?.className);
|
|
2352
|
+
const isTocDetails = classNames.includes(TOC_CLASS_NAME2);
|
|
2353
|
+
if (!isTocDetails) {
|
|
2354
|
+
addClassNames(node, [DETAILS_CLASS_NAME2, FRAME_CLASS_NAME2]);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
if (Array.isArray(node.children)) {
|
|
2358
|
+
for (const childNode of node.children) {
|
|
2359
|
+
tagContentDetails(childNode);
|
|
2360
|
+
}
|
|
1972
2361
|
}
|
|
1973
|
-
const nodeData = node.data ?? {};
|
|
1974
|
-
const hProperties = nodeData.hProperties ?? {};
|
|
1975
|
-
hProperties[SOURCE_LINE_HAST_PROPERTY] = startLine;
|
|
1976
|
-
nodeData.hProperties = hProperties;
|
|
1977
|
-
node.data = nodeData;
|
|
1978
2362
|
}
|
|
1979
|
-
function
|
|
1980
|
-
if (
|
|
1981
|
-
return;
|
|
2363
|
+
function normalizeClassNames(className) {
|
|
2364
|
+
if (Array.isArray(className)) {
|
|
2365
|
+
return className.filter((item) => typeof item === "string");
|
|
1982
2366
|
}
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
2367
|
+
if (typeof className === "string") {
|
|
2368
|
+
return className.split(/\s+/u).filter(Boolean);
|
|
2369
|
+
}
|
|
2370
|
+
return [];
|
|
2371
|
+
}
|
|
2372
|
+
function addClassNames(node, classNamesToAdd) {
|
|
2373
|
+
const mergedClassNames = normalizeClassNames(node.properties?.className);
|
|
2374
|
+
for (const classNameToAdd of classNamesToAdd) {
|
|
2375
|
+
if (!mergedClassNames.includes(classNameToAdd)) {
|
|
2376
|
+
mergedClassNames.push(classNameToAdd);
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
node.properties = { ...node.properties, className: mergedClassNames };
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
// src/syntax/frontmatter.ts
|
|
2383
|
+
import {
|
|
2384
|
+
FRAME_CLASS_NAME as FRAME_CLASS_NAME3,
|
|
2385
|
+
FRONTMATTER_CHROME_CLASS_NAME as FRONTMATTER_CHROME_CLASS_NAME2,
|
|
2386
|
+
FRONTMATTER_CLASS_NAME as FRONTMATTER_CLASS_NAME2,
|
|
2387
|
+
FRONTMATTER_LABEL_CLASS_NAME as FRONTMATTER_LABEL_CLASS_NAME2,
|
|
2388
|
+
FRONTMATTER_LIST_CLASS_NAME as FRONTMATTER_LIST_CLASS_NAME2,
|
|
2389
|
+
FRONTMATTER_LIST_NESTED_CLASS_NAME as FRONTMATTER_LIST_NESTED_CLASS_NAME2
|
|
2390
|
+
} from "@scribdown/shared";
|
|
2391
|
+
import { parse as parseYamlSource } from "yaml";
|
|
2392
|
+
var FRONTMATTER_LABEL_TEXT = "\u5143\u6570\u636E";
|
|
2393
|
+
var FRONTMATTER_ARRAY_SEPARATOR = ", ";
|
|
2394
|
+
var FRONTMATTER_FALLBACK_LANGUAGE_ID = "yaml";
|
|
2395
|
+
function remarkFrontmatterMetadata() {
|
|
2396
|
+
return (tree) => {
|
|
2397
|
+
const childNodes = tree.children ?? [];
|
|
2398
|
+
const yamlNode = childNodes[0];
|
|
2399
|
+
if (!yamlNode || yamlNode.type !== "yaml" || typeof yamlNode.value !== "string") {
|
|
2400
|
+
return;
|
|
2401
|
+
}
|
|
2402
|
+
childNodes[0] = createFrontmatterNode(yamlNode);
|
|
2403
|
+
};
|
|
2404
|
+
}
|
|
2405
|
+
function createFrontmatterNode(yamlNode) {
|
|
2406
|
+
const yamlSource = yamlNode.value ?? "";
|
|
2407
|
+
let parsedValue;
|
|
2408
|
+
try {
|
|
2409
|
+
parsedValue = parseYamlSource(yamlSource);
|
|
2410
|
+
} catch {
|
|
2411
|
+
parsedValue = void 0;
|
|
2412
|
+
}
|
|
2413
|
+
if (!isPlainRecord(parsedValue)) {
|
|
2414
|
+
return {
|
|
2415
|
+
type: "code",
|
|
2416
|
+
lang: FRONTMATTER_FALLBACK_LANGUAGE_ID,
|
|
2417
|
+
position: yamlNode.position,
|
|
2418
|
+
value: yamlSource
|
|
2419
|
+
};
|
|
2420
|
+
}
|
|
2421
|
+
return {
|
|
2422
|
+
type: "frontmatterMetadata",
|
|
2423
|
+
// 关键步骤:保留原节点源码位置,使 remarkSourceLine 能标注 data-source-line。
|
|
2424
|
+
position: yamlNode.position,
|
|
2425
|
+
data: {
|
|
2426
|
+
hName: "div",
|
|
2427
|
+
hProperties: { className: [FRONTMATTER_CLASS_NAME2, FRAME_CLASS_NAME3] }
|
|
2428
|
+
},
|
|
2429
|
+
children: [
|
|
2430
|
+
{
|
|
2431
|
+
type: "frontmatterChrome",
|
|
2432
|
+
data: {
|
|
2433
|
+
hName: "div",
|
|
2434
|
+
hProperties: { className: [FRONTMATTER_CHROME_CLASS_NAME2] }
|
|
2435
|
+
},
|
|
2436
|
+
children: [
|
|
2437
|
+
{
|
|
2438
|
+
type: "frontmatterLabel",
|
|
2439
|
+
data: {
|
|
2440
|
+
hName: "span",
|
|
2441
|
+
hProperties: { className: [FRONTMATTER_LABEL_CLASS_NAME2] }
|
|
2442
|
+
},
|
|
2443
|
+
children: [{ type: "text", value: FRONTMATTER_LABEL_TEXT }]
|
|
2444
|
+
}
|
|
2445
|
+
]
|
|
2446
|
+
},
|
|
2447
|
+
createFrontmatterListNode(parsedValue, false)
|
|
2448
|
+
]
|
|
2449
|
+
};
|
|
2450
|
+
}
|
|
2451
|
+
function isPlainRecord(value) {
|
|
2452
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2453
|
+
}
|
|
2454
|
+
function createFrontmatterListNode(record, nested) {
|
|
2455
|
+
const listClassNames = nested ? [FRONTMATTER_LIST_CLASS_NAME2, FRONTMATTER_LIST_NESTED_CLASS_NAME2] : [FRONTMATTER_LIST_CLASS_NAME2];
|
|
2456
|
+
const listChildren = [];
|
|
2457
|
+
Object.entries(record).forEach(([entryKey, entryValue]) => {
|
|
2458
|
+
listChildren.push({
|
|
2459
|
+
type: "frontmatterTerm",
|
|
2460
|
+
data: { hName: "dt" },
|
|
2461
|
+
children: [{ type: "text", value: entryKey }]
|
|
2462
|
+
});
|
|
2463
|
+
listChildren.push({
|
|
2464
|
+
type: "frontmatterDescription",
|
|
2465
|
+
data: { hName: "dd" },
|
|
2466
|
+
children: createFrontmatterValueNodes(entryValue)
|
|
2467
|
+
});
|
|
1987
2468
|
});
|
|
2469
|
+
return {
|
|
2470
|
+
type: "frontmatterList",
|
|
2471
|
+
data: {
|
|
2472
|
+
hName: "dl",
|
|
2473
|
+
hProperties: { className: listClassNames }
|
|
2474
|
+
},
|
|
2475
|
+
children: listChildren
|
|
2476
|
+
};
|
|
2477
|
+
}
|
|
2478
|
+
function createFrontmatterValueNodes(value) {
|
|
2479
|
+
if (isPlainRecord(value)) {
|
|
2480
|
+
return [createFrontmatterListNode(value, true)];
|
|
2481
|
+
}
|
|
2482
|
+
if (Array.isArray(value)) {
|
|
2483
|
+
return [
|
|
2484
|
+
{
|
|
2485
|
+
type: "text",
|
|
2486
|
+
value: value.map(formatFrontmatterScalar).join(FRONTMATTER_ARRAY_SEPARATOR)
|
|
2487
|
+
}
|
|
2488
|
+
];
|
|
2489
|
+
}
|
|
2490
|
+
return [{ type: "text", value: formatFrontmatterScalar(value) }];
|
|
2491
|
+
}
|
|
2492
|
+
function formatFrontmatterScalar(value) {
|
|
2493
|
+
if (value === null || value === void 0) {
|
|
2494
|
+
return "";
|
|
2495
|
+
}
|
|
2496
|
+
if (typeof value === "object") {
|
|
2497
|
+
return JSON.stringify(value);
|
|
2498
|
+
}
|
|
2499
|
+
return String(value);
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
// src/syntax/highlight-mark.ts
|
|
2503
|
+
var HIGHLIGHT_MARKER_PATTERN = /==([^=\n]+?)==/g;
|
|
2504
|
+
function remarkHighlightMark() {
|
|
2505
|
+
return (tree) => {
|
|
2506
|
+
transformHighlightMarks(tree);
|
|
2507
|
+
};
|
|
1988
2508
|
}
|
|
1989
2509
|
function transformHighlightMarks(node) {
|
|
1990
2510
|
if (!node.children) {
|
|
@@ -2028,197 +2548,30 @@ function splitHighlightMarks(textValue) {
|
|
|
2028
2548
|
}
|
|
2029
2549
|
return segments;
|
|
2030
2550
|
}
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
const paragraphText = extractNodeText(node);
|
|
2056
|
-
const definitionMatch = DEFINITION_LIST_PARAGRAPH_PATTERN.exec(paragraphText);
|
|
2057
|
-
if (!definitionMatch) {
|
|
2058
|
-
return void 0;
|
|
2059
|
-
}
|
|
2060
|
-
const definitionTerm = definitionMatch[1].trim();
|
|
2061
|
-
const definitionDescription = definitionMatch[2].trim();
|
|
2062
|
-
if (definitionTerm.length === 0 || definitionDescription.length === 0) {
|
|
2063
|
-
return void 0;
|
|
2064
|
-
}
|
|
2065
|
-
return {
|
|
2066
|
-
type: "definitionList",
|
|
2067
|
-
// 关键步骤:保留原段落源码位置,使 remarkSourceLine 能为 dl 标注 data-source-line。
|
|
2068
|
-
position: node.position,
|
|
2069
|
-
data: {
|
|
2070
|
-
hName: "dl"
|
|
2071
|
-
},
|
|
2072
|
-
children: [
|
|
2073
|
-
{
|
|
2074
|
-
type: "definitionTerm",
|
|
2075
|
-
data: {
|
|
2076
|
-
hName: "dt"
|
|
2077
|
-
},
|
|
2078
|
-
children: [{ type: "text", value: definitionTerm }]
|
|
2079
|
-
},
|
|
2080
|
-
{
|
|
2081
|
-
type: "definitionDescription",
|
|
2082
|
-
data: {
|
|
2083
|
-
hName: "dd"
|
|
2084
|
-
},
|
|
2085
|
-
children: [{ type: "text", value: definitionDescription }]
|
|
2086
|
-
}
|
|
2087
|
-
]
|
|
2088
|
-
};
|
|
2089
|
-
}
|
|
2090
|
-
function remarkImageFigures() {
|
|
2091
|
-
return (tree) => {
|
|
2092
|
-
transformImageFigures(tree);
|
|
2093
|
-
};
|
|
2094
|
-
}
|
|
2095
|
-
function transformImageFigures(node) {
|
|
2096
|
-
if (!node.children) {
|
|
2097
|
-
return;
|
|
2098
|
-
}
|
|
2099
|
-
const childNodes = node.children;
|
|
2100
|
-
for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {
|
|
2101
|
-
const childNode = childNodes[childIndex];
|
|
2102
|
-
const imageFigureNode = createImageFigureNode(childNode);
|
|
2103
|
-
if (imageFigureNode) {
|
|
2104
|
-
childNodes[childIndex] = imageFigureNode;
|
|
2105
|
-
continue;
|
|
2106
|
-
}
|
|
2107
|
-
if (isImageNode(childNode)) {
|
|
2108
|
-
decorateImageNode(childNode);
|
|
2109
|
-
continue;
|
|
2110
|
-
}
|
|
2111
|
-
transformImageFigures(childNode);
|
|
2112
|
-
}
|
|
2113
|
-
}
|
|
2114
|
-
function createImageFigureNode(node) {
|
|
2115
|
-
if (node.type !== "paragraph" || !node.children || node.children.length !== 1) {
|
|
2116
|
-
return void 0;
|
|
2117
|
-
}
|
|
2118
|
-
const onlyChild = node.children[0];
|
|
2119
|
-
if (!isImageNode(onlyChild)) {
|
|
2120
|
-
return void 0;
|
|
2121
|
-
}
|
|
2122
|
-
decorateImageNode(onlyChild);
|
|
2123
|
-
return {
|
|
2124
|
-
type: "imageFigure",
|
|
2125
|
-
// 关键步骤:保留原段落源码位置,使 remarkSourceLine 能为 figure 标注 data-source-line。
|
|
2126
|
-
position: node.position,
|
|
2127
|
-
data: {
|
|
2128
|
-
hName: "figure",
|
|
2129
|
-
hProperties: {
|
|
2130
|
-
className: [IMAGE_FIGURE_CLASS_NAME]
|
|
2131
|
-
}
|
|
2132
|
-
},
|
|
2133
|
-
children: [createImageFrameNode(onlyChild), ...createImageCaptionNodes(onlyChild)]
|
|
2134
|
-
};
|
|
2135
|
-
}
|
|
2136
|
-
function decorateImageNode(imageNode) {
|
|
2137
|
-
imageNode.data = {
|
|
2138
|
-
...imageNode.data,
|
|
2139
|
-
hProperties: {
|
|
2140
|
-
...imageNode.data?.hProperties,
|
|
2141
|
-
className: [IMAGE_ELEMENT_CLASS_NAME]
|
|
2142
|
-
}
|
|
2143
|
-
};
|
|
2144
|
-
}
|
|
2145
|
-
function createImageFrameNode(imageNode) {
|
|
2146
|
-
return {
|
|
2147
|
-
type: "imageFrame",
|
|
2148
|
-
data: {
|
|
2149
|
-
hName: "span",
|
|
2150
|
-
hProperties: {
|
|
2151
|
-
className: [IMAGE_FRAME_CLASS_NAME]
|
|
2152
|
-
}
|
|
2153
|
-
},
|
|
2154
|
-
children: [imageNode, createImageFallbackNode(imageNode)]
|
|
2155
|
-
};
|
|
2156
|
-
}
|
|
2157
|
-
function createImageCaptionNodes(imageNode) {
|
|
2158
|
-
const imageTitle = typeof imageNode.title === "string" ? imageNode.title.trim() : "";
|
|
2159
|
-
if (imageTitle.length === 0) {
|
|
2160
|
-
return [];
|
|
2161
|
-
}
|
|
2162
|
-
return [
|
|
2163
|
-
{
|
|
2164
|
-
type: "imageCaption",
|
|
2165
|
-
data: {
|
|
2166
|
-
hName: "figcaption",
|
|
2167
|
-
hProperties: {
|
|
2168
|
-
className: [IMAGE_CAPTION_CLASS_NAME]
|
|
2169
|
-
}
|
|
2170
|
-
},
|
|
2171
|
-
children: [{ type: "text", value: imageTitle }]
|
|
2172
|
-
}
|
|
2173
|
-
];
|
|
2174
|
-
}
|
|
2175
|
-
function createImageFallbackNode(imageNode) {
|
|
2176
|
-
const fallbackText = imageNode.alt?.trim() || "\u56FE\u7247\u52A0\u8F7D\u5931\u8D25";
|
|
2177
|
-
const fallbackSource = imageNode.url ?? imageNode.identifier ?? "";
|
|
2178
|
-
return {
|
|
2179
|
-
type: "imageFallback",
|
|
2180
|
-
data: {
|
|
2181
|
-
hName: "span",
|
|
2182
|
-
hProperties: {
|
|
2183
|
-
className: [IMAGE_FALLBACK_CLASS_NAME]
|
|
2184
|
-
}
|
|
2185
|
-
},
|
|
2186
|
-
children: [
|
|
2187
|
-
{
|
|
2188
|
-
type: "imageFallbackIcon",
|
|
2189
|
-
data: {
|
|
2190
|
-
hName: "span",
|
|
2191
|
-
hProperties: {
|
|
2192
|
-
className: [IMAGE_FALLBACK_ICON_CLASS_NAME]
|
|
2193
|
-
}
|
|
2194
|
-
}
|
|
2195
|
-
},
|
|
2196
|
-
{
|
|
2197
|
-
type: "imageFallbackText",
|
|
2198
|
-
data: {
|
|
2199
|
-
hName: "span",
|
|
2200
|
-
hProperties: {
|
|
2201
|
-
className: [IMAGE_FALLBACK_TEXT_CLASS_NAME]
|
|
2202
|
-
}
|
|
2203
|
-
},
|
|
2204
|
-
children: [{ type: "text", value: fallbackText }]
|
|
2205
|
-
},
|
|
2206
|
-
{
|
|
2207
|
-
type: "imageFallbackSource",
|
|
2208
|
-
data: {
|
|
2209
|
-
hName: "span",
|
|
2210
|
-
hProperties: {
|
|
2211
|
-
className: [IMAGE_FALLBACK_SOURCE_CLASS_NAME]
|
|
2212
|
-
}
|
|
2213
|
-
},
|
|
2214
|
-
children: [{ type: "text", value: fallbackSource }]
|
|
2215
|
-
}
|
|
2216
|
-
]
|
|
2217
|
-
};
|
|
2218
|
-
}
|
|
2219
|
-
function isImageNode(node) {
|
|
2220
|
-
return node.type === "image" || node.type === "imageReference";
|
|
2221
|
-
}
|
|
2551
|
+
|
|
2552
|
+
// src/syntax/toc.ts
|
|
2553
|
+
import {
|
|
2554
|
+
FRAME_CLASS_NAME as FRAME_CLASS_NAME4,
|
|
2555
|
+
HEADING_MARK_CLASS_NAME as HEADING_MARK_CLASS_NAME2,
|
|
2556
|
+
SCRIBDOWN_MARKDOWN_CLASS_NAME,
|
|
2557
|
+
TOC_CLASS_NAME as TOC_CLASS_NAME3,
|
|
2558
|
+
TOC_ITEM_BRANCH_CLASS_NAME,
|
|
2559
|
+
TOC_ITEM_CLASS_PREFIX as TOC_ITEM_CLASS_PREFIX2,
|
|
2560
|
+
TOC_ITEM_COLLAPSED_CLASS_NAME,
|
|
2561
|
+
TOC_LINK_CLASS_NAME as TOC_LINK_CLASS_NAME2,
|
|
2562
|
+
TOC_LIST_CLASS_NAME as TOC_LIST_CLASS_NAME2,
|
|
2563
|
+
TOC_LIST_NESTED_CLASS_NAME as TOC_LIST_NESTED_CLASS_NAME2,
|
|
2564
|
+
TOC_NAV_CLASS_NAME as TOC_NAV_CLASS_NAME2,
|
|
2565
|
+
TOC_SUMMARY_CLASS_NAME as TOC_SUMMARY_CLASS_NAME2,
|
|
2566
|
+
TOC_TOGGLE_CLASS_NAME as TOC_TOGGLE_CLASS_NAME2
|
|
2567
|
+
} from "@scribdown/shared";
|
|
2568
|
+
var TOC_MARKER_PATTERN = /^\s*\[toc]\s*$/i;
|
|
2569
|
+
var TOC_TOGGLE_ARIA_LABEL = "\u5C55\u5F00\u6216\u6298\u53E0\u5B50\u76EE\u5F55";
|
|
2570
|
+
var TOC_TOGGLE_HYDRATED_DATA_KEY = "scribdownTocToggleHydrated";
|
|
2571
|
+
var TOC_LINK_HYDRATED_DATA_KEY = "scribdownTocLinkHydrated";
|
|
2572
|
+
var TOC_ARIA_LABEL = "\u76EE\u5F55";
|
|
2573
|
+
var TOC_SUMMARY_TEXT = "\u76EE\u5F55";
|
|
2574
|
+
var EMPTY_HEADING_SLUG_PREFIX = "section";
|
|
2222
2575
|
function remarkTableOfContents() {
|
|
2223
2576
|
return (tree) => {
|
|
2224
2577
|
const tocHeadings = collectTocHeadings(tree);
|
|
@@ -2256,7 +2609,7 @@ function collectTocHeadings(tree) {
|
|
|
2256
2609
|
data: {
|
|
2257
2610
|
hName: "span",
|
|
2258
2611
|
hProperties: {
|
|
2259
|
-
className: [
|
|
2612
|
+
className: [HEADING_MARK_CLASS_NAME2]
|
|
2260
2613
|
}
|
|
2261
2614
|
},
|
|
2262
2615
|
children: node.children ?? []
|
|
@@ -2271,16 +2624,6 @@ function collectTocHeadings(tree) {
|
|
|
2271
2624
|
});
|
|
2272
2625
|
return tocHeadings;
|
|
2273
2626
|
}
|
|
2274
|
-
function visitMarkdownNode(node, visitor) {
|
|
2275
|
-
visitor(node);
|
|
2276
|
-
if (!node.children) {
|
|
2277
|
-
return;
|
|
2278
|
-
}
|
|
2279
|
-
const childNodes = node.children;
|
|
2280
|
-
for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {
|
|
2281
|
-
visitMarkdownNode(childNodes[childIndex], visitor);
|
|
2282
|
-
}
|
|
2283
|
-
}
|
|
2284
2627
|
function replaceTocMarkers(node, tocHeadings) {
|
|
2285
2628
|
if (!node.children) {
|
|
2286
2629
|
return;
|
|
@@ -2312,7 +2655,7 @@ function createTocNode(tocHeadings, markerNode) {
|
|
|
2312
2655
|
hName: "details",
|
|
2313
2656
|
hProperties: {
|
|
2314
2657
|
// 关键步骤:目录根 opt-in 手绘边框,再由 toc.css 叠加目录自身样式。
|
|
2315
|
-
className: [
|
|
2658
|
+
className: [TOC_CLASS_NAME3, FRAME_CLASS_NAME4]
|
|
2316
2659
|
}
|
|
2317
2660
|
},
|
|
2318
2661
|
children: [
|
|
@@ -2321,7 +2664,7 @@ function createTocNode(tocHeadings, markerNode) {
|
|
|
2321
2664
|
data: {
|
|
2322
2665
|
hName: "summary",
|
|
2323
2666
|
hProperties: {
|
|
2324
|
-
className: [
|
|
2667
|
+
className: [TOC_SUMMARY_CLASS_NAME2]
|
|
2325
2668
|
}
|
|
2326
2669
|
},
|
|
2327
2670
|
children: [
|
|
@@ -2337,7 +2680,7 @@ function createTocNode(tocHeadings, markerNode) {
|
|
|
2337
2680
|
hName: "nav",
|
|
2338
2681
|
hProperties: {
|
|
2339
2682
|
ariaLabel: TOC_ARIA_LABEL,
|
|
2340
|
-
className: [
|
|
2683
|
+
className: [TOC_NAV_CLASS_NAME2]
|
|
2341
2684
|
}
|
|
2342
2685
|
},
|
|
2343
2686
|
children: [createTocListNode(tocTree, false)]
|
|
@@ -2367,7 +2710,7 @@ function createTocTree(tocHeadings) {
|
|
|
2367
2710
|
return rootItems;
|
|
2368
2711
|
}
|
|
2369
2712
|
function createTocListNode(tocItems, isNested) {
|
|
2370
|
-
const tocListClassNames = isNested ? [
|
|
2713
|
+
const tocListClassNames = isNested ? [TOC_LIST_CLASS_NAME2, TOC_LIST_NESTED_CLASS_NAME2] : [TOC_LIST_CLASS_NAME2];
|
|
2371
2714
|
return {
|
|
2372
2715
|
type: "list",
|
|
2373
2716
|
ordered: true,
|
|
@@ -2398,8 +2741,8 @@ function createTocListItem(tocItem) {
|
|
|
2398
2741
|
}
|
|
2399
2742
|
function createTocItemClassNames(depth, hasChildren) {
|
|
2400
2743
|
return [
|
|
2401
|
-
|
|
2402
|
-
`${
|
|
2744
|
+
TOC_ITEM_CLASS_PREFIX2,
|
|
2745
|
+
`${TOC_ITEM_CLASS_PREFIX2}--depth-${depth}`,
|
|
2403
2746
|
...hasChildren ? [TOC_ITEM_BRANCH_CLASS_NAME] : []
|
|
2404
2747
|
];
|
|
2405
2748
|
}
|
|
@@ -2410,7 +2753,7 @@ function createTocToggleNode() {
|
|
|
2410
2753
|
hName: "button",
|
|
2411
2754
|
hProperties: {
|
|
2412
2755
|
type: "button",
|
|
2413
|
-
className: [
|
|
2756
|
+
className: [TOC_TOGGLE_CLASS_NAME2],
|
|
2414
2757
|
ariaExpanded: "true",
|
|
2415
2758
|
ariaLabel: TOC_TOGGLE_ARIA_LABEL
|
|
2416
2759
|
}
|
|
@@ -2418,22 +2761,49 @@ function createTocToggleNode() {
|
|
|
2418
2761
|
children: []
|
|
2419
2762
|
};
|
|
2420
2763
|
}
|
|
2421
|
-
function createTocLinkNode(tocItem) {
|
|
2422
|
-
return {
|
|
2423
|
-
type: "link",
|
|
2424
|
-
url: `#${tocItem.id}`,
|
|
2425
|
-
data: {
|
|
2426
|
-
hProperties: {
|
|
2427
|
-
className: [
|
|
2428
|
-
}
|
|
2429
|
-
},
|
|
2430
|
-
children: [
|
|
2431
|
-
{
|
|
2432
|
-
type: "text",
|
|
2433
|
-
value: tocItem.text
|
|
2434
|
-
}
|
|
2435
|
-
]
|
|
2436
|
-
};
|
|
2764
|
+
function createTocLinkNode(tocItem) {
|
|
2765
|
+
return {
|
|
2766
|
+
type: "link",
|
|
2767
|
+
url: `#${tocItem.id}`,
|
|
2768
|
+
data: {
|
|
2769
|
+
hProperties: {
|
|
2770
|
+
className: [TOC_LINK_CLASS_NAME2]
|
|
2771
|
+
}
|
|
2772
|
+
},
|
|
2773
|
+
children: [
|
|
2774
|
+
{
|
|
2775
|
+
type: "text",
|
|
2776
|
+
value: tocItem.text
|
|
2777
|
+
}
|
|
2778
|
+
]
|
|
2779
|
+
};
|
|
2780
|
+
}
|
|
2781
|
+
function createUniqueSlug(headingText, slugCounts, fallbackIndex) {
|
|
2782
|
+
const normalizedSlug = normalizeSlugText(headingText);
|
|
2783
|
+
const fallbackSlug = `${EMPTY_HEADING_SLUG_PREFIX}-${fallbackIndex}`;
|
|
2784
|
+
const baseSlug = normalizedSlug || fallbackSlug;
|
|
2785
|
+
const usedCount = slugCounts.get(baseSlug) ?? 0;
|
|
2786
|
+
slugCounts.set(baseSlug, usedCount + 1);
|
|
2787
|
+
if (usedCount === 0) {
|
|
2788
|
+
return baseSlug;
|
|
2789
|
+
}
|
|
2790
|
+
return `${baseSlug}-${usedCount}`;
|
|
2791
|
+
}
|
|
2792
|
+
function createHeadingIndex(headingDepth, headingIndexCounts, rootHeadingDepth) {
|
|
2793
|
+
const startDepth = Math.min(rootHeadingDepth, headingDepth);
|
|
2794
|
+
headingIndexCounts[headingDepth] += 1;
|
|
2795
|
+
for (let depthIndex = headingDepth + 1; depthIndex < headingIndexCounts.length; depthIndex += 1) {
|
|
2796
|
+
headingIndexCounts[depthIndex] = 0;
|
|
2797
|
+
}
|
|
2798
|
+
for (let depthIndex = startDepth; depthIndex < headingDepth; depthIndex += 1) {
|
|
2799
|
+
if (headingIndexCounts[depthIndex] === 0) {
|
|
2800
|
+
headingIndexCounts[depthIndex] = 1;
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
return headingIndexCounts.slice(startDepth, headingDepth + 1).join(".");
|
|
2804
|
+
}
|
|
2805
|
+
function normalizeSlugText(headingText) {
|
|
2806
|
+
return headingText.trim().toLowerCase().replace(/<[^>]+>/g, "").replace(/[^\p{L}\p{N}\s_-]+/gu, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
2437
2807
|
}
|
|
2438
2808
|
function collectTocHeadingsFromDom(rootElement) {
|
|
2439
2809
|
const markdownContainer = rootElement instanceof Element && rootElement.matches(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME}`) ? rootElement : rootElement.querySelector(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME}`);
|
|
@@ -2473,13 +2843,13 @@ function collectTocHeadingsFromDom(rootElement) {
|
|
|
2473
2843
|
function createTocNavElement(ownerDocument, tocTreeItems) {
|
|
2474
2844
|
const navElement = ownerDocument.createElement("nav");
|
|
2475
2845
|
navElement.setAttribute("aria-label", TOC_ARIA_LABEL);
|
|
2476
|
-
navElement.className =
|
|
2846
|
+
navElement.className = TOC_NAV_CLASS_NAME2;
|
|
2477
2847
|
navElement.appendChild(createTocListElement(ownerDocument, tocTreeItems, false));
|
|
2478
2848
|
return navElement;
|
|
2479
2849
|
}
|
|
2480
2850
|
function createTocListElement(ownerDocument, tocItems, isNested) {
|
|
2481
2851
|
const listElement = ownerDocument.createElement("ol");
|
|
2482
|
-
listElement.className = isNested ? `${
|
|
2852
|
+
listElement.className = isNested ? `${TOC_LIST_CLASS_NAME2} ${TOC_LIST_NESTED_CLASS_NAME2}` : TOC_LIST_CLASS_NAME2;
|
|
2483
2853
|
for (const tocItem of tocItems) {
|
|
2484
2854
|
listElement.appendChild(createTocListItemElement(ownerDocument, tocItem));
|
|
2485
2855
|
}
|
|
@@ -2493,13 +2863,13 @@ function createTocListItemElement(ownerDocument, tocItem) {
|
|
|
2493
2863
|
if (hasChildren) {
|
|
2494
2864
|
const toggleElement = ownerDocument.createElement("button");
|
|
2495
2865
|
toggleElement.type = "button";
|
|
2496
|
-
toggleElement.className =
|
|
2866
|
+
toggleElement.className = TOC_TOGGLE_CLASS_NAME2;
|
|
2497
2867
|
toggleElement.setAttribute("aria-expanded", "true");
|
|
2498
2868
|
toggleElement.setAttribute("aria-label", TOC_TOGGLE_ARIA_LABEL);
|
|
2499
2869
|
itemElement.appendChild(toggleElement);
|
|
2500
2870
|
}
|
|
2501
2871
|
const linkElement = ownerDocument.createElement("a");
|
|
2502
|
-
linkElement.className =
|
|
2872
|
+
linkElement.className = TOC_LINK_CLASS_NAME2;
|
|
2503
2873
|
linkElement.href = `#${tocItem.id}`;
|
|
2504
2874
|
linkElement.textContent = tocItem.text;
|
|
2505
2875
|
itemElement.appendChild(linkElement);
|
|
@@ -2510,7 +2880,7 @@ function createTocListItemElement(ownerDocument, tocItem) {
|
|
|
2510
2880
|
}
|
|
2511
2881
|
function hydrateToc(rootElement, scrollToHeading) {
|
|
2512
2882
|
const toggleElements = rootElement.querySelectorAll(
|
|
2513
|
-
`button.${
|
|
2883
|
+
`button.${TOC_TOGGLE_CLASS_NAME2}`
|
|
2514
2884
|
);
|
|
2515
2885
|
toggleElements.forEach((toggleElement) => {
|
|
2516
2886
|
if (toggleElement.dataset[TOC_TOGGLE_HYDRATED_DATA_KEY] === "true") {
|
|
@@ -2518,7 +2888,7 @@ function hydrateToc(rootElement, scrollToHeading) {
|
|
|
2518
2888
|
}
|
|
2519
2889
|
toggleElement.dataset[TOC_TOGGLE_HYDRATED_DATA_KEY] = "true";
|
|
2520
2890
|
toggleElement.addEventListener("click", () => {
|
|
2521
|
-
const itemElement = toggleElement.closest(`.${
|
|
2891
|
+
const itemElement = toggleElement.closest(`.${TOC_ITEM_CLASS_PREFIX2}`);
|
|
2522
2892
|
if (!itemElement) {
|
|
2523
2893
|
return;
|
|
2524
2894
|
}
|
|
@@ -2528,7 +2898,7 @@ function hydrateToc(rootElement, scrollToHeading) {
|
|
|
2528
2898
|
});
|
|
2529
2899
|
});
|
|
2530
2900
|
const linkElements = rootElement.querySelectorAll(
|
|
2531
|
-
`a.${
|
|
2901
|
+
`a.${TOC_LINK_CLASS_NAME2}`
|
|
2532
2902
|
);
|
|
2533
2903
|
linkElements.forEach((linkElement) => {
|
|
2534
2904
|
if (linkElement.dataset[TOC_LINK_HYDRATED_DATA_KEY] === "true") {
|
|
@@ -2556,192 +2926,37 @@ function hydrateToc(rootElement, scrollToHeading) {
|
|
|
2556
2926
|
});
|
|
2557
2927
|
});
|
|
2558
2928
|
}
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
headingIndexCounts[depthIndex] = 1;
|
|
2591
|
-
}
|
|
2592
|
-
}
|
|
2593
|
-
return headingIndexCounts.slice(startDepth, headingDepth + 1).join(".");
|
|
2594
|
-
}
|
|
2595
|
-
function normalizeSlugText(headingText) {
|
|
2596
|
-
return headingText.trim().toLowerCase().replace(/<[^>]+>/g, "").replace(/[^\p{L}\p{N}\s_-]+/gu, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
2597
|
-
}
|
|
2598
|
-
function rehypeFrameClass() {
|
|
2599
|
-
return (tree) => {
|
|
2600
|
-
tagContentDetails(tree);
|
|
2601
|
-
};
|
|
2602
|
-
}
|
|
2603
|
-
function tagContentDetails(node) {
|
|
2604
|
-
if (node.type === "element" && node.tagName === "details") {
|
|
2605
|
-
const classNames = normalizeClassNames(node.properties?.className);
|
|
2606
|
-
const isTocDetails = classNames.includes(TOC_CLASS_NAME);
|
|
2607
|
-
if (!isTocDetails) {
|
|
2608
|
-
addClassNames(node, [DETAILS_CLASS_NAME, FRAME_CLASS_NAME]);
|
|
2609
|
-
}
|
|
2610
|
-
}
|
|
2611
|
-
if (Array.isArray(node.children)) {
|
|
2612
|
-
for (const childNode of node.children) {
|
|
2613
|
-
tagContentDetails(childNode);
|
|
2614
|
-
}
|
|
2615
|
-
}
|
|
2616
|
-
}
|
|
2617
|
-
function normalizeClassNames(className) {
|
|
2618
|
-
if (Array.isArray(className)) {
|
|
2619
|
-
return className.filter((item) => typeof item === "string");
|
|
2620
|
-
}
|
|
2621
|
-
if (typeof className === "string") {
|
|
2622
|
-
return className.split(/\s+/u).filter(Boolean);
|
|
2623
|
-
}
|
|
2624
|
-
return [];
|
|
2625
|
-
}
|
|
2626
|
-
function addClassNames(node, classNamesToAdd) {
|
|
2627
|
-
const mergedClassNames = normalizeClassNames(node.properties?.className);
|
|
2628
|
-
for (const classNameToAdd of classNamesToAdd) {
|
|
2629
|
-
if (!mergedClassNames.includes(classNameToAdd)) {
|
|
2630
|
-
mergedClassNames.push(classNameToAdd);
|
|
2631
|
-
}
|
|
2632
|
-
}
|
|
2633
|
-
node.properties = { ...node.properties, className: mergedClassNames };
|
|
2634
|
-
}
|
|
2635
|
-
function rehypeVideoFigures() {
|
|
2636
|
-
return (tree) => {
|
|
2637
|
-
transformVideoFigures(tree);
|
|
2638
|
-
};
|
|
2639
|
-
}
|
|
2640
|
-
function transformVideoFigures(node) {
|
|
2641
|
-
if (!Array.isArray(node.children)) {
|
|
2642
|
-
return;
|
|
2643
|
-
}
|
|
2644
|
-
const childNodes = node.children;
|
|
2645
|
-
for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {
|
|
2646
|
-
const childNode = childNodes[childIndex];
|
|
2647
|
-
if (isHastVideoElement(childNode)) {
|
|
2648
|
-
childNodes[childIndex] = createVideoFigureHast(childNode);
|
|
2649
|
-
continue;
|
|
2650
|
-
}
|
|
2651
|
-
const standaloneVideo = extractStandaloneParagraphVideo(childNode);
|
|
2652
|
-
if (standaloneVideo) {
|
|
2653
|
-
childNodes[childIndex] = createVideoFigureHast(standaloneVideo);
|
|
2654
|
-
continue;
|
|
2655
|
-
}
|
|
2656
|
-
transformVideoFigures(childNode);
|
|
2657
|
-
}
|
|
2658
|
-
}
|
|
2659
|
-
function isHastVideoElement(node) {
|
|
2660
|
-
return node.type === "element" && node.tagName === "video";
|
|
2661
|
-
}
|
|
2662
|
-
function extractStandaloneParagraphVideo(node) {
|
|
2663
|
-
if (node.type !== "element" || node.tagName !== "p" || !Array.isArray(node.children)) {
|
|
2664
|
-
return void 0;
|
|
2665
|
-
}
|
|
2666
|
-
const significantChildren = node.children.filter((childNode) => {
|
|
2667
|
-
if (childNode.type === "text" && typeof childNode.value === "string") {
|
|
2668
|
-
return childNode.value.trim().length > 0;
|
|
2669
|
-
}
|
|
2670
|
-
return true;
|
|
2671
|
-
});
|
|
2672
|
-
if (significantChildren.length === 1 && isHastVideoElement(significantChildren[0])) {
|
|
2673
|
-
return significantChildren[0];
|
|
2674
|
-
}
|
|
2675
|
-
return void 0;
|
|
2676
|
-
}
|
|
2677
|
-
function createVideoFigureHast(videoNode) {
|
|
2678
|
-
decorateHastVideoElement(videoNode);
|
|
2679
|
-
const sourceUrl = readHastVideoSourceUrl(videoNode);
|
|
2680
|
-
return {
|
|
2681
|
-
type: "element",
|
|
2682
|
-
tagName: "figure",
|
|
2683
|
-
properties: { className: [VIDEO_FIGURE_CLASS_NAME] },
|
|
2684
|
-
children: [
|
|
2685
|
-
{
|
|
2686
|
-
type: "element",
|
|
2687
|
-
tagName: "span",
|
|
2688
|
-
properties: { className: [VIDEO_FRAME_CLASS_NAME] },
|
|
2689
|
-
children: [videoNode, createVideoFallbackHast(sourceUrl)]
|
|
2690
|
-
}
|
|
2691
|
-
]
|
|
2692
|
-
};
|
|
2693
|
-
}
|
|
2694
|
-
function decorateHastVideoElement(videoNode) {
|
|
2695
|
-
const properties = videoNode.properties ?? {};
|
|
2696
|
-
const existingClassName = properties.className;
|
|
2697
|
-
const classList = Array.isArray(existingClassName) ? existingClassName.map(String) : typeof existingClassName === "string" ? [existingClassName] : [];
|
|
2698
|
-
if (!classList.includes(VIDEO_ELEMENT_CLASS_NAME)) {
|
|
2699
|
-
classList.push(VIDEO_ELEMENT_CLASS_NAME);
|
|
2700
|
-
}
|
|
2701
|
-
properties.className = classList;
|
|
2702
|
-
videoNode.properties = properties;
|
|
2703
|
-
}
|
|
2704
|
-
function readHastVideoSourceUrl(videoNode) {
|
|
2705
|
-
const properties = videoNode.properties ?? {};
|
|
2706
|
-
if (typeof properties.src === "string") {
|
|
2707
|
-
return properties.src;
|
|
2708
|
-
}
|
|
2709
|
-
if (Array.isArray(videoNode.children)) {
|
|
2710
|
-
for (const childNode of videoNode.children) {
|
|
2711
|
-
if (childNode.type === "element" && childNode.tagName === "source" && typeof childNode.properties?.src === "string") {
|
|
2712
|
-
return childNode.properties.src;
|
|
2713
|
-
}
|
|
2714
|
-
}
|
|
2715
|
-
}
|
|
2716
|
-
return "";
|
|
2717
|
-
}
|
|
2718
|
-
function createVideoFallbackHast(sourceUrl) {
|
|
2719
|
-
return {
|
|
2720
|
-
type: "element",
|
|
2721
|
-
tagName: "span",
|
|
2722
|
-
properties: { className: [VIDEO_FALLBACK_CLASS_NAME] },
|
|
2723
|
-
children: [
|
|
2724
|
-
{
|
|
2725
|
-
type: "element",
|
|
2726
|
-
tagName: "span",
|
|
2727
|
-
properties: { className: [VIDEO_FALLBACK_ICON_CLASS_NAME] },
|
|
2728
|
-
children: []
|
|
2729
|
-
},
|
|
2730
|
-
{
|
|
2731
|
-
type: "element",
|
|
2732
|
-
tagName: "span",
|
|
2733
|
-
properties: { className: [VIDEO_FALLBACK_TEXT_CLASS_NAME] },
|
|
2734
|
-
children: [{ type: "text", value: VIDEO_FALLBACK_DEFAULT_TEXT }]
|
|
2735
|
-
},
|
|
2736
|
-
{
|
|
2737
|
-
type: "element",
|
|
2738
|
-
tagName: "span",
|
|
2739
|
-
properties: { className: [VIDEO_FALLBACK_SOURCE_CLASS_NAME] },
|
|
2740
|
-
children: [{ type: "text", value: sourceUrl }]
|
|
2741
|
-
}
|
|
2742
|
-
]
|
|
2743
|
-
};
|
|
2744
|
-
}
|
|
2929
|
+
|
|
2930
|
+
// src/toolbar.ts
|
|
2931
|
+
import {
|
|
2932
|
+
CONTENT_WIDTH_STORAGE_KEY,
|
|
2933
|
+
PROJECT_HOMEPAGE_URL,
|
|
2934
|
+
SCRIBDOWN_CONTENT_AREA_CLASS_NAME,
|
|
2935
|
+
SCRIBDOWN_CONTENT_SCROLL_CLASS_NAME,
|
|
2936
|
+
SCRIBDOWN_MARKDOWN_CLASS_NAME as SCRIBDOWN_MARKDOWN_CLASS_NAME2,
|
|
2937
|
+
SCRIBDOWN_THIN_SCROLLBAR_CLASS_NAME,
|
|
2938
|
+
SCRIBDOWN_TOC_HOST_CLASS_NAME,
|
|
2939
|
+
SCRIBDOWN_TOOLBAR_BTN_CLASS_NAME,
|
|
2940
|
+
SCRIBDOWN_TOOLBAR_CLASS_NAME,
|
|
2941
|
+
SCRIBDOWN_TOOLBAR_CURRENT_CLASS_NAME,
|
|
2942
|
+
SCRIBDOWN_TOOLBAR_CURRENT_TEXT_CLASS_NAME,
|
|
2943
|
+
SCRIBDOWN_TOOLBAR_MENU_CLASS_NAME,
|
|
2944
|
+
SCRIBDOWN_TOOLBAR_MENU_GROUP_CHOICES_CLASS_NAME,
|
|
2945
|
+
SCRIBDOWN_TOOLBAR_MENU_GROUP_CLASS_NAME,
|
|
2946
|
+
SCRIBDOWN_TOOLBAR_MENU_ITEM_CLASS_NAME,
|
|
2947
|
+
SCRIBDOWN_TOOLBAR_MENU_SELECT_CHEVRON_CLASS_NAME,
|
|
2948
|
+
SCRIBDOWN_TOOLBAR_MENU_SELECT_LABEL_CLASS_NAME,
|
|
2949
|
+
SCRIBDOWN_TOOLBAR_MENU_SELECT_VALUE_CLASS_NAME,
|
|
2950
|
+
SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_CLASS_NAME,
|
|
2951
|
+
SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_LABEL_CLASS_NAME,
|
|
2952
|
+
SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_MARK_CLASS_NAME,
|
|
2953
|
+
SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME,
|
|
2954
|
+
SCRIBDOWN_TOOLBAR_TOC_PANEL_CLOSE_CLASS_NAME,
|
|
2955
|
+
SCRIBDOWN_TOOLBAR_TOC_PANEL_EMPTY_CLASS_NAME,
|
|
2956
|
+
SCRIBDOWN_TOOLBAR_TOC_PANEL_TITLE_CLASS_NAME,
|
|
2957
|
+
TOC_LINK_ACTIVE_CLASS_NAME,
|
|
2958
|
+
TOC_LINK_CLASS_NAME as TOC_LINK_CLASS_NAME3
|
|
2959
|
+
} from "@scribdown/shared";
|
|
2745
2960
|
var TOOLBAR_WIDTH_PRESETS = [
|
|
2746
2961
|
{ label: "680", value: "680px" },
|
|
2747
2962
|
{ label: "840", value: "840px" },
|
|
@@ -2778,10 +2993,11 @@ var TOOLBAR_MORE_ICON_SVG = '<svg width="16" height="16" viewBox="0 0 18 18" fil
|
|
|
2778
2993
|
var TOOLBAR_BACK_TOP_ICON_SVG = '<svg width="16" height="16" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M9 14V5M9 5L5 9M9 5l4 4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/><line x1="4" y1="3.5" x2="14" y2="3.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>';
|
|
2779
2994
|
var TOOLBAR_WIDTH_ICON_SVG = '<svg width="16" height="16" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M3 6v6M15 6v6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><path d="M5.5 9H3l2.5-2.5M3 9l2.5 2.5M12.5 9H15l-2.5-2.5M15 9l-2.5 2.5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
2780
2995
|
var TOOLBAR_ABOUT_ICON_SVG = '<svg width="16" height="16" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><circle cx="9" cy="9" r="6.5" stroke="currentColor" stroke-width="1.6"/><path d="M9 8v4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><circle cx="9" cy="5.6" r="0.9" fill="currentColor"/></svg>';
|
|
2781
|
-
var
|
|
2996
|
+
var pageToolbarTeardowns = /* @__PURE__ */ new WeakMap();
|
|
2782
2997
|
function mountPageToolbar(ownerDocument, container, scrollToHeading) {
|
|
2783
|
-
|
|
2784
|
-
|
|
2998
|
+
pageToolbarTeardowns.get(container)?.();
|
|
2999
|
+
pageToolbarTeardowns.delete(container);
|
|
3000
|
+
const teardownCallbacks = [];
|
|
2785
3001
|
container.querySelector(`:scope > .${SCRIBDOWN_TOOLBAR_CLASS_NAME}`)?.remove();
|
|
2786
3002
|
container.querySelector(`:scope > .${SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME}`)?.remove();
|
|
2787
3003
|
container.classList.add(SCRIBDOWN_TOC_HOST_CLASS_NAME);
|
|
@@ -2815,13 +3031,13 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
|
|
|
2815
3031
|
const tocPanel = ownerDocument.createElement("div");
|
|
2816
3032
|
tocPanel.className = `${SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME} ${SCRIBDOWN_THIN_SCROLLBAR_CLASS_NAME}`;
|
|
2817
3033
|
const tocTitle = ownerDocument.createElement("div");
|
|
2818
|
-
tocTitle.className =
|
|
3034
|
+
tocTitle.className = SCRIBDOWN_TOOLBAR_TOC_PANEL_TITLE_CLASS_NAME;
|
|
2819
3035
|
const tocTitleText = ownerDocument.createElement("span");
|
|
2820
3036
|
tocTitleText.textContent = "\u76EE\u5F55";
|
|
2821
3037
|
tocTitle.appendChild(tocTitleText);
|
|
2822
3038
|
const tocCloseBtn = ownerDocument.createElement("button");
|
|
2823
3039
|
tocCloseBtn.type = "button";
|
|
2824
|
-
tocCloseBtn.className =
|
|
3040
|
+
tocCloseBtn.className = SCRIBDOWN_TOOLBAR_TOC_PANEL_CLOSE_CLASS_NAME;
|
|
2825
3041
|
tocCloseBtn.setAttribute("aria-label", "\u5173\u95ED\u76EE\u5F55");
|
|
2826
3042
|
tocCloseBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>';
|
|
2827
3043
|
const setTocOpen = (open) => {
|
|
@@ -2836,7 +3052,7 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
|
|
|
2836
3052
|
const tocHeadings = collectTocHeadingsFromDom(container);
|
|
2837
3053
|
if (tocHeadings.length === 0) {
|
|
2838
3054
|
const emptyElement = ownerDocument.createElement("p");
|
|
2839
|
-
emptyElement.className =
|
|
3055
|
+
emptyElement.className = SCRIBDOWN_TOOLBAR_TOC_PANEL_EMPTY_CLASS_NAME;
|
|
2840
3056
|
emptyElement.textContent = "\u6682\u65E0\u6807\u9898";
|
|
2841
3057
|
tocPanel.appendChild(emptyElement);
|
|
2842
3058
|
} else {
|
|
@@ -2866,17 +3082,17 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
|
|
|
2866
3082
|
widthTrigger.setAttribute("aria-haspopup", "listbox");
|
|
2867
3083
|
widthTrigger.setAttribute("aria-expanded", "false");
|
|
2868
3084
|
const widthValueElement = ownerDocument.createElement("span");
|
|
2869
|
-
widthValueElement.className =
|
|
3085
|
+
widthValueElement.className = SCRIBDOWN_TOOLBAR_MENU_SELECT_VALUE_CLASS_NAME;
|
|
2870
3086
|
widthValueElement.textContent = TOOLBAR_WIDTH_PRESETS[widthIndex].label;
|
|
2871
|
-
widthTrigger.innerHTML = `${TOOLBAR_WIDTH_ICON_SVG}<span class="
|
|
3087
|
+
widthTrigger.innerHTML = `${TOOLBAR_WIDTH_ICON_SVG}<span class="${SCRIBDOWN_TOOLBAR_MENU_SELECT_LABEL_CLASS_NAME}">\u9875\u9762\u5BBD\u5EA6</span>`;
|
|
2872
3088
|
widthTrigger.appendChild(widthValueElement);
|
|
2873
3089
|
widthTrigger.insertAdjacentHTML(
|
|
2874
3090
|
"beforeend",
|
|
2875
|
-
|
|
3091
|
+
`<svg class="${SCRIBDOWN_TOOLBAR_MENU_SELECT_CHEVRON_CLASS_NAME}" width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true"><path d="M3 4.5l3 3 3-3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>`
|
|
2876
3092
|
);
|
|
2877
3093
|
widthGroup.appendChild(widthTrigger);
|
|
2878
3094
|
const widthChoices = ownerDocument.createElement("div");
|
|
2879
|
-
widthChoices.className =
|
|
3095
|
+
widthChoices.className = SCRIBDOWN_TOOLBAR_MENU_GROUP_CHOICES_CLASS_NAME;
|
|
2880
3096
|
widthChoices.setAttribute("role", "listbox");
|
|
2881
3097
|
const widthSubItems = [];
|
|
2882
3098
|
TOOLBAR_WIDTH_PRESETS.forEach((preset, idx) => {
|
|
@@ -2885,7 +3101,7 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
|
|
|
2885
3101
|
subItem.className = SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_CLASS_NAME;
|
|
2886
3102
|
subItem.setAttribute("role", "option");
|
|
2887
3103
|
subItem.setAttribute("aria-selected", String(idx === widthIndex));
|
|
2888
|
-
subItem.innerHTML = `<span class="
|
|
3104
|
+
subItem.innerHTML = `<span class="${SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_MARK_CLASS_NAME}" aria-hidden="true"><svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M2.5 6.5l2.5 2.5 4.5-5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span><span class="${SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_LABEL_CLASS_NAME}">${preset.label}</span>`;
|
|
2889
3105
|
subItem.addEventListener("click", () => {
|
|
2890
3106
|
widthIndex = idx;
|
|
2891
3107
|
applyContentWidth(ownerDocument, preset.value);
|
|
@@ -2948,13 +3164,17 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
|
|
|
2948
3164
|
const opened = menu.classList.toggle("is-open");
|
|
2949
3165
|
moreBtn.setAttribute("aria-expanded", String(opened));
|
|
2950
3166
|
});
|
|
2951
|
-
|
|
3167
|
+
const handleDocumentClick = (e) => {
|
|
2952
3168
|
const target = e.target;
|
|
2953
3169
|
if (!menu.contains(target) && target !== moreBtn) {
|
|
2954
3170
|
closeMoreMenu();
|
|
2955
3171
|
}
|
|
3172
|
+
};
|
|
3173
|
+
ownerDocument.addEventListener("click", handleDocumentClick);
|
|
3174
|
+
teardownCallbacks.push(() => {
|
|
3175
|
+
ownerDocument.removeEventListener("click", handleDocumentClick);
|
|
2956
3176
|
});
|
|
2957
|
-
const markdownContainer = container.matches(`.${
|
|
3177
|
+
const markdownContainer = container.matches(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME2}`) ? container : container.querySelector(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME2}`);
|
|
2958
3178
|
const headingElements = markdownContainer ? Array.from(
|
|
2959
3179
|
markdownContainer.querySelectorAll(
|
|
2960
3180
|
"h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]"
|
|
@@ -2967,12 +3187,12 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
|
|
|
2967
3187
|
currentHeading.setAttribute("role", "button");
|
|
2968
3188
|
currentHeading.tabIndex = 0;
|
|
2969
3189
|
const currentHeadingText = ownerDocument.createElement("span");
|
|
2970
|
-
currentHeadingText.className =
|
|
3190
|
+
currentHeadingText.className = SCRIBDOWN_TOOLBAR_CURRENT_TEXT_CLASS_NAME;
|
|
2971
3191
|
currentHeading.appendChild(currentHeadingText);
|
|
2972
3192
|
let lastHeadingText = "";
|
|
2973
3193
|
let activeHeadingElement = headingElements[0];
|
|
2974
3194
|
const tocLinkByHeading = /* @__PURE__ */ new Map();
|
|
2975
|
-
tocPanel.querySelectorAll(`a.${
|
|
3195
|
+
tocPanel.querySelectorAll(`a.${TOC_LINK_CLASS_NAME3}`).forEach((linkElement) => {
|
|
2976
3196
|
const rawTargetId = (linkElement.getAttribute("href") ?? "").slice(1);
|
|
2977
3197
|
if (!rawTargetId) {
|
|
2978
3198
|
return;
|
|
@@ -3050,7 +3270,7 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
|
|
|
3050
3270
|
});
|
|
3051
3271
|
ownerWindow?.addEventListener("resize", onScrollOrResize, { passive: true });
|
|
3052
3272
|
updateCurrentHeading();
|
|
3053
|
-
|
|
3273
|
+
teardownCallbacks.push(() => {
|
|
3054
3274
|
if (scrollRafId) {
|
|
3055
3275
|
ownerWindow?.cancelAnimationFrame(scrollRafId);
|
|
3056
3276
|
}
|
|
@@ -3064,6 +3284,40 @@ function mountPageToolbar(ownerDocument, container, scrollToHeading) {
|
|
|
3064
3284
|
toolbar.appendChild(menu);
|
|
3065
3285
|
container.appendChild(toolbar);
|
|
3066
3286
|
container.insertBefore(tocPanel, contentArea);
|
|
3287
|
+
pageToolbarTeardowns.set(container, () => {
|
|
3288
|
+
teardownCallbacks.forEach((teardownCallback) => {
|
|
3289
|
+
teardownCallback();
|
|
3290
|
+
});
|
|
3291
|
+
});
|
|
3292
|
+
}
|
|
3293
|
+
|
|
3294
|
+
// src/index.ts
|
|
3295
|
+
async function renderMarkdown(markdownText) {
|
|
3296
|
+
const processor = unified().use(remarkParse).use(remarkGfm).use(remarkFrontmatter).use(remarkFrontmatterMetadata).use(remarkHighlightMark).use(remarkDefinitionLists).use(remarkTableOfContents).use(remarkImageFigures).use(remarkSourceLine).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeRaw).use(rehypeFrameClass).use(rehypeVideoFigures).use(rehypeSanitize, createScribdownSanitizeSchema()).use(rehypeStringify);
|
|
3297
|
+
const renderedHtml = String(await processor.process(markdownText));
|
|
3298
|
+
const sanitizedHtml = sanitizeHtmlWithDomPurify(renderedHtml);
|
|
3299
|
+
return highlightMarkdownCodeBlocks(sanitizedHtml);
|
|
3300
|
+
}
|
|
3301
|
+
function defaultScrollToHeading(targetElement) {
|
|
3302
|
+
targetElement.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
3303
|
+
}
|
|
3304
|
+
function hydrateMarkdown(rootElement, options = {}) {
|
|
3305
|
+
hydrateMarkdownImages(rootElement);
|
|
3306
|
+
hydrateMarkdownVideos(rootElement);
|
|
3307
|
+
hydrateMermaidBlocks(rootElement);
|
|
3308
|
+
hydrateCodeBlocks(rootElement);
|
|
3309
|
+
hydrateToc(rootElement, options.scrollToHeading ?? defaultScrollToHeading);
|
|
3310
|
+
}
|
|
3311
|
+
function mountMarkdownToolbar(container, options = {}) {
|
|
3312
|
+
const ownerDocument = container.ownerDocument;
|
|
3313
|
+
if (!ownerDocument) {
|
|
3314
|
+
return;
|
|
3315
|
+
}
|
|
3316
|
+
if (typeof ownerDocument.defaultView?.scrollTo !== "function") {
|
|
3317
|
+
return;
|
|
3318
|
+
}
|
|
3319
|
+
applyContentWidth(ownerDocument, loadContentWidth());
|
|
3320
|
+
mountPageToolbar(ownerDocument, container, options.scrollToHeading ?? defaultScrollToHeading);
|
|
3067
3321
|
}
|
|
3068
3322
|
export {
|
|
3069
3323
|
hydrateMarkdown,
|