@scribdown/markdown-renderer 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/code-highlighter-langs.ts"],"sourcesContent":["import {\n CONTENT_WIDTH_STORAGE_KEY,\n PROJECT_HOMEPAGE_URL,\n SCRIBDOWN_MARKDOWN_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_BTN_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_MENU_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_MENU_GROUP_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_MENU_ITEM_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_CURRENT_CLASS_NAME,\n SCRIBDOWN_TOC_HOST_CLASS_NAME,\n SCRIBDOWN_CONTENT_AREA_CLASS_NAME,\n SCRIBDOWN_CONTENT_SCROLL_CLASS_NAME,\n SCRIBDOWN_THIN_SCROLLBAR_CLASS_NAME,\n SOURCE_LINE_DATA_ATTRIBUTE\n} from \"@scribdown/shared\";\nimport DOMPurify from \"dompurify\";\nimport rehypeRaw from \"rehype-raw\";\nimport rehypeSanitize, { defaultSchema } from \"rehype-sanitize\";\nimport rehypeStringify from \"rehype-stringify\";\nimport remarkGfm from \"remark-gfm\";\nimport remarkParse from \"remark-parse\";\nimport remarkRehype from \"remark-rehype\";\nimport githubLightTheme from \"@shikijs/themes/github-light\";\nimport { type HighlighterCore, createHighlighterCore } from \"shiki/core\";\nimport { createOnigurumaEngine } from \"shiki/engine/oniguruma\";\nimport { unified } from \"unified\";\nimport {\n CODE_HIGHLIGHTER_EAGER_LOADERS,\n CODE_HIGHLIGHTER_LAZY_LANGS\n} from \"./code-highlighter-langs\";\n\n/**\n * 源码行号在 hast 节点上的属性名(camelCase)。\n * rehype-raw 会把 DOM 属性 data-source-line 回解析为该 camelCase 形式,\n * sanitize 白名单与 hProperties 注入均需以此形式匹配,序列化后仍输出 data-source-line。\n */\nconst SOURCE_LINE_HAST_PROPERTY = SOURCE_LINE_DATA_ATTRIBUTE.replace(\n /-([a-z])/gu,\n (_match: string, letter: string) => letter.toUpperCase()\n);\n\n/**\n * Markdown AST 节点的最小结构。\n */\ninterface MarkdownNode {\n type: string;\n value?: string;\n depth?: number;\n url?: string;\n alt?: string;\n title?: string | null;\n identifier?: string;\n ordered?: boolean;\n spread?: boolean;\n children?: MarkdownNode[];\n data?: MarkdownNodeData;\n position?: MarkdownNodePosition;\n}\n\n/**\n * Markdown AST 节点上的 HTML 转换元数据。\n */\ninterface MarkdownNodeData {\n hName?: string;\n hProperties?: Record<string, unknown>;\n}\n\n/**\n * Markdown AST 节点的源码位置信息。\n */\ninterface MarkdownNodePosition {\n /** 节点在源码中的起始位置。 */\n start: {\n /** 起始行号(1-based)。 */\n line: number;\n };\n}\n\n/**\n * 目录中的单个标题条目。\n */\ninterface TocHeading {\n depth: number;\n id: string;\n index: string;\n text: string;\n}\n\n/**\n * 目录树中的单个标题条目。\n */\ninterface TocTreeItem extends TocHeading {\n children: TocTreeItem[];\n}\n\n/**\n * 图片全图查看器的运行时状态。\n */\ninterface MarkdownImageViewerState {\n captionElement: HTMLElement;\n closeButtonElement: HTMLButtonElement;\n dialogElement: HTMLDialogElement;\n /** 拖拽起始时鼠标的客户端 X 坐标。 */\n dragStartClientX: number;\n /** 拖拽起始时鼠标的客户端 Y 坐标。 */\n dragStartClientY: number;\n /** 拖拽起始时视口的横向滚动量。 */\n dragStartScrollLeft: number;\n /** 拖拽起始时视口的纵向滚动量。 */\n dragStartScrollTop: number;\n hintElement: HTMLElement;\n imageElement: HTMLImageElement;\n /** 当前是否处于鼠标拖拽平移状态。 */\n isDragging: boolean;\n naturalHeight: number;\n naturalWidth: number;\n resetButtonElement: HTMLButtonElement;\n sourceImageElement?: HTMLImageElement;\n viewportElement: HTMLElement;\n zoomInButtonElement: HTMLButtonElement;\n zoomOutButtonElement: HTMLButtonElement;\n zoomValue: number;\n zoomValueElement: HTMLElement;\n}\n\n/**\n * 缩放前记录的视口焦点信息,用于保持光标/视口中心在缩放后位置不变。\n */\ninterface MarkdownImageViewerFocalPoint {\n /** 焦点在客户端坐标系的 X。 */\n anchorClientX: number;\n /** 焦点在客户端坐标系的 Y。 */\n anchorClientY: number;\n /** 焦点在图片内的归一化 X (0..1)。 */\n normalizedX: number;\n /** 焦点在图片内的归一化 Y (0..1)。 */\n normalizedY: number;\n}\n\n/**\n * 触发缩放时的可选锚点(鼠标客户端坐标)。\n */\ninterface MarkdownImageViewerZoomAnchor {\n x: number;\n y: number;\n}\n\n/**\n * 图片全图查看器按钮配置。\n */\ninterface MarkdownImageViewerButtonOptions {\n ariaLabel: string;\n className?: string;\n text: string;\n}\n\n// [TOC] 占位符匹配规则:仅处理独占一段的目录标记。\nconst TOC_MARKER_PATTERN = /^\\s*\\[toc]\\s*$/i;\n\n// 目录容器类名。\nconst TOC_CLASS_NAME = \"scribdown-toc\";\n\n// 手绘卡片边框类名:可复用的「边框」基底,由内容折叠块、目录根等显式 opt-in 引入。\nconst FRAME_CLASS_NAME = \"scribdown-frame\";\n\n// 内容折叠块类名:用户手写的原生 <details>(区别于目录用 <details>)在渲染时打上此标。\nconst DETAILS_CLASS_NAME = \"scribdown-details\";\n\n// 目录摘要按钮类名。\nconst TOC_SUMMARY_CLASS_NAME = \"scribdown-toc-summary\";\n\n// 目录导航区域类名。\nconst TOC_NAV_CLASS_NAME = \"scribdown-toc-nav\";\n\n// 目录列表类名。\nconst TOC_LIST_CLASS_NAME = \"scribdown-toc-list\";\n\n// 目录嵌套列表类名。\nconst TOC_LIST_NESTED_CLASS_NAME = \"scribdown-toc-list--nested\";\n\n// 目录条目类名前缀。\nconst TOC_ITEM_CLASS_PREFIX = \"scribdown-toc-item\";\n\n// 目录分支条目类名(拥有可折叠子层级)。\nconst TOC_ITEM_BRANCH_CLASS_NAME = \"scribdown-toc-item--branch\";\n\n// 目录分支条目折叠态类名(运行时由折叠按钮切换,控制隐藏嵌套列表与箭头朝向)。\nconst TOC_ITEM_COLLAPSED_CLASS_NAME = \"scribdown-toc-item--collapsed\";\n\n// 目录条目标题跳转链接类名:叶子与分支共用同一种 <a href=\"#id\"> 元素与跳转逻辑。\nconst TOC_LINK_CLASS_NAME = \"scribdown-toc-link\";\n\n// 目录标题链接「当前章节」高亮类名:scrollspy 命中当前可视区标题时切换,与工具栏当前指示器同步。\nconst TOC_LINK_ACTIVE_CLASS_NAME = \"scribdown-toc-link--active\";\n\n// 目录分支折叠按钮类名(与标题链接分离,专职展开/折叠,避免与跳转冲突)。\nconst TOC_TOGGLE_CLASS_NAME = \"scribdown-toc-toggle\";\n\n// 目录分支折叠按钮可访问名称。\nconst TOC_TOGGLE_ARIA_LABEL = \"展开或折叠子目录\";\n\n// 目录折叠按钮已绑定交互的标记 dataset 键,保证 hydrate 幂等。\nconst TOC_TOGGLE_HYDRATED_DATA_KEY = \"scribdownTocToggleHydrated\";\n\n// 目录标题链接已绑定平滑滚动的标记 dataset 键,保证 hydrate 幂等。\nconst TOC_LINK_HYDRATED_DATA_KEY = \"scribdownTocLinkHydrated\";\n\n// 目录可访问名称。\nconst TOC_ARIA_LABEL = \"目录\";\n\n// 目录摘要显示文本。\nconst TOC_SUMMARY_TEXT = \"目录\";\n\n// 空标题生成锚点时使用的前缀。\nconst EMPTY_HEADING_SLUG_PREFIX = \"section\";\n\n// 标题文本包裹层类名:用于按行绘制手绘高亮,确保多行标题每行都有底色。\nconst HEADING_MARK_CLASS_NAME = \"scribdown-heading-mark\";\n\n// 图片 figure 容器类名。\nconst IMAGE_FIGURE_CLASS_NAME = \"scribdown-image-figure\";\n\n// 图片边框容器类名。\nconst IMAGE_FRAME_CLASS_NAME = \"scribdown-image-frame\";\n\n// 图片元素类名。\nconst IMAGE_ELEMENT_CLASS_NAME = \"scribdown-image\";\n\n// 图片标题类名。\nconst IMAGE_CAPTION_CLASS_NAME = \"scribdown-image-caption\";\n\n// 图片加载失败状态类名。\nconst IMAGE_FRAME_FAILED_CLASS_NAME = \"scribdown-image-frame--failed\";\n\n// 图片加载完成状态类名。\nconst IMAGE_FRAME_LOADED_CLASS_NAME = \"scribdown-image-frame--loaded\";\n\n// 图片失败态占位内容类名。\nconst IMAGE_FALLBACK_CLASS_NAME = \"scribdown-image-fallback\";\n\n// 图片失败态图标类名。\nconst IMAGE_FALLBACK_ICON_CLASS_NAME = \"scribdown-image-fallback-icon\";\n\n// 图片失败态标题类名。\nconst IMAGE_FALLBACK_TEXT_CLASS_NAME = \"scribdown-image-fallback-text\";\n\n// 图片失败态来源类名。\nconst IMAGE_FALLBACK_SOURCE_CLASS_NAME = \"scribdown-image-fallback-source\";\n\n// 图片运行时已绑定标记的 dataset 键。\nconst IMAGE_HYDRATED_DATA_KEY = \"scribdownImageHydrated\";\n\n// 视频 figure 容器类名。\nconst VIDEO_FIGURE_CLASS_NAME = \"scribdown-video-figure\";\n\n// 视频边框容器类名。\nconst VIDEO_FRAME_CLASS_NAME = \"scribdown-video-frame\";\n\n// 视频元素类名。\nconst VIDEO_ELEMENT_CLASS_NAME = \"scribdown-video\";\n\n// 视频加载失败状态类名。\nconst VIDEO_FRAME_FAILED_CLASS_NAME = \"scribdown-video-frame--failed\";\n\n// 视频加载完成状态类名。\nconst VIDEO_FRAME_LOADED_CLASS_NAME = \"scribdown-video-frame--loaded\";\n\n// 视频失败态占位内容类名。\nconst VIDEO_FALLBACK_CLASS_NAME = \"scribdown-video-fallback\";\n\n// 视频失败态图标类名。\nconst VIDEO_FALLBACK_ICON_CLASS_NAME = \"scribdown-video-fallback-icon\";\n\n// 视频失败态标题类名。\nconst VIDEO_FALLBACK_TEXT_CLASS_NAME = \"scribdown-video-fallback-text\";\n\n// 视频失败态来源类名。\nconst VIDEO_FALLBACK_SOURCE_CLASS_NAME = \"scribdown-video-fallback-source\";\n\n// 视频运行时已绑定标记的 dataset 键。\nconst VIDEO_HYDRATED_DATA_KEY = \"scribdownVideoHydrated\";\n\n// 视频失败态展示文本。\nconst VIDEO_FALLBACK_DEFAULT_TEXT = \"视频加载失败\";\n\n// Mermaid 代码块的语言标识,对应 fixture 中的 ```mermaid。\nconst MERMAID_LANGUAGE_ID = \"mermaid\";\n\n// Mermaid 图表外层 figure 容器类名。\nconst MERMAID_FIGURE_CLASS_NAME = \"scribdown-mermaid\";\n\n// Mermaid 图表顶部 chrome 容器类名。\nconst MERMAID_CHROME_CLASS_NAME = \"scribdown-mermaid__chrome\";\n\n// Mermaid 图表语言标签类名。\nconst MERMAID_LABEL_CLASS_NAME = \"scribdown-mermaid__label\";\n\n// Mermaid 图表正文容器类名。\nconst MERMAID_BODY_CLASS_NAME = \"scribdown-mermaid__body\";\n\n// Mermaid 图表 SVG 画布容器类名。\nconst MERMAID_CANVAS_CLASS_NAME = \"scribdown-mermaid__canvas\";\n\n// Mermaid 图表失败态容器类名。\nconst MERMAID_FALLBACK_CLASS_NAME = \"scribdown-mermaid__fallback\";\n\n// Mermaid 失败态图标类名。\nconst MERMAID_FALLBACK_ICON_CLASS_NAME = \"scribdown-mermaid__fallback-icon\";\n\n// Mermaid 失败态文案类名。\nconst MERMAID_FALLBACK_TEXT_CLASS_NAME = \"scribdown-mermaid__fallback-text\";\n\n// Mermaid 失败态源码块类名。\nconst MERMAID_FALLBACK_SOURCE_CLASS_NAME = \"scribdown-mermaid__fallback-source\";\n\n// Mermaid 加载/失败/完成态修饰类名。\nconst MERMAID_FIGURE_LOADING_CLASS_NAME = \"scribdown-mermaid--loading\";\nconst MERMAID_FIGURE_FAILED_CLASS_NAME = \"scribdown-mermaid--failed\";\nconst MERMAID_FIGURE_LOADED_CLASS_NAME = \"scribdown-mermaid--loaded\";\n\n// Mermaid 已 hydrate 标记的 dataset 键(仅表示结构已构建)。\nconst MERMAID_HYDRATED_DATA_KEY = \"scribdownMermaidHydrated\";\n\n// Mermaid 渲染已启动标记,避免在 live DOM 重复触发 mermaid.render。\nconst MERMAID_RENDER_STARTED_DATA_KEY = \"scribdownMermaidRenderStarted\";\n\n// Mermaid 源码寄存在 figure 上的 dataset 键,供延后的 live-DOM 渲染读取。\nconst MERMAID_SOURCE_DATA_KEY = \"scribdownMermaidSourceText\";\n\n// Mermaid 顶部展示标签。\nconst MERMAID_LABEL_TEXT = \"Mermaid\";\n\n// Mermaid 失败态默认文案。\nconst MERMAID_FALLBACK_DEFAULT_TEXT = \"图表渲染失败\";\n\n// Mermaid SVG 节点宿主元素 id 前缀,确保多图表 id 唯一。\nconst MERMAID_RENDER_ID_PREFIX = \"scribdown-mermaid-\";\n\n// Mermaid 渲染顺序计数器,配合前缀生成全局唯一 id。\nlet mermaidRenderIdCounter = 0;\n\n// Mermaid 全屏按钮类名(位于 figure 右下角)。\nconst MERMAID_FULLSCREEN_BUTTON_CLASS_NAME = \"scribdown-mermaid__fullscreen\";\n\n// Mermaid 全屏按钮可访问名称。\nconst MERMAID_FULLSCREEN_BUTTON_ARIA_LABEL = \"全屏查看图表\";\n\n// Mermaid 全屏查看器 dialog 类名。\nconst MERMAID_VIEWER_DIALOG_CLASS_NAME = \"scribdown-mermaid-viewer\";\n\n// Mermaid 全屏查看器缩放进行中状态类名。\nconst MERMAID_VIEWER_ZOOMED_CLASS_NAME = \"scribdown-mermaid-viewer--zoomed\";\n\n// Mermaid 全屏查看器拖拽中状态类名。\nconst MERMAID_VIEWER_DRAGGING_CLASS_NAME = \"scribdown-mermaid-viewer--dragging\";\n\n// Mermaid 全屏查看器顶部 chrome 类名。\nconst MERMAID_VIEWER_CHROME_CLASS_NAME = \"scribdown-mermaid-viewer__chrome\";\n\n// Mermaid 全屏查看器 caption 类名。\nconst MERMAID_VIEWER_CAPTION_CLASS_NAME = \"scribdown-mermaid-viewer__caption\";\n\n// Mermaid 全屏查看器控件容器类名。\nconst MERMAID_VIEWER_CONTROLS_CLASS_NAME = \"scribdown-mermaid-viewer__controls\";\n\n// Mermaid 全屏查看器按钮类名。\nconst MERMAID_VIEWER_BUTTON_CLASS_NAME = \"scribdown-mermaid-viewer__button\";\n\n// Mermaid 全屏查看器关闭按钮修饰类名。\nconst MERMAID_VIEWER_CLOSE_BUTTON_CLASS_NAME = \"scribdown-mermaid-viewer__button--close\";\n\n// Mermaid 全屏查看器视口类名。\nconst MERMAID_VIEWER_VIEWPORT_CLASS_NAME = \"scribdown-mermaid-viewer__viewport\";\n\n// Mermaid 全屏查看器画布类名。\nconst MERMAID_VIEWER_CANVAS_CLASS_NAME = \"scribdown-mermaid-viewer__canvas\";\n\n// Mermaid 全屏查看器缩放百分比文本类名。\nconst MERMAID_VIEWER_ZOOM_VALUE_CLASS_NAME = \"scribdown-mermaid-viewer__zoom-value\";\n\n// 是否允许 mermaid 在 figure 右下角悬浮显示全屏按钮(仅 loaded 态显示)。\nconst MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY = \"scribdownMermaidFullscreenReady\";\n\n// Mermaid 全屏查看器存放原始 SVG 字符串的 dataset 键。\nconst MERMAID_VIEWER_SOURCE_DATA_KEY = \"scribdownMermaidSource\";\n\n// 用于按 document 缓存 mermaid 全屏查看器单例的映射。\nconst mermaidViewerStateByDocument = new WeakMap<Document, MarkdownMermaidViewerState>();\n\n// 用于按 dialog 元素反查查看器状态,便于事件回调读取。\nconst mermaidViewerStateByDialogElement = new WeakMap<\n HTMLDialogElement,\n MarkdownMermaidViewerState\n>();\n\n/**\n * Mermaid 全屏查看器运行时状态。\n */\ninterface MarkdownMermaidViewerState {\n /** 全屏 dialog 容器。 */\n dialogElement: HTMLDialogElement;\n /** 顶部 caption 节点。 */\n captionElement: HTMLElement;\n /** 缩放比例显示节点。 */\n zoomValueElement: HTMLElement;\n /** 缩小按钮。 */\n zoomOutButtonElement: HTMLButtonElement;\n /** 放大按钮。 */\n zoomInButtonElement: HTMLButtonElement;\n /** 重置按钮。 */\n resetButtonElement: HTMLButtonElement;\n /** 关闭按钮。 */\n closeButtonElement: HTMLButtonElement;\n /** 滚动视口容器。 */\n viewportElement: HTMLElement;\n /** SVG 挂载画布。 */\n canvasElement: HTMLElement;\n /** SVG 的固有宽度(来自 viewBox / width 属性)。 */\n naturalWidth: number;\n /** SVG 的固有高度。 */\n naturalHeight: number;\n /** 当前缩放倍数。 */\n zoomValue: number;\n /** 当前是否处于鼠标拖拽平移状态。 */\n isDragging: boolean;\n /** 拖拽起始时鼠标的客户端 X 坐标。 */\n dragStartClientX: number;\n /** 拖拽起始时鼠标的客户端 Y 坐标。 */\n dragStartClientY: number;\n /** 拖拽起始时视口的横向滚动量。 */\n dragStartScrollLeft: number;\n /** 拖拽起始时视口的纵向滚动量。 */\n dragStartScrollTop: number;\n}\n\n// 图片查看器 dialog 类名。\nconst IMAGE_VIEWER_DIALOG_CLASS_NAME = \"scribdown-image-viewer\";\n\n// 图片查看器缩放状态类名。\nconst IMAGE_VIEWER_ZOOMED_CLASS_NAME = \"scribdown-image-viewer--zoomed\";\n\n// 图片查看器拖拽状态类名。\nconst IMAGE_VIEWER_DRAGGING_CLASS_NAME = \"scribdown-image-viewer--dragging\";\n\n// 图片查看器标题区域类名(包裹 caption + hint)。\nconst IMAGE_VIEWER_CAPTION_GROUP_CLASS_NAME = \"scribdown-image-viewer__caption-group\";\n\n// 图片查看器快捷键提示类名。\nconst IMAGE_VIEWER_HINT_CLASS_NAME = \"scribdown-image-viewer__hint\";\n\n// 图片查看器快捷键提示文本。\nconst IMAGE_VIEWER_HINT_TEXT =\n \"快捷键:+/= 放大 · - 缩小 · 0 重置 · Esc 关闭 · 鼠标拖拽可平移 · Ctrl/⌘ + 滚轮缩放\";\n\n// 图片查看器顶部区域类名。\nconst IMAGE_VIEWER_CHROME_CLASS_NAME = \"scribdown-image-viewer__chrome\";\n\n// 图片查看器按钮区域类名。\nconst IMAGE_VIEWER_CONTROLS_CLASS_NAME = \"scribdown-image-viewer__controls\";\n\n// 图片查看器按钮类名。\nconst IMAGE_VIEWER_BUTTON_CLASS_NAME = \"scribdown-image-viewer__button\";\n\n// 图片查看器关闭按钮修饰类名。\nconst IMAGE_VIEWER_CLOSE_BUTTON_CLASS_NAME = \"scribdown-image-viewer__button--close\";\n\n// 图片查看器视口类名。\nconst IMAGE_VIEWER_VIEWPORT_CLASS_NAME = \"scribdown-image-viewer__viewport\";\n\n// 图片查看器图片类名。\nconst IMAGE_VIEWER_IMAGE_CLASS_NAME = \"scribdown-image-viewer__image\";\n\n// 图片查看器说明文字类名。\nconst IMAGE_VIEWER_CAPTION_CLASS_NAME = \"scribdown-image-viewer__caption\";\n\n// 图片查看器缩放数值类名。\nconst IMAGE_VIEWER_ZOOM_VALUE_CLASS_NAME = \"scribdown-image-viewer__zoom-value\";\n\n// 图片查看器默认缩放倍数。\nconst IMAGE_VIEWER_DEFAULT_ZOOM = 1;\n\n// 图片查看器最小缩放倍数。\nconst IMAGE_VIEWER_MIN_ZOOM = 0.25;\n\n// 图片查看器最大缩放倍数。\nconst IMAGE_VIEWER_MAX_ZOOM = 4;\n\n// 图片查看器每次缩放步进。\nconst IMAGE_VIEWER_ZOOM_STEP = 0.25;\n\n// 滚轮 deltaY 转换为缩放增量的比例系数:触控板单次事件 deltaY 通常较小(个位数),\n// 选 0.005 让一次轻微滑动只产生 ~0.5%-5% 的缩放变化,避免触控板上跳变过大。\nconst IMAGE_VIEWER_WHEEL_ZOOM_FACTOR = 0.010;\n\n// 单次滚轮事件允许的最大缩放变化量,防止鼠标滚轮一格 deltaY=100 时跳得过远。\nconst IMAGE_VIEWER_WHEEL_ZOOM_MAX_DELTA = 0.1;\n\n// 图片查看器适配视口时预留的安全比例。\nconst IMAGE_VIEWER_FIT_RATIO = 0.92;\n\n// 图片查看器兜底可访问名称。\nconst IMAGE_VIEWER_FALLBACK_LABEL = \"查看全图\";\n\n// 图片查看器关闭按钮文本。\nconst IMAGE_VIEWER_CLOSE_TEXT = \"x\";\n\n// 图片查看器放大按钮文本。\nconst IMAGE_VIEWER_ZOOM_IN_TEXT = \"+\";\n\n// 图片查看器缩小按钮文本。\nconst IMAGE_VIEWER_ZOOM_OUT_TEXT = \"-\";\n\n// 图片查看器重置按钮文本。\nconst IMAGE_VIEWER_RESET_TEXT = \"1:1\";\n\n// 每个 document 复用一个图片查看器。\nconst imageViewerStateByDocument = new WeakMap<Document, MarkdownImageViewerState>();\n\n// 从 dialog 反查图片查看器状态。\nconst imageViewerStateByDialogElement = new WeakMap<HTMLDialogElement, MarkdownImageViewerState>();\n\n// ==text== 高亮匹配:成对的 == 之间不能换行或再次出现 ==。\nconst HIGHLIGHT_MARKER_PATTERN = /==([^=\\n]+?)==/g;\n\n// 定义列表段落匹配规则:支持 CommonMark 未内建的 term + 下一行冒号定义写法。\nconst DEFINITION_LIST_PARAGRAPH_PATTERN = /^\\s*([^\\n:][^\\n]*)\\n\\s*:\\s+([^\\n]+)\\s*$/u;\n\n// 代码块外层 figure 类名。\nconst CODE_BLOCK_CLASS_NAME = \"scribdown-code-block\";\n\n// 代码块顶部 chrome 区域类名。\nconst CODE_BLOCK_CHROME_CLASS_NAME = \"scribdown-code-block__chrome\";\n\n// 代码块正文区域类名。\nconst CODE_BLOCK_BODY_CLASS_NAME = \"scribdown-code-block__body\";\n\n// 代码块语言标签类名。\nconst CODE_BLOCK_LANG_CLASS_NAME = \"scribdown-code-block__lang\";\n\n// 代码块复制按钮类名。\nconst CODE_BLOCK_COPY_CLASS_NAME = \"scribdown-code-block__copy\";\n\n// 代码块复制按钮图标类名。\nconst CODE_BLOCK_COPY_ICON_CLASS_NAME = \"scribdown-code-block__copy-icon\";\n\n// 代码块复制按钮\"复制\"态图标类名(默认显示)。\nconst CODE_BLOCK_COPY_ICON_COPY_CLASS_NAME = \"scribdown-code-block__copy-icon--copy\";\n\n// 代码块复制按钮\"已复制\"态图标类名(复制成功后显示)。\nconst CODE_BLOCK_COPY_ICON_CHECK_CLASS_NAME = \"scribdown-code-block__copy-icon--check\";\n\n// 代码块代码行类名。\nconst CODE_BLOCK_LINE_CLASS_NAME = \"scribdown-code-block__line\";\n\n// 代码块行号固定列类名。\nconst CODE_BLOCK_GUTTER_CLASS_NAME = \"scribdown-code-block__gutter\";\n\n// 代码块行号固定列中的单行节点类名。\nconst CODE_BLOCK_GUTTER_LINE_CLASS_NAME = \"scribdown-code-block__gutter-line\";\n\n// 代码块空行占位文本,避免空 span 被浏览器完全折叠。\nconst CODE_BLOCK_EMPTY_LINE_PLACEHOLDER = \"\\u200b\";\n\n// 代码块运行时已绑定标记的 dataset 键。\nconst CODE_BLOCK_HYDRATED_DATA_KEY = \"scribdownCodeBlockHydrated\";\n\n// 代码语言标识 → 展示标签的映射。\nconst CODE_LANGUAGE_DISPLAY_NAMES: Record<string, string> = {\n bash: \"Bash\",\n c: \"C\",\n cpp: \"C++\",\n csharp: \"C#\",\n css: \"CSS\",\n diff: \"Diff\",\n go: \"Go\",\n graphql: \"GraphQL\",\n html: \"HTML\",\n java: \"Java\",\n javascript: \"JavaScript\",\n js: \"JavaScript\",\n json: \"JSON\",\n jsx: \"JSX\",\n kotlin: \"Kotlin\",\n less: \"Less\",\n markdown: \"Markdown\",\n md: \"Markdown\",\n php: \"PHP\",\n python: \"Python\",\n py: \"Python\",\n ruby: \"Ruby\",\n rust: \"Rust\",\n rs: \"Rust\",\n sass: \"Sass\",\n scss: \"SCSS\",\n sh: \"Shell\",\n shell: \"Shell\",\n sql: \"SQL\",\n swift: \"Swift\",\n toml: \"TOML\",\n ts: \"TypeScript\",\n tsx: \"TSX\",\n typescript: \"TypeScript\",\n vue: \"Vue\",\n xml: \"XML\",\n yaml: \"YAML\",\n yml: \"YAML\",\n zsh: \"Zsh\"\n};\n\n// 代码块语言标识缺省值。\nconst CODE_LANGUAGE_DEFAULT_LABEL = \"Text\";\n\n// 代码块复制按钮已复制状态恢复延迟,单位毫秒。\nconst CODE_BLOCK_COPY_RESTORE_DELAY_MS = 1600;\n\n// 代码块复制按钮默认可访问名称。\nconst CODE_BLOCK_COPY_ARIA_LABEL = \"复制代码\";\n\n// 代码块复制按钮已复制可访问名称。\nconst CODE_BLOCK_COPY_ARIA_LABEL_COPIED = \"已复制\";\n\n// Shiki 高亮主题名(与 githubLightTheme 默认导出对应),用于 codeToHtml 选项。\nconst CODE_HIGHLIGHTER_THEME = \"github-light\";\n\n// 单例 Shiki 高亮器的初始化 Promise,确保整个进程只初始化一次。\nlet codeHighlighterPromise: Promise<HighlighterCore> | undefined;\n\n// 标记懒加载语言的请求去重:同一语言并发命中时复用同一 import Promise。\nconst codeHighlighterLazyLoadPromises = new Map<string, Promise<void>>();\n\n// 匹配渲染后 `<pre ...><code class=\"language-X\" ...>...</code></pre>` 的正则,用于 Shiki 替换。\n// 捕获 pre 与 code(class 之后)的附加属性(如 data-source-line),替换时原样保留,\n// 避免 remarkSourceLine 注入的 data-source-line 破坏匹配导致代码块不被高亮。\nconst CODE_BLOCK_HIGHLIGHT_PATTERN =\n /<pre([^>]*)><code class=\"language-([\\w-]+)\"([^>]*)>([\\s\\S]*?)<\\/code><\\/pre>/g;\n\n// 提取 Shiki HTML 输出中 `<code>...</code>` 之间内容的正则。\nconst SHIKI_CODE_INNER_PATTERN = /<code[^>]*>([\\s\\S]*?)<\\/code>/u;\n\n/**\n * 将 Markdown 文本渲染为安全 HTML。\n * 渲染链路固定开启代码高亮与 HTML sanitize(rehype 结构清洗 + DOMPurify),\n * 库不再对外暴露这些细节开关,保证所有宿主拿到一致的预览输出。\n * @param markdownText 输入的 Markdown 文本。\n * @returns 可挂载到 DOM 容器的 HTML 字符串。\n */\nexport async function renderMarkdown(markdownText: string): Promise<string> {\n // 渲染流水线:先解析 Markdown 与 GFM 行内标记,再转换为 HTML AST。\n // allowDangerousHtml + rehypeRaw 让 fixture 中的 <u> / <sub> / <sup> / <kbd> 等行内 HTML 保留下来。\n // 关键步骤:rehypeSanitize 在 stringify 前对结构做白名单清洗。\n const processor = unified()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkHighlightMark)\n .use(remarkDefinitionLists)\n .use(remarkTableOfContents)\n .use(remarkImageFigures)\n .use(remarkSourceLine)\n .use(remarkRehype, { allowDangerousHtml: true })\n .use(rehypeRaw)\n .use(rehypeFrameClass)\n .use(rehypeVideoFigures)\n .use(rehypeSanitize, createScribdownSanitizeSchema())\n .use(rehypeStringify);\n\n /** unified 渲染输出的 HTML 文本(已经过 rehype 结构清洗)。 */\n const renderedHtml = String(await processor.process(markdownText));\n\n // 关键步骤:rehype 清洗后再用 DOMPurify 做一次字符串级 sanitize,双重保险。\n const sanitizedHtml = sanitizeHtmlWithDomPurify(renderedHtml);\n\n // 关键步骤:sanitize 之后再做 Shiki 高亮,把 token span 添加到信任过滤后的代码体内。\n return highlightMarkdownCodeBlocks(sanitizedHtml);\n}\n\n/**\n * 取得(必要时初始化)Shiki 单例高亮器。\n * 使用 shiki/core + 显式 grammar 列表,避免默认 bundle 把 200+ 语言全部打进 dist。\n * @returns 已就绪的 Shiki 高亮器实例。\n */\nasync function getCodeHighlighter(): Promise<HighlighterCore> {\n if (!codeHighlighterPromise) {\n codeHighlighterPromise = createHighlighterCore({\n themes: [githubLightTheme],\n // eager loaders 通过动态 import 拆为独立 chunk,初始化时并发拉取;\n // 主 bundle 不再内联 grammar JSON,体积更小,多个 grammar 也可并行加载。\n langs: CODE_HIGHLIGHTER_EAGER_LOADERS,\n // oniguruma 引擎 + wasm;shiki/wasm 内部走 base64 内联,扩展环境无需额外资源声明。\n engine: createOnigurumaEngine(import(\"shiki/wasm\"))\n });\n }\n\n return codeHighlighterPromise;\n}\n\n/**\n * 把渲染 HTML 中的代码块替换为 Shiki 高亮后的结构。\n * @param html 已渲染(可能已 sanitize)的 HTML 文本。\n * @returns 含高亮 span 的 HTML 文本。\n */\nasync function highlightMarkdownCodeBlocks(html: string): Promise<string> {\n if (!CODE_BLOCK_HIGHLIGHT_PATTERN.test(html)) {\n return html;\n }\n // RegExp 是 global 状态,重置 lastIndex 以保证多次调用一致。\n CODE_BLOCK_HIGHLIGHT_PATTERN.lastIndex = 0;\n\n // 取得 Shiki 单例高亮器。\n const highlighter = await getCodeHighlighter();\n // 替换累积器:从输入 HTML 起逐段拼接处理结果。\n let resultHtml = \"\";\n // 上一段未处理 HTML 的结束索引。\n let lastIndex = 0;\n\n for (const match of html.matchAll(CODE_BLOCK_HIGHLIGHT_PATTERN)) {\n const [matched, preAttributes, rawLanguage, codeAttributes, encodedCode] = match;\n const matchStart = match.index ?? 0;\n\n resultHtml += html.slice(lastIndex, matchStart);\n resultHtml += await highlightSingleCodeBlock(highlighter, {\n rawLanguage,\n encodedCode,\n matchedHtml: matched,\n preAttributes,\n codeAttributes\n });\n lastIndex = matchStart + matched.length;\n }\n\n resultHtml += html.slice(lastIndex);\n return resultHtml;\n}\n\n/**\n * 单个代码块的 Shiki 高亮入参。\n */\ninterface CodeBlockHighlightInput {\n /** 代码块标注的语言标识。 */\n rawLanguage: string;\n /** 已 HTML 转义的代码内容。 */\n encodedCode: string;\n /** 原始匹配片段,作为兜底返回值。 */\n matchedHtml: string;\n /** pre 标签上的附加属性文本,替换时原样保留。 */\n preAttributes: string;\n /** code 标签上 class 之后的附加属性文本(如 data-source-line),替换时原样保留。 */\n codeAttributes: string;\n}\n\n/**\n * 高亮单个代码块,必要时按需加载语言。\n * @param highlighter Shiki 高亮器实例。\n * @param input 代码块高亮入参。\n * @returns 高亮后的代码块 HTML。\n */\nasync function highlightSingleCodeBlock(\n highlighter: HighlighterCore,\n input: CodeBlockHighlightInput\n): Promise<string> {\n // 代码块标注的语言标识。\n const { rawLanguage, encodedCode, matchedHtml, preAttributes, codeAttributes } = input;\n // 归一化语言标识:去除大小写差异,方便匹配 Shiki 内置名。\n const normalizedLanguage = rawLanguage.toLowerCase();\n\n // 关键步骤:mermaid 代码块跳过 Shiki 高亮,保留原始源码文本,\n // 后续 hydrate 阶段会读取 textContent 调用 mermaid 渲染。\n if (normalizedLanguage === MERMAID_LANGUAGE_ID) {\n return matchedHtml;\n }\n // Shiki 中真实可用的语言标识,找不到时退回 \"text\"。\n const resolvedLanguage = await ensureHighlighterLanguage(highlighter, normalizedLanguage);\n\n // 还原 HTML 转义,得到 Shiki 期望的原始代码。\n const codeText = decodeHtmlEntities(encodedCode);\n\n try {\n // Shiki 输出形如 `<pre class=\"shiki ...\"><code><span class=\"line\">...</span></code></pre>`。\n const highlightedHtml = highlighter.codeToHtml(codeText, {\n lang: resolvedLanguage,\n theme: CODE_HIGHLIGHTER_THEME\n });\n // 仅保留 `<code>` 内部内容,外层 `<pre><code>` 沿用原结构,便于 hydrate 识别。\n const innerMatch = highlightedHtml.match(SHIKI_CODE_INNER_PATTERN);\n const innerHtml = innerMatch?.[1] ?? encodedCode;\n\n return `<pre${preAttributes}><code class=\"language-${rawLanguage}\"${codeAttributes}>${innerHtml}</code></pre>`;\n } catch {\n // Shiki 调用异常时(语言未注册等)原样返回,避免影响整体渲染。\n return matchedHtml;\n }\n}\n\n/**\n * 在 Shiki 高亮器里确保给定语言可用,必要时动态加载。\n * @param highlighter Shiki 高亮器实例。\n * @param normalizedLanguage 已归一化的语言标识。\n * @returns 高亮时可使用的语言标识。\n */\nasync function ensureHighlighterLanguage(\n highlighter: HighlighterCore,\n normalizedLanguage: string\n): Promise<string> {\n if (normalizedLanguage.length === 0) {\n return \"text\";\n }\n\n // highlighter.getLoadedLanguages() 自带别名解析(grammar 自身声明的 aliases 也会被登记)。\n if (highlighter.getLoadedLanguages().includes(normalizedLanguage)) {\n return normalizedLanguage;\n }\n\n /** 从懒加载注册表查找对应 grammar import 函数。 */\n const lazyLoader = CODE_HIGHLIGHTER_LAZY_LANGS[normalizedLanguage];\n if (!lazyLoader) {\n return \"text\";\n }\n\n // 并发命中同一语言时复用同一 Promise,避免重复 import / loadLanguage。\n let pending = codeHighlighterLazyLoadPromises.get(normalizedLanguage);\n if (!pending) {\n pending = (async () => {\n const grammarModule = await lazyLoader();\n await highlighter.loadLanguage(grammarModule.default);\n })();\n codeHighlighterLazyLoadPromises.set(normalizedLanguage, pending);\n }\n\n try {\n await pending;\n return normalizedLanguage;\n } catch {\n return \"text\";\n }\n}\n\n// HTML 命名实体与对应字符的映射,覆盖代码块中常见的转义结果。\nconst HTML_NAMED_ENTITIES: Record<string, string> = {\n lt: \"<\",\n gt: \">\",\n quot: '\"',\n apos: \"'\",\n amp: \"&\"\n};\n\n// 匹配命名 / 十进制 / 十六进制三种字符实体;hex / dec 大小写均兼容。\nconst HTML_ENTITY_PATTERN = /&(?:(lt|gt|quot|apos|amp)|#x([0-9a-fA-F]+)|#(\\d+));/g;\n\n/**\n * 还原 HTML 文本中的字符实体(命名 + 十进制 + 十六进制)。\n *\n * 关键步骤:单次左到右扫描,避免“先解 `&amp;` 再解 `&lt;`”导致的二次解码漏洞,\n * 同时识别 `&#x3C;` 等十六进制实体(hast-util-to-html 在 Node 环境下会输出该形式)。\n *\n * @param encodedText HTML 转义后的文本。\n * @returns 还原后的原始文本。\n */\nfunction decodeHtmlEntities(encodedText: string): string {\n return encodedText.replace(\n HTML_ENTITY_PATTERN,\n (matched, namedEntity?: string, hexCodePoint?: string, decimalCodePoint?: string) => {\n if (namedEntity) {\n return HTML_NAMED_ENTITIES[namedEntity] ?? matched;\n }\n if (hexCodePoint) {\n return safeFromCodePoint(parseInt(hexCodePoint, 16), matched);\n }\n if (decimalCodePoint) {\n return safeFromCodePoint(parseInt(decimalCodePoint, 10), matched);\n }\n return matched;\n }\n );\n}\n\n/**\n * 安全地把 Unicode 码点转成字符,越界 / NaN 时回退原始片段,避免抛错。\n * @param codePoint Unicode 码点(10/16 进制解析后的数值)。\n * @param fallback 解析失败时返回的原始 HTML 片段。\n * @returns 对应字符或原始片段。\n */\nfunction safeFromCodePoint(codePoint: number, fallback: string): string {\n if (!Number.isFinite(codePoint) || codePoint < 0 || codePoint > 0x10ffff) {\n return fallback;\n }\n try {\n return String.fromCodePoint(codePoint);\n } catch {\n return fallback;\n }\n}\n\n/**\n * 使用 DOMPurify 清洗 HTML。\n * @param unsafeHtml 未清洗的 HTML。\n * @returns 清洗后的 HTML。\n */\nfunction sanitizeHtmlWithDomPurify(unsafeHtml: string): string {\n // DOMPurify 在 Node 环境下没有绑定 window 时会以工厂形态存在。\n const domPurify = DOMPurify as { sanitize?: (unsafeHtml: string) => string };\n\n if (typeof domPurify.sanitize !== \"function\") {\n return unsafeHtml;\n }\n\n return domPurify.sanitize(unsafeHtml);\n}\n\n/**\n * 给渲染后的图片绑定加载状态类名。\n * @param rootElement 包含 Markdown 渲染结果的根节点。\n */\nfunction hydrateMarkdownImages(rootElement: ParentNode): void {\n // 当前根节点内的所有图片元素。\n const imageElements = rootElement.querySelectorAll<HTMLImageElement>(\n `img.${IMAGE_ELEMENT_CLASS_NAME}`\n );\n\n imageElements.forEach(bindMarkdownImageState);\n}\n\n/**\n * 给渲染后的视频绑定加载/失败状态类名。\n * @param rootElement 包含 Markdown 渲染结果的根节点。\n */\nfunction hydrateMarkdownVideos(rootElement: ParentNode): void {\n // 当前根节点内的所有视频元素。\n const videoElements = rootElement.querySelectorAll<HTMLVideoElement>(\n `video.${VIDEO_ELEMENT_CLASS_NAME}`\n );\n\n videoElements.forEach(bindMarkdownVideoState);\n}\n\n/**\n * 把渲染后的代码块包装为带语言标签、复制按钮与行号的 chrome 结构。\n * @param rootElement 包含 Markdown 渲染结果的根节点。\n */\nfunction hydrateCodeBlocks(rootElement: ParentNode): void {\n // 当前根节点内的所有未绑定 chrome 的 pre 元素。\n const preElements = rootElement.querySelectorAll<HTMLPreElement>(\"pre\");\n\n preElements.forEach(decorateCodeBlock);\n}\n\n/**\n * Mermaid 渲染句柄缓存:仅在浏览器环境(含 VS Code webview)下加载,\n * 避免在 Node 单元测试环境触发 mermaid 依赖加载。\n */\nlet mermaidLoaderPromise: Promise<MermaidApi | undefined> | undefined;\n\n/**\n * Mermaid 11+ 的最小 API 子集,仅声明渲染必需成员,避免引入巨大类型。\n */\ninterface MermaidApi {\n initialize: (config: Record<string, unknown>) => void;\n parse: (source: string) => Promise<unknown> | unknown;\n render: (\n id: string,\n source: string,\n container?: Element\n ) => Promise<{ svg: string; bindFunctions?: (element: Element) => void }>;\n}\n\n/**\n * 把渲染后的 mermaid 代码块转换为图表 figure 容器,并按需触发异步渲染。\n *\n * 拆分为两步:\n * 1. {@link decorateMermaidBlock} 同步把 `<pre><code>` 替换为 figure 结构,把源码寄存在 dataset 上。\n * 2. {@link kickOffPendingMermaidRenders} 仅对真正落入 live DOM 的 figure 触发 mermaid.render,\n * 避免 VS Code 预览路径里 hydrate 跑在 detached 节点上、被随后的 morphdom 丢弃。\n *\n * @param rootElement 包含 Markdown 渲染结果的根节点。\n */\nfunction hydrateMermaidBlocks(rootElement: ParentNode): void {\n // 当前根节点内所有未 hydrate 的 mermaid 代码块。\n const mermaidCodeElements = rootElement.querySelectorAll<HTMLElement>(\n `pre > code.language-${MERMAID_LANGUAGE_ID}`\n );\n\n mermaidCodeElements.forEach((codeElement) => {\n // 对应的 pre 容器。\n const preElement = codeElement.parentElement as HTMLPreElement | null;\n if (!preElement) {\n return;\n }\n\n // 若 pre 已经被代码块 chrome 包裹,跳过避免重复处理。\n if (preElement.dataset[CODE_BLOCK_HYDRATED_DATA_KEY] === \"true\") {\n return;\n }\n\n decorateMermaidBlock(preElement, codeElement);\n });\n\n kickOffPendingMermaidRenders(rootElement);\n}\n\n/**\n * 针对 live DOM 中仍处于 loading 态的 mermaid figure 启动 mermaid.render。\n * 未连接到 document 的 figure 直接跳过,等下一次 hydrate(live DOM 阶段)再触发。\n * @param rootElement 包含 Markdown 渲染结果的根节点。\n */\nfunction kickOffPendingMermaidRenders(rootElement: ParentNode): void {\n // 仍在 loading 态的 figure 集合。\n const pendingFigures = rootElement.querySelectorAll<HTMLElement>(\n `.${MERMAID_FIGURE_CLASS_NAME}.${MERMAID_FIGURE_LOADING_CLASS_NAME}`\n );\n\n pendingFigures.forEach((figureElement) => {\n // 仅在已挂载到 document 时启动渲染,避免在 detached 节点上空跑。\n if (!figureElement.isConnected) {\n return;\n }\n if (figureElement.dataset[MERMAID_RENDER_STARTED_DATA_KEY] === \"true\") {\n return;\n }\n\n // 画布与 mermaid 源码均从 figure 结构 / dataset 上恢复。\n const canvasElement = figureElement.querySelector<HTMLElement>(\n `.${MERMAID_CANVAS_CLASS_NAME}`\n );\n const mermaidSource = figureElement.dataset[MERMAID_SOURCE_DATA_KEY] ?? \"\";\n\n if (!canvasElement || mermaidSource.length === 0) {\n return;\n }\n\n figureElement.dataset[MERMAID_RENDER_STARTED_DATA_KEY] = \"true\";\n void renderMermaidIntoCanvas(figureElement, canvasElement, mermaidSource);\n });\n}\n\n/**\n * 把 pre + code 替换为 mermaid figure 结构,并异步渲染 SVG。\n * @param preElement 原始代码块 pre 元素。\n * @param codeElement 原始代码块 code 元素。\n */\nfunction decorateMermaidBlock(preElement: HTMLPreElement, codeElement: HTMLElement): void {\n // 已经 hydrate 过的代码块直接跳过。\n if (preElement.dataset[MERMAID_HYDRATED_DATA_KEY] === \"true\") {\n return;\n }\n preElement.dataset[MERMAID_HYDRATED_DATA_KEY] = \"true\";\n\n // 当前 pre 所属 document。\n const ownerDocument = preElement.ownerDocument;\n // 关键步骤:在替换 DOM 前抓取原始 mermaid 源码文本。\n const mermaidSource = (codeElement.textContent ?? \"\").replace(/\\n+$/u, \"\");\n\n // 外层 figure 容器,承载顶部标签与图表正文。\n const figureElement = ownerDocument.createElement(\"figure\");\n figureElement.className = `${MERMAID_FIGURE_CLASS_NAME} ${MERMAID_FIGURE_LOADING_CLASS_NAME}`;\n\n // 顶部 chrome,仅承载 Mermaid 类型标签。\n const chromeElement = ownerDocument.createElement(\"div\");\n chromeElement.className = MERMAID_CHROME_CLASS_NAME;\n\n const labelElement = ownerDocument.createElement(\"span\");\n labelElement.className = MERMAID_LABEL_CLASS_NAME;\n labelElement.textContent = MERMAID_LABEL_TEXT;\n chromeElement.append(labelElement);\n\n // 正文容器,承载 SVG 画布与失败态。\n const bodyElement = ownerDocument.createElement(\"div\");\n bodyElement.className = MERMAID_BODY_CLASS_NAME;\n\n // 用于挂载 SVG 的画布节点。\n const canvasElement = ownerDocument.createElement(\"div\");\n canvasElement.className = MERMAID_CANVAS_CLASS_NAME;\n canvasElement.setAttribute(\"role\", \"img\");\n canvasElement.setAttribute(\"aria-label\", MERMAID_LABEL_TEXT);\n\n // 右下角悬浮全屏按钮,渲染成功后再启用。\n const fullscreenButtonElement = ownerDocument.createElement(\"button\");\n fullscreenButtonElement.type = \"button\";\n fullscreenButtonElement.className = MERMAID_FULLSCREEN_BUTTON_CLASS_NAME;\n fullscreenButtonElement.setAttribute(\"aria-label\", MERMAID_FULLSCREEN_BUTTON_ARIA_LABEL);\n // 渲染过程中先禁用,避免点击空白图表。\n fullscreenButtonElement.disabled = true;\n fullscreenButtonElement.innerHTML = MERMAID_FULLSCREEN_ICON_SVG;\n fullscreenButtonElement.addEventListener(\"click\", handleMermaidFullscreenButtonClick);\n\n bodyElement.append(canvasElement, fullscreenButtonElement);\n figureElement.append(chromeElement, bodyElement);\n\n // 关键步骤:把源码行锚点从 code 迁移到 figure,对齐编辑器双向滚动。\n const sourceLine = codeElement.getAttribute(SOURCE_LINE_DATA_ATTRIBUTE);\n if (sourceLine !== null) {\n figureElement.setAttribute(SOURCE_LINE_DATA_ATTRIBUTE, sourceLine);\n }\n\n // 关键步骤:把源码寄存在 figure 上,留给 live-DOM 阶段读取后真正触发 mermaid 渲染。\n // 不在此处直接 await render:VS Code 预览首先在 detached 节点上 hydrate,\n // 之后才把 figure 合并进 live DOM,提前渲染的结果会被 morphdom 丢弃。\n figureElement.dataset[MERMAID_SOURCE_DATA_KEY] = mermaidSource;\n\n preElement.replaceWith(figureElement);\n}\n\n/**\n * 加载并初始化 mermaid 实例,浏览器环境外返回 undefined。\n * @returns mermaid API 句柄。\n */\nasync function loadMermaid(): Promise<MermaidApi | undefined> {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n return undefined;\n }\n\n if (!mermaidLoaderPromise) {\n mermaidLoaderPromise = (async () => {\n // 动态导入:仅在确实出现 mermaid 块时才下载 mermaid 主包。\n const mermaidModule = (await import(\"mermaid\")) as { default: MermaidApi };\n const mermaidApi = mermaidModule.default;\n // startOnLoad=false 由 hydrate 主动控制渲染时机;securityLevel=strict 阻断脚本注入。\n // useMaxWidth=false:让 mermaid 输出带固有宽高属性的 SVG,避免它内联 width:100%/max-width\n // 强行覆盖 CSS,从而把缩放完全交给画布上的 max-width/max-height:100% 配合 viewBox 等比适配。\n mermaidApi.initialize({\n startOnLoad: false,\n securityLevel: \"strict\",\n theme: \"default\",\n flowchart: { useMaxWidth: false },\n sequence: { useMaxWidth: false },\n class: { useMaxWidth: false },\n state: { useMaxWidth: false },\n gantt: { useMaxWidth: false },\n er: { useMaxWidth: false },\n journey: { useMaxWidth: false },\n pie: { useMaxWidth: false },\n requirement: { useMaxWidth: false },\n c4: { useMaxWidth: false },\n mindmap: { useMaxWidth: false },\n timeline: { useMaxWidth: false },\n gitGraph: { useMaxWidth: false },\n quadrantChart: { useMaxWidth: false },\n xyChart: { useMaxWidth: false },\n sankey: { useMaxWidth: false },\n block: { useMaxWidth: false }\n });\n return mermaidApi;\n })().catch((loadError: unknown) => {\n // 加载失败后重置 promise,给下次渲染重试机会。\n mermaidLoaderPromise = undefined;\n throw loadError;\n });\n }\n\n return mermaidLoaderPromise;\n}\n\n/**\n * 异步把 mermaid 源码渲染为 SVG 注入指定画布,失败时切换到 fallback 态。\n * @param figureElement mermaid 外层 figure 容器。\n * @param canvasElement SVG 挂载点。\n * @param mermaidSource mermaid 源码文本。\n */\nasync function renderMermaidIntoCanvas(\n figureElement: HTMLElement,\n canvasElement: HTMLElement,\n mermaidSource: string\n): Promise<void> {\n try {\n const mermaidApi = await loadMermaid();\n if (!mermaidApi) {\n // 非浏览器环境:保留 loading 类名但不抛错,避免单测污染。\n return;\n }\n\n mermaidRenderIdCounter += 1;\n const renderId = `${MERMAID_RENDER_ID_PREFIX}${mermaidRenderIdCounter}`;\n const { svg, bindFunctions } = await mermaidApi.render(renderId, mermaidSource);\n\n canvasElement.innerHTML = svg;\n bindFunctions?.(canvasElement);\n figureElement.classList.remove(MERMAID_FIGURE_LOADING_CLASS_NAME);\n figureElement.classList.add(MERMAID_FIGURE_LOADED_CLASS_NAME);\n\n // 关键步骤:记录原始 SVG 文本,全屏查看器以同样的源码注入,避免引用同一 DOM。\n figureElement.dataset[MERMAID_VIEWER_SOURCE_DATA_KEY] = svg;\n figureElement.dataset[MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY] = \"true\";\n\n // 渲染成功后启用全屏按钮。\n const fullscreenButtonElement = figureElement.querySelector<HTMLButtonElement>(\n `.${MERMAID_FULLSCREEN_BUTTON_CLASS_NAME}`\n );\n if (fullscreenButtonElement) {\n fullscreenButtonElement.disabled = false;\n }\n } catch (renderError: unknown) {\n showMermaidFallback(figureElement, canvasElement, mermaidSource, renderError);\n }\n}\n\n/**\n * 切换 mermaid 容器到失败态:隐藏画布、展示错误摘要与源码。\n * @param figureElement mermaid 外层 figure。\n * @param canvasElement SVG 画布节点。\n * @param mermaidSource 原始 mermaid 源码。\n * @param renderError mermaid 抛出的错误。\n */\nfunction showMermaidFallback(\n figureElement: HTMLElement,\n canvasElement: HTMLElement,\n mermaidSource: string,\n renderError: unknown\n): void {\n const ownerDocument = figureElement.ownerDocument;\n figureElement.classList.remove(MERMAID_FIGURE_LOADING_CLASS_NAME);\n figureElement.classList.add(MERMAID_FIGURE_FAILED_CLASS_NAME);\n\n // 失败态:移除右下角全屏按钮,避免对无图表的容器开启查看器。\n const fullscreenButtonElement = figureElement.querySelector<HTMLButtonElement>(\n `.${MERMAID_FULLSCREEN_BUTTON_CLASS_NAME}`\n );\n fullscreenButtonElement?.remove();\n\n // 失败态:清空 SVG 画布并替换为错误说明 + 源码块。\n canvasElement.replaceChildren();\n\n // mermaid 抛错时通常会污染 document 末尾的临时 div,需要清理。\n cleanupOrphanMermaidNodes(ownerDocument);\n\n const fallbackElement = ownerDocument.createElement(\"div\");\n fallbackElement.className = MERMAID_FALLBACK_CLASS_NAME;\n\n const iconElement = ownerDocument.createElement(\"span\");\n iconElement.className = MERMAID_FALLBACK_ICON_CLASS_NAME;\n iconElement.setAttribute(\"aria-hidden\", \"true\");\n fallbackElement.append(iconElement);\n\n const textElement = ownerDocument.createElement(\"p\");\n textElement.className = MERMAID_FALLBACK_TEXT_CLASS_NAME;\n textElement.textContent = MERMAID_FALLBACK_DEFAULT_TEXT;\n fallbackElement.append(textElement);\n\n // mermaid 错误对象常带可读 message,附在源码块前给排查使用。\n const errorMessage = extractErrorMessage(renderError);\n if (errorMessage) {\n const messageElement = ownerDocument.createElement(\"p\");\n messageElement.className = MERMAID_FALLBACK_SOURCE_CLASS_NAME;\n messageElement.textContent = errorMessage;\n fallbackElement.append(messageElement);\n }\n\n // 源码 pre:失败时把原文展示给用户便于复制修改。\n if (mermaidSource.length > 0) {\n const sourceElement = ownerDocument.createElement(\"pre\");\n sourceElement.className = MERMAID_FALLBACK_SOURCE_CLASS_NAME;\n sourceElement.textContent = mermaidSource;\n fallbackElement.append(sourceElement);\n }\n\n canvasElement.replaceWith(fallbackElement);\n}\n\n/**\n * 提取 mermaid 渲染错误的可读文本。\n * @param renderError mermaid 抛出的错误对象。\n * @returns 错误描述文本,未识别时返回空串。\n */\nfunction extractErrorMessage(renderError: unknown): string {\n if (renderError instanceof Error) {\n return renderError.message;\n }\n if (typeof renderError === \"string\") {\n return renderError;\n }\n return \"\";\n}\n\n/**\n * 清理 mermaid 渲染失败时残留在文档尾部的临时节点。\n * Mermaid 在 render 抛错时不一定会移除自己挂在 body 上的占位 div。\n * @param ownerDocument 当前 document。\n */\nfunction cleanupOrphanMermaidNodes(ownerDocument: Document): void {\n // mermaid 在 document.body 末尾创建临时容器,id 以 d 开头或包含 render id 前缀。\n const orphanNodes = ownerDocument.querySelectorAll<HTMLElement>(\n `body > [id^=\"${MERMAID_RENDER_ID_PREFIX}\"], body > div[id^=\"d\"][id*=\"mermaid\"]`\n );\n orphanNodes.forEach((orphanNode) => {\n orphanNode.remove();\n });\n}\n\n/**\n * 右下角全屏按钮使用的 SVG 图标(两个对角线箭头组成的方框)。\n */\nconst MERMAID_FULLSCREEN_ICON_SVG =\n '<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" aria-hidden=\"true\" focusable=\"false\">' +\n '<path d=\"M4 9 V4 H9 M15 4 H20 V9 M20 15 V20 H15 M9 20 H4 V15\" ' +\n 'fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>' +\n \"</svg>\";\n\n/**\n * 处理 mermaid 全屏按钮点击:找到所属 figure,把 SVG 源码注入全屏查看器。\n * @param event 全屏按钮点击事件。\n */\nfunction handleMermaidFullscreenButtonClick(event: MouseEvent): void {\n // 触发事件的按钮元素。\n const buttonElement = event.currentTarget as HTMLButtonElement;\n // 所属的 mermaid figure。\n const figureElement = buttonElement.closest<HTMLElement>(`.${MERMAID_FIGURE_CLASS_NAME}`);\n\n if (!figureElement) {\n return;\n }\n\n if (figureElement.dataset[MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY] !== \"true\") {\n return;\n }\n\n // 之前渲染缓存下来的 SVG 源码。\n const svgSource = figureElement.dataset[MERMAID_VIEWER_SOURCE_DATA_KEY] ?? \"\";\n\n if (svgSource.length === 0) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n openMarkdownMermaidViewer(figureElement.ownerDocument, svgSource);\n}\n\n/**\n * 打开 mermaid 全屏查看器。\n * @param ownerDocument 当前 figure 所在 document。\n * @param svgSource 缓存的 SVG HTML 源码。\n */\nfunction openMarkdownMermaidViewer(ownerDocument: Document, svgSource: string): void {\n // 当前 document 对应的查看器状态(单例)。\n const viewerState = getOrCreateMarkdownMermaidViewerState(ownerDocument);\n\n // 关键步骤:注入 SVG 并解析固有尺寸,做为 fit + 缩放计算基准。\n viewerState.canvasElement.innerHTML = svgSource;\n const svgDimensions = readSvgNaturalDimensions(viewerState.canvasElement);\n viewerState.naturalWidth = svgDimensions.width;\n viewerState.naturalHeight = svgDimensions.height;\n viewerState.viewportElement.scrollLeft = 0;\n viewerState.viewportElement.scrollTop = 0;\n viewerState.zoomValue = IMAGE_VIEWER_DEFAULT_ZOOM;\n\n showMarkdownMermaidViewerDialog(viewerState);\n requestMarkdownMermaidViewerLayout(viewerState);\n}\n\n/**\n * 解析画布内 SVG 的固有尺寸,优先读取 viewBox。\n * @param canvasElement 包含 SVG 的画布节点。\n * @returns 固有宽高,缺省回退为 720 × 480。\n */\nfunction readSvgNaturalDimensions(canvasElement: HTMLElement): {\n width: number;\n height: number;\n} {\n // 画布中第一个 SVG 节点。\n const svgElement = canvasElement.querySelector<SVGSVGElement>(\"svg\");\n\n if (!svgElement) {\n return { width: 720, height: 480 };\n }\n\n // 优先读取 viewBox.baseVal,未声明时回退到 width/height 属性。\n const viewBox = svgElement.viewBox?.baseVal;\n\n if (viewBox && viewBox.width > 0 && viewBox.height > 0) {\n return { width: viewBox.width, height: viewBox.height };\n }\n\n // SVG width/height 可能是百分比,无法直接当作固有尺寸时退化为兜底值。\n const widthAttribute = parseFloat(svgElement.getAttribute(\"width\") ?? \"\");\n const heightAttribute = parseFloat(svgElement.getAttribute(\"height\") ?? \"\");\n\n if (Number.isFinite(widthAttribute) && Number.isFinite(heightAttribute)) {\n return {\n width: widthAttribute > 0 ? widthAttribute : 720,\n height: heightAttribute > 0 ? heightAttribute : 480\n };\n }\n\n return { width: 720, height: 480 };\n}\n\n/**\n * 获取或创建当前 document 上的 mermaid 全屏查看器单例。\n * @param ownerDocument 当前 document。\n * @returns 查看器状态。\n */\nfunction getOrCreateMarkdownMermaidViewerState(\n ownerDocument: Document\n): MarkdownMermaidViewerState {\n const existingViewerState = mermaidViewerStateByDocument.get(ownerDocument);\n\n if (existingViewerState) {\n return existingViewerState;\n }\n\n const createdViewerState = createMarkdownMermaidViewerState(ownerDocument);\n\n mermaidViewerStateByDocument.set(ownerDocument, createdViewerState);\n\n return createdViewerState;\n}\n\n/**\n * 创建 mermaid 全屏查看器 DOM 与事件绑定。\n * @param ownerDocument 当前 document。\n * @returns 查看器状态。\n */\nfunction createMarkdownMermaidViewerState(ownerDocument: Document): MarkdownMermaidViewerState {\n /** 查看器外层 dialog。 */\n const dialogElement = ownerDocument.createElement(\"dialog\") as HTMLDialogElement;\n /** 顶部 chrome。 */\n const chromeElement = ownerDocument.createElement(\"div\");\n /** caption 节点。 */\n const captionElement = ownerDocument.createElement(\"p\");\n /** 控件容器。 */\n const controlsElement = ownerDocument.createElement(\"div\");\n /** 缩小按钮。 */\n const zoomOutButtonElement = createMarkdownMermaidViewerButton(ownerDocument, {\n ariaLabel: \"缩小图表\",\n text: IMAGE_VIEWER_ZOOM_OUT_TEXT\n });\n /** 缩放比例显示。 */\n const zoomValueElement = ownerDocument.createElement(\"span\");\n /** 放大按钮。 */\n const zoomInButtonElement = createMarkdownMermaidViewerButton(ownerDocument, {\n ariaLabel: \"放大图表\",\n text: IMAGE_VIEWER_ZOOM_IN_TEXT\n });\n /** 重置按钮。 */\n const resetButtonElement = createMarkdownMermaidViewerButton(ownerDocument, {\n ariaLabel: \"重置缩放\",\n text: IMAGE_VIEWER_RESET_TEXT\n });\n /** 关闭按钮。 */\n const closeButtonElement = createMarkdownMermaidViewerButton(ownerDocument, {\n ariaLabel: \"关闭全屏查看\",\n className: MERMAID_VIEWER_CLOSE_BUTTON_CLASS_NAME,\n text: IMAGE_VIEWER_CLOSE_TEXT\n });\n /** 滚动视口。 */\n const viewportElement = ownerDocument.createElement(\"div\");\n /** SVG 画布。 */\n const canvasElement = ownerDocument.createElement(\"div\");\n\n /** 查看器运行时状态。 */\n const viewerState: MarkdownMermaidViewerState = {\n dialogElement,\n captionElement,\n zoomValueElement,\n zoomOutButtonElement,\n zoomInButtonElement,\n resetButtonElement,\n closeButtonElement,\n viewportElement,\n canvasElement,\n naturalWidth: 720,\n naturalHeight: 480,\n zoomValue: IMAGE_VIEWER_DEFAULT_ZOOM,\n isDragging: false,\n dragStartClientX: 0,\n dragStartClientY: 0,\n dragStartScrollLeft: 0,\n dragStartScrollTop: 0\n };\n /** 当前 document 对应的 window。 */\n const ownerWindow = ownerDocument.defaultView;\n\n dialogElement.className = MERMAID_VIEWER_DIALOG_CLASS_NAME;\n dialogElement.setAttribute(\"aria-modal\", \"true\");\n dialogElement.setAttribute(\"aria-label\", \"全屏查看 Mermaid 图表\");\n dialogElement.setAttribute(\"tabindex\", \"-1\");\n chromeElement.className = MERMAID_VIEWER_CHROME_CLASS_NAME;\n captionElement.className = MERMAID_VIEWER_CAPTION_CLASS_NAME;\n captionElement.textContent = MERMAID_LABEL_TEXT;\n controlsElement.className = MERMAID_VIEWER_CONTROLS_CLASS_NAME;\n zoomValueElement.className = MERMAID_VIEWER_ZOOM_VALUE_CLASS_NAME;\n viewportElement.className = MERMAID_VIEWER_VIEWPORT_CLASS_NAME;\n canvasElement.className = MERMAID_VIEWER_CANVAS_CLASS_NAME;\n\n mermaidViewerStateByDialogElement.set(dialogElement, viewerState);\n controlsElement.append(\n zoomOutButtonElement,\n zoomValueElement,\n zoomInButtonElement,\n resetButtonElement,\n closeButtonElement\n );\n chromeElement.append(captionElement, controlsElement);\n viewportElement.append(canvasElement);\n dialogElement.append(chromeElement, viewportElement);\n ownerDocument.body.append(dialogElement);\n\n dialogElement.addEventListener(\"click\", handleMarkdownMermaidViewerBackdropClick);\n dialogElement.addEventListener(\"close\", handleMarkdownMermaidViewerClose);\n dialogElement.addEventListener(\"keydown\", handleMarkdownMermaidViewerKeyDown);\n viewportElement.addEventListener(\"wheel\", handleMarkdownMermaidViewerWheel, { passive: false });\n viewportElement.addEventListener(\"pointerdown\", handleMarkdownMermaidViewerPointerDown);\n viewportElement.addEventListener(\"pointermove\", handleMarkdownMermaidViewerPointerMove);\n viewportElement.addEventListener(\"pointerup\", handleMarkdownMermaidViewerPointerUp);\n viewportElement.addEventListener(\"pointercancel\", handleMarkdownMermaidViewerPointerUp);\n zoomOutButtonElement.addEventListener(\"click\", handleMarkdownMermaidViewerZoomOutClick);\n zoomInButtonElement.addEventListener(\"click\", handleMarkdownMermaidViewerZoomInClick);\n resetButtonElement.addEventListener(\"click\", handleMarkdownMermaidViewerResetClick);\n closeButtonElement.addEventListener(\"click\", handleMarkdownMermaidViewerCloseClick);\n\n if (ownerWindow) {\n ownerWindow.addEventListener(\"resize\", handleMarkdownMermaidViewerWindowResize);\n }\n\n updateMarkdownMermaidViewerZoom(viewerState, IMAGE_VIEWER_DEFAULT_ZOOM);\n\n return viewerState;\n}\n\n/**\n * 创建查看器按钮。\n * @param ownerDocument 当前 document。\n * @param options 按钮文案与可访问名称。\n * @returns 已配置的按钮元素。\n */\nfunction createMarkdownMermaidViewerButton(\n ownerDocument: Document,\n options: MarkdownImageViewerButtonOptions\n): HTMLButtonElement {\n const buttonElement = ownerDocument.createElement(\"button\");\n buttonElement.type = \"button\";\n buttonElement.className = options.className\n ? `${MERMAID_VIEWER_BUTTON_CLASS_NAME} ${options.className}`\n : MERMAID_VIEWER_BUTTON_CLASS_NAME;\n buttonElement.textContent = options.text;\n buttonElement.setAttribute(\"aria-label\", options.ariaLabel);\n return buttonElement;\n}\n\n/**\n * 显示 mermaid 全屏查看器 dialog。\n * @param viewerState 查看器状态。\n */\nfunction showMarkdownMermaidViewerDialog(viewerState: MarkdownMermaidViewerState): void {\n if (typeof viewerState.dialogElement.showModal === \"function\") {\n if (!viewerState.dialogElement.open) {\n viewerState.dialogElement.showModal();\n }\n } else {\n viewerState.dialogElement.setAttribute(\"open\", \"\");\n }\n viewerState.dialogElement.focus();\n}\n\n/**\n * 延后一次布局计算,确保 dialog 已经进入可测量状态。\n * @param viewerState 查看器状态。\n */\nfunction requestMarkdownMermaidViewerLayout(viewerState: MarkdownMermaidViewerState): void {\n const ownerWindow = viewerState.dialogElement.ownerDocument.defaultView;\n\n if (!ownerWindow) {\n updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue);\n return;\n }\n\n ownerWindow.setTimeout(handleMarkdownMermaidViewerDeferredLayout, 0, viewerState);\n}\n\n/**\n * 处理延后的布局计算。\n * @param viewerState 查看器状态。\n */\nfunction handleMarkdownMermaidViewerDeferredLayout(\n viewerState: MarkdownMermaidViewerState\n): void {\n updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue);\n}\n\n/**\n * 点击遮罩关闭查看器。\n * @param event dialog 点击事件。\n */\nfunction handleMarkdownMermaidViewerBackdropClick(event: MouseEvent): void {\n if (event.target !== event.currentTarget) {\n return;\n }\n const dialogElement = event.currentTarget as HTMLDialogElement;\n const viewerState = mermaidViewerStateByDialogElement.get(dialogElement);\n if (!viewerState) {\n return;\n }\n closeMarkdownMermaidViewer(viewerState);\n}\n\n/**\n * dialog close 后复位临时状态。\n * @param event dialog close 事件。\n */\nfunction handleMarkdownMermaidViewerClose(event: Event): void {\n const dialogElement = event.currentTarget as HTMLDialogElement;\n const viewerState = mermaidViewerStateByDialogElement.get(dialogElement);\n if (!viewerState) {\n return;\n }\n resetMarkdownMermaidViewerAfterClose(viewerState);\n}\n\n/**\n * 关闭后复位查看器内部临时状态(清空画布、重置缩放)。\n * @param viewerState 查看器状态。\n */\nfunction resetMarkdownMermaidViewerAfterClose(viewerState: MarkdownMermaidViewerState): void {\n viewerState.canvasElement.replaceChildren();\n viewerState.viewportElement.scrollLeft = 0;\n viewerState.viewportElement.scrollTop = 0;\n viewerState.zoomValue = IMAGE_VIEWER_DEFAULT_ZOOM;\n viewerState.isDragging = false;\n viewerState.dialogElement.classList.remove(MERMAID_VIEWER_DRAGGING_CLASS_NAME);\n viewerState.dialogElement.classList.remove(MERMAID_VIEWER_ZOOMED_CLASS_NAME);\n}\n\n/**\n * 处理键盘缩放和关闭。\n * @param event 键盘事件。\n */\nfunction handleMarkdownMermaidViewerKeyDown(event: KeyboardEvent): void {\n const viewerState = getMarkdownMermaidViewerStateFromEvent(event);\n if (!viewerState) {\n return;\n }\n\n if (event.key === \"+\" || event.key === \"=\") {\n event.preventDefault();\n updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue + IMAGE_VIEWER_ZOOM_STEP);\n return;\n }\n\n if (event.key === \"-\") {\n event.preventDefault();\n updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue - IMAGE_VIEWER_ZOOM_STEP);\n return;\n }\n\n if (event.key === \"0\") {\n event.preventDefault();\n updateMarkdownMermaidViewerZoom(viewerState, IMAGE_VIEWER_DEFAULT_ZOOM);\n return;\n }\n\n if (event.key === \"Escape\") {\n event.preventDefault();\n closeMarkdownMermaidViewer(viewerState);\n }\n}\n\n/**\n * 处理 ctrl/cmd + 滚轮缩放图表。\n * @param event 视口滚轮事件。\n */\nfunction handleMarkdownMermaidViewerWheel(event: WheelEvent): void {\n if (!event.ctrlKey && !event.metaKey) {\n return;\n }\n const viewerState = getMarkdownMermaidViewerStateFromEvent(event);\n if (!viewerState) {\n return;\n }\n // 与 deltaY 成比例并夹紧到上限,让触控板细滑动产生细粒度缩放、鼠标滚轮单格仍可见。\n const rawDelta = -event.deltaY * IMAGE_VIEWER_WHEEL_ZOOM_FACTOR;\n const zoomDelta = Math.max(\n -IMAGE_VIEWER_WHEEL_ZOOM_MAX_DELTA,\n Math.min(IMAGE_VIEWER_WHEEL_ZOOM_MAX_DELTA, rawDelta)\n );\n event.preventDefault();\n updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue + zoomDelta, {\n x: event.clientX,\n y: event.clientY\n });\n}\n\n/**\n * 处理鼠标按下开启拖拽平移。\n * @param event 视口指针事件。\n */\nfunction handleMarkdownMermaidViewerPointerDown(event: PointerEvent): void {\n if (event.button !== 0) {\n return;\n }\n const viewportElement = event.currentTarget as HTMLElement;\n const viewerState = getMarkdownMermaidViewerStateFromEvent(event);\n if (!viewerState) {\n return;\n }\n const canScrollHorizontally = viewportElement.scrollWidth > viewportElement.clientWidth;\n const canScrollVertically = viewportElement.scrollHeight > viewportElement.clientHeight;\n if (!canScrollHorizontally && !canScrollVertically) {\n return;\n }\n viewerState.isDragging = true;\n viewerState.dragStartClientX = event.clientX;\n viewerState.dragStartClientY = event.clientY;\n viewerState.dragStartScrollLeft = viewportElement.scrollLeft;\n viewerState.dragStartScrollTop = viewportElement.scrollTop;\n viewerState.dialogElement.classList.add(MERMAID_VIEWER_DRAGGING_CLASS_NAME);\n if (typeof viewportElement.setPointerCapture === \"function\") {\n viewportElement.setPointerCapture(event.pointerId);\n }\n event.preventDefault();\n}\n\n/**\n * 处理鼠标移动跟随滚动。\n * @param event 视口指针事件。\n */\nfunction handleMarkdownMermaidViewerPointerMove(event: PointerEvent): void {\n const viewportElement = event.currentTarget as HTMLElement;\n const viewerState = getMarkdownMermaidViewerStateFromEvent(event);\n if (!viewerState || !viewerState.isDragging) {\n return;\n }\n const deltaX = event.clientX - viewerState.dragStartClientX;\n const deltaY = event.clientY - viewerState.dragStartClientY;\n viewportElement.scrollLeft = viewerState.dragStartScrollLeft - deltaX;\n viewportElement.scrollTop = viewerState.dragStartScrollTop - deltaY;\n}\n\n/**\n * 处理鼠标抬起/取消,结束拖拽。\n * @param event 视口指针事件。\n */\nfunction handleMarkdownMermaidViewerPointerUp(event: PointerEvent): void {\n const viewportElement = event.currentTarget as HTMLElement;\n const viewerState = getMarkdownMermaidViewerStateFromEvent(event);\n if (!viewerState || !viewerState.isDragging) {\n return;\n }\n viewerState.isDragging = false;\n viewerState.dialogElement.classList.remove(MERMAID_VIEWER_DRAGGING_CLASS_NAME);\n if (\n typeof viewportElement.releasePointerCapture === \"function\" &&\n viewportElement.hasPointerCapture(event.pointerId)\n ) {\n viewportElement.releasePointerCapture(event.pointerId);\n }\n}\n\n/**\n * 处理 window resize 后的画布尺寸适配。\n * @param event window resize 事件。\n */\nfunction handleMarkdownMermaidViewerWindowResize(event: Event): void {\n const ownerWindow = event.currentTarget as Window;\n const viewerState = mermaidViewerStateByDocument.get(ownerWindow.document);\n if (!viewerState || !isMarkdownMermaidViewerOpen(viewerState)) {\n return;\n }\n updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue);\n}\n\n/**\n * 处理缩小按钮点击。\n * @param event 按钮点击事件。\n */\nfunction handleMarkdownMermaidViewerZoomOutClick(event: MouseEvent): void {\n const viewerState = getMarkdownMermaidViewerStateFromEvent(event);\n if (!viewerState) {\n return;\n }\n updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue - IMAGE_VIEWER_ZOOM_STEP);\n}\n\n/**\n * 处理放大按钮点击。\n * @param event 按钮点击事件。\n */\nfunction handleMarkdownMermaidViewerZoomInClick(event: MouseEvent): void {\n const viewerState = getMarkdownMermaidViewerStateFromEvent(event);\n if (!viewerState) {\n return;\n }\n updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue + IMAGE_VIEWER_ZOOM_STEP);\n}\n\n/**\n * 处理重置按钮点击。\n * @param event 按钮点击事件。\n */\nfunction handleMarkdownMermaidViewerResetClick(event: MouseEvent): void {\n const viewerState = getMarkdownMermaidViewerStateFromEvent(event);\n if (!viewerState) {\n return;\n }\n updateMarkdownMermaidViewerZoom(viewerState, IMAGE_VIEWER_DEFAULT_ZOOM);\n}\n\n/**\n * 处理关闭按钮点击。\n * @param event 按钮点击事件。\n */\nfunction handleMarkdownMermaidViewerCloseClick(event: MouseEvent): void {\n const viewerState = getMarkdownMermaidViewerStateFromEvent(event);\n if (!viewerState) {\n return;\n }\n closeMarkdownMermaidViewer(viewerState);\n}\n\n/**\n * 从事件回溯查看器状态。\n * @param event DOM 事件。\n * @returns 当前事件所属的查看器状态。\n */\nfunction getMarkdownMermaidViewerStateFromEvent(\n event: Event\n): MarkdownMermaidViewerState | undefined {\n // 优先沿祖先链找到所属 dialog。\n const targetElement = event.target as HTMLElement | null;\n const dialogElement =\n targetElement?.closest<HTMLDialogElement>(`.${MERMAID_VIEWER_DIALOG_CLASS_NAME}`) ?? null;\n\n if (!dialogElement) {\n return undefined;\n }\n return mermaidViewerStateByDialogElement.get(dialogElement);\n}\n\n/**\n * 关闭 mermaid 全屏查看器。\n * @param viewerState 查看器状态。\n */\nfunction closeMarkdownMermaidViewer(viewerState: MarkdownMermaidViewerState): void {\n if (typeof viewerState.dialogElement.close === \"function\" && viewerState.dialogElement.open) {\n viewerState.dialogElement.close();\n return;\n }\n viewerState.dialogElement.removeAttribute(\"open\");\n resetMarkdownMermaidViewerAfterClose(viewerState);\n}\n\n/**\n * 当前查看器是否处于打开态。\n * @param viewerState 查看器状态。\n * @returns dialog 是否打开。\n */\nfunction isMarkdownMermaidViewerOpen(viewerState: MarkdownMermaidViewerState): boolean {\n return viewerState.dialogElement.open === true;\n}\n\n/**\n * 更新查看器缩放比例与画布尺寸。\n * @param viewerState 查看器状态。\n * @param nextZoom 目标缩放倍数。\n * @param zoomAnchor 可选的缩放焦点(客户端坐标)。\n */\nfunction updateMarkdownMermaidViewerZoom(\n viewerState: MarkdownMermaidViewerState,\n nextZoom: number,\n zoomAnchor?: MarkdownImageViewerZoomAnchor\n): void {\n /** 归一化的缩放倍数。 */\n const normalizedZoom = clampMarkdownImageViewerZoom(nextZoom);\n /** 缩放前的焦点。 */\n const focalPoint = isMarkdownMermaidViewerOpen(viewerState)\n ? captureMarkdownMermaidViewerFocalPoint(viewerState, zoomAnchor)\n : undefined;\n\n viewerState.zoomValue = normalizedZoom;\n viewerState.dialogElement.classList.toggle(\n MERMAID_VIEWER_ZOOMED_CLASS_NAME,\n normalizedZoom > IMAGE_VIEWER_DEFAULT_ZOOM\n );\n viewerState.zoomValueElement.textContent = `${Math.round(normalizedZoom * 100)}%`;\n viewerState.zoomOutButtonElement.disabled = normalizedZoom <= IMAGE_VIEWER_MIN_ZOOM;\n viewerState.zoomInButtonElement.disabled = normalizedZoom >= IMAGE_VIEWER_MAX_ZOOM;\n viewerState.resetButtonElement.disabled = normalizedZoom === IMAGE_VIEWER_DEFAULT_ZOOM;\n updateMarkdownMermaidViewerCanvasSize(viewerState);\n\n if (focalPoint) {\n applyMarkdownMermaidViewerFocalPoint(viewerState, focalPoint);\n }\n}\n\n/**\n * 记录缩放前的焦点:把客户端坐标转成画布内的归一化位置。\n * @param viewerState 查看器状态。\n * @param zoomAnchor 客户端坐标锚点。\n * @returns 焦点信息,画布未布局时返回 undefined。\n */\nfunction captureMarkdownMermaidViewerFocalPoint(\n viewerState: MarkdownMermaidViewerState,\n zoomAnchor?: MarkdownImageViewerZoomAnchor\n): MarkdownImageViewerFocalPoint | undefined {\n /** 缩放前画布矩形。 */\n const preCanvasRect = viewerState.canvasElement.getBoundingClientRect();\n if (preCanvasRect.width <= 0 || preCanvasRect.height <= 0) {\n return undefined;\n }\n const viewportRect = viewerState.viewportElement.getBoundingClientRect();\n const anchorClientX = zoomAnchor?.x ?? viewportRect.left + viewportRect.width / 2;\n const anchorClientY = zoomAnchor?.y ?? viewportRect.top + viewportRect.height / 2;\n const normalizedX = (anchorClientX - preCanvasRect.left) / preCanvasRect.width;\n const normalizedY = (anchorClientY - preCanvasRect.top) / preCanvasRect.height;\n return { anchorClientX, anchorClientY, normalizedX, normalizedY };\n}\n\n/**\n * 缩放后调整滚动量,使焦点保持在原客户端位置。\n * @param viewerState 查看器状态。\n * @param focalPoint 缩放前记录的焦点。\n */\nfunction applyMarkdownMermaidViewerFocalPoint(\n viewerState: MarkdownMermaidViewerState,\n focalPoint: MarkdownImageViewerFocalPoint\n): void {\n viewerState.viewportElement.scrollLeft = 0;\n viewerState.viewportElement.scrollTop = 0;\n const postCanvasRect = viewerState.canvasElement.getBoundingClientRect();\n if (postCanvasRect.width <= 0 || postCanvasRect.height <= 0) {\n return;\n }\n const targetCanvasClientLeft =\n focalPoint.anchorClientX - focalPoint.normalizedX * postCanvasRect.width;\n const targetCanvasClientTop =\n focalPoint.anchorClientY - focalPoint.normalizedY * postCanvasRect.height;\n viewerState.viewportElement.scrollLeft = postCanvasRect.left - targetCanvasClientLeft;\n viewerState.viewportElement.scrollTop = postCanvasRect.top - targetCanvasClientTop;\n}\n\n/**\n * 根据视口与缩放比例更新画布尺寸。\n * @param viewerState 查看器状态。\n */\nfunction updateMarkdownMermaidViewerCanvasSize(viewerState: MarkdownMermaidViewerState): void {\n const viewportWidth = Math.max(\n viewerState.viewportElement.clientWidth * IMAGE_VIEWER_FIT_RATIO,\n 1\n );\n const viewportHeight = Math.max(\n viewerState.viewportElement.clientHeight * IMAGE_VIEWER_FIT_RATIO,\n 1\n );\n const naturalWidth = Math.max(viewerState.naturalWidth, 1);\n const naturalHeight = Math.max(viewerState.naturalHeight, 1);\n // fit 缩放:在不放大原图的前提下让整张图适配视口。\n const fitScale = Math.min(1, viewportWidth / naturalWidth, viewportHeight / naturalHeight);\n const displayWidth = Math.max(1, Math.round(naturalWidth * fitScale * viewerState.zoomValue));\n const displayHeight = Math.max(1, Math.round(naturalHeight * fitScale * viewerState.zoomValue));\n\n viewerState.canvasElement.style.width = `${displayWidth}px`;\n viewerState.canvasElement.style.height = `${displayHeight}px`;\n\n // 关键步骤:让内部 SVG 撑满 canvas,保持等比缩放。\n const innerSvgElement = viewerState.canvasElement.querySelector<SVGSVGElement>(\"svg\");\n if (innerSvgElement) {\n innerSvgElement.removeAttribute(\"width\");\n innerSvgElement.removeAttribute(\"height\");\n innerSvgElement.style.width = \"100%\";\n innerSvgElement.style.height = \"100%\";\n innerSvgElement.style.maxWidth = \"none\";\n innerSvgElement.style.display = \"block\";\n }\n}\n\n/**\n * Markdown 交互 hydration 的可注入选项,用于让各宿主落地平台差异,\n * 渲染器自身保持跨端一致(仅调用注入的实现)。\n */\nexport interface MarkdownInteractionOptions {\n /**\n * 目录条目跳转到目标标题的滚动实现,由宿主注入以适配各端平滑滚动差异。\n * 缺省使用原生 `scrollIntoView({ behavior: \"smooth\" })`(标准浏览器即丝滑);\n * 若宿主原生平滑被降级(如 VS Code webview 对真实锚点点击会瞬时),\n * 可注入自定义实现(如手动 requestAnimationFrame 动画)。\n * @param targetElement 目标标题元素。\n */\n scrollToHeading?: (targetElement: HTMLElement) => void;\n}\n\n/**\n * 默认目录跳转滚动实现:原生平滑 scrollIntoView。\n * @param targetElement 目标标题元素。\n */\nfunction defaultScrollToHeading(targetElement: HTMLElement): void {\n targetElement.scrollIntoView({ behavior: \"smooth\", block: \"start\" });\n}\n\n/**\n * 对已渲染到 DOM 的 Markdown 内容执行交互 hydration。\n * 处理图片放大查看、视频占位、Mermaid、代码块复制等运行时行为。\n * 不挂载浮动工具栏;如需工具栏请额外调用 {@link mountMarkdownToolbar}。\n * @param rootElement 包含 Markdown 渲染结果的根节点。\n * @param options 可注入交互选项(如目录跳转滚动实现)。\n */\nexport function hydrateMarkdown(\n rootElement: ParentNode,\n options: MarkdownInteractionOptions = {}\n): void {\n hydrateMarkdownImages(rootElement);\n hydrateMarkdownVideos(rootElement);\n // 关键步骤:mermaid 必须先于代码块 hydrate,避免被通用 code chrome 包装。\n hydrateMermaidBlocks(rootElement);\n hydrateCodeBlocks(rootElement);\n // 关键步骤:为行内 [TOC] 绑定折叠与标题跳转;滚动实现由宿主注入,缺省原生平滑。\n hydrateToc(rootElement, options.scrollToHeading ?? defaultScrollToHeading);\n}\n\n/**\n * 在指定 DOM 容器上挂载 Scribdown 浮动工具栏(回到顶部 / 目录 / 页面宽度切换)。\n * 工具栏 DOM 会直接 append 到 `container` 内,便于宿主控制生命周期:\n * 移除 / 替换 container 即可一并卸载工具栏,不会污染 `<body>`。\n * 视觉上工具栏使用 `position: fixed`,位置始终相对视口,与挂载点的 CSS 上下文无关。\n *\n * 仅在浏览器环境生效;非浏览器环境(如 Node.js 单测)或 SSR 阶段直接跳过。\n * 重复调用会先清理 container 内的旧实例,可在每次 {@link hydrateMarkdown} 后安全重新挂载。\n * @param container 目标挂载容器;同时作为目录采集与点击外部关闭的作用域。\n * @param options 可注入交互选项(如目录跳转滚动实现)。\n */\nexport function mountMarkdownToolbar(\n container: Element,\n options: MarkdownInteractionOptions = {}\n): void {\n /** 容器所属的 document。 */\n const ownerDocument = container.ownerDocument;\n if (!ownerDocument) {\n return;\n }\n if (typeof ownerDocument.defaultView?.scrollTo !== \"function\") {\n return;\n }\n // 关键步骤:恢复上次保存的内容宽度,再挂载工具栏。\n applyContentWidth(ownerDocument, loadContentWidth());\n mountPageToolbar(ownerDocument, container, options.scrollToHeading ?? defaultScrollToHeading);\n}\n\n/**\n * 给单个 pre 元素加上 chrome 结构与复制行为。\n * @param preElement 渲染产出的 pre 元素。\n */\nfunction decorateCodeBlock(preElement: HTMLPreElement): void {\n // 已经包裹过 figure 时直接跳过,避免重复 hydrate。\n if (preElement.parentElement?.classList.contains(CODE_BLOCK_CLASS_NAME)) {\n return;\n }\n\n if (preElement.dataset[CODE_BLOCK_HYDRATED_DATA_KEY] === \"true\") {\n return;\n }\n\n // pre 内部的 code 元素,无 code 子节点时认定为非代码块。\n const codeElement = preElement.querySelector<HTMLElement>(\":scope > code\");\n\n if (!codeElement) {\n return;\n }\n\n preElement.dataset[CODE_BLOCK_HYDRATED_DATA_KEY] = \"true\";\n\n // 当前 pre 所属的 document,用于创建子节点。\n const ownerDocument = preElement.ownerDocument;\n // 代码块语言标识。\n const languageId = resolveCodeBlockLanguageId(codeElement);\n // 语言展示标签。\n const languageLabel = resolveCodeBlockLanguageLabel(languageId);\n\n // 关键步骤:在替换 DOM 前抓取原始代码文本,便于复制按钮使用。\n const originalCodeText = codeElement.textContent ?? \"\";\n\n // 把代码内容按行拆分为带类名的 span,并得到总行数用于固定行号列。\n const codeLineCount = rewriteCodeBlockLines(codeElement, ownerDocument);\n\n // 顶部 chrome 区域,承载语言标签与复制按钮。\n const chromeElement = ownerDocument.createElement(\"div\");\n chromeElement.className = CODE_BLOCK_CHROME_CLASS_NAME;\n\n // 语言标签元素。\n const langElement = ownerDocument.createElement(\"span\");\n langElement.className = CODE_BLOCK_LANG_CLASS_NAME;\n langElement.textContent = languageLabel;\n chromeElement.append(langElement);\n\n // 复制按钮元素。\n const copyButtonElement = createCodeBlockCopyButton(ownerDocument);\n chromeElement.append(copyButtonElement);\n\n // 外层 figure 容器,让 chrome 与 pre 在同一手绘边框内。\n const figureElement = ownerDocument.createElement(\"figure\");\n figureElement.className = CODE_BLOCK_CLASS_NAME;\n figureElement.dataset.scribdownCodeLang = languageId;\n\n // 正文容器,用于独立绘制代码区分隔线和纸面。\n const bodyElement = ownerDocument.createElement(\"div\");\n bodyElement.className = CODE_BLOCK_BODY_CLASS_NAME;\n\n // 固定宽度行号列,作为 pre 的兄弟节点独立布局,不跟随代码横向滚动。\n const gutterElement = createCodeBlockLineNumberGutter(ownerDocument, codeLineCount);\n\n // 把 pre 替换为 figure,再把 chrome + body + 原 pre 放进 figure。\n preElement.replaceWith(figureElement);\n bodyElement.append(gutterElement, preElement);\n figureElement.append(chromeElement, bodyElement);\n\n // 关键步骤:把源码行锚点从内层 code 迁移到最外层 figure,\n // 使编辑器光标定位的高亮能覆盖整个代码块(含顶部 chrome)。\n // remark-rehype 把 code 节点的 data-source-line 落在 code 元素上,而非 pre。\n const codeBlockSourceLine = codeElement.getAttribute(SOURCE_LINE_DATA_ATTRIBUTE);\n\n if (codeBlockSourceLine !== null) {\n figureElement.setAttribute(SOURCE_LINE_DATA_ATTRIBUTE, codeBlockSourceLine);\n codeElement.removeAttribute(SOURCE_LINE_DATA_ATTRIBUTE);\n }\n\n // 在按钮上记录原始代码文本,复制时直接读取,无需再走 DOM。\n copyButtonElement.dataset.scribdownCodeSource = originalCodeText;\n copyButtonElement.addEventListener(\"click\", handleCodeBlockCopyClick);\n}\n\n/**\n * 解析 pre > code 节点的语言标识。\n * @param codeElement pre 内部的 code 元素。\n * @returns 语言标识,未识别时返回空字符串。\n */\nfunction resolveCodeBlockLanguageId(codeElement: HTMLElement): string {\n // class 列表中以 language- 开头的类名记录了语言标识。\n const languageClassName = Array.from(codeElement.classList).find((className) =>\n className.startsWith(\"language-\")\n );\n\n if (!languageClassName) {\n return \"\";\n }\n\n return languageClassName.slice(\"language-\".length).toLowerCase();\n}\n\n/**\n * 把语言标识转换为可读展示标签。\n * @param languageId 语言标识。\n * @returns 用于 chrome 的语言展示文本。\n */\nfunction resolveCodeBlockLanguageLabel(languageId: string): string {\n if (!languageId) {\n return CODE_LANGUAGE_DEFAULT_LABEL;\n }\n\n // 优先使用预定义映射,未命中时退化为首字母大写。\n const mappedLabel = CODE_LANGUAGE_DISPLAY_NAMES[languageId];\n\n if (mappedLabel) {\n return mappedLabel;\n }\n\n return languageId.charAt(0).toUpperCase() + languageId.slice(1);\n}\n\n/**\n * 把代码内容按行拆分为带类名的 span。\n * Shiki 已经输出 `<span class=\"line\">` 时直接复用并补充类名,否则做纯文本拆分。\n * @param codeElement pre 内部的 code 元素。\n * @param ownerDocument 当前 document。\n * @returns 当前代码块拆分后的总行数。\n */\nfunction rewriteCodeBlockLines(codeElement: HTMLElement, ownerDocument: Document): number {\n // Shiki 高亮输出的行 span 列表。\n const shikiLineElements = codeElement.querySelectorAll<HTMLElement>(\":scope > span.line\");\n\n if (shikiLineElements.length > 0) {\n // Shiki 行节点数组,用于重排 code 子节点并移除行间格式化换行。\n const shikiLineNodes = Array.from(shikiLineElements);\n\n shikiLineElements.forEach((lineElement) => {\n lineElement.classList.add(CODE_BLOCK_LINE_CLASS_NAME);\n // 空行使用零宽占位,行高统一继承 pre 的设置。\n if (lineElement.textContent === \"\") {\n lineElement.textContent = CODE_BLOCK_EMPTY_LINE_PLACEHOLDER;\n }\n });\n\n // 关键步骤:删除 Shiki 在行 span 之间输出的换行文本节点,避免 pre 把它们渲染成额外行距。\n codeElement.replaceChildren(...shikiLineNodes);\n return shikiLineNodes.length;\n }\n\n // 代码块的全部文本内容。\n const codeText = codeElement.textContent ?? \"\";\n // 去掉结尾多余换行,避免最后多出一个空行号。\n const trimmedCodeText = codeText.replace(/\\n+$/u, \"\");\n // 按换行符切分得到每一行原文。\n const codeLines = trimmedCodeText.split(\"\\n\");\n\n // 清空 code 现有子节点,重新生成行容器。\n while (codeElement.firstChild) {\n codeElement.removeChild(codeElement.firstChild);\n }\n\n codeLines.forEach((lineText) => {\n // 当前行的 span 容器,依赖 CSS `display: block` 提供视觉换行。\n const lineElement = ownerDocument.createElement(\"span\");\n lineElement.className = CODE_BLOCK_LINE_CLASS_NAME;\n // 空行使用零宽占位,行高统一继承 pre 的设置。\n if (lineText.length === 0) {\n lineElement.textContent = CODE_BLOCK_EMPTY_LINE_PLACEHOLDER;\n } else {\n lineElement.textContent = lineText;\n }\n codeElement.append(lineElement);\n });\n\n return codeLines.length;\n}\n\n/**\n * 创建代码块左侧固定行号列,保证行号区不参与横向滚动。\n * @param ownerDocument 当前 document。\n * @param lineCount 代码总行数。\n * @returns 已填充行号文本的固定列容器。\n */\nfunction createCodeBlockLineNumberGutter(\n ownerDocument: Document,\n lineCount: number\n): HTMLDivElement {\n // 行号固定列容器。\n const gutterElement = ownerDocument.createElement(\"div\");\n gutterElement.className = CODE_BLOCK_GUTTER_CLASS_NAME;\n\n // 当前循环行索引(0-based),用于生成 1-based 行号文本。\n for (let lineIndex = 0; lineIndex < lineCount; lineIndex += 1) {\n // 当前行号文本(1-based)。\n const lineNumberText = String(lineIndex + 1);\n // 单行行号节点。\n const lineNumberElement = ownerDocument.createElement(\"span\");\n lineNumberElement.className = CODE_BLOCK_GUTTER_LINE_CLASS_NAME;\n lineNumberElement.textContent = lineNumberText;\n gutterElement.append(lineNumberElement);\n }\n\n return gutterElement;\n}\n\n/**\n * \"复制\"态图标的 SVG 内部路径(双层叠放的矩形 + 复笔),\n * 直接内嵌以便用 currentColor 跟随按钮 hover / focus 颜色。\n */\nconst CODE_BLOCK_COPY_ICON_COPY_PATHS =\n '<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\"/>' +\n '<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\"/>' +\n '<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\"/>' +\n '<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\"/>';\n\n/**\n * \"已复制\"态图标的 SVG 内部路径(对勾 + 复笔),currentColor 由按钮状态控制。\n */\nconst CODE_BLOCK_COPY_ICON_CHECK_PATHS =\n '<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\"/>' +\n '<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\"/>';\n\n/**\n * 创建代码块复制按钮节点,仅包含图标,无文字标签。\n * @param ownerDocument 当前 document。\n * @returns 已配置类名与 aria 信息的按钮元素。\n */\nfunction createCodeBlockCopyButton(ownerDocument: Document): HTMLButtonElement {\n // 复制按钮元素。\n const copyButtonElement = ownerDocument.createElement(\"button\");\n copyButtonElement.type = \"button\";\n copyButtonElement.className = CODE_BLOCK_COPY_CLASS_NAME;\n copyButtonElement.setAttribute(\"aria-label\", CODE_BLOCK_COPY_ARIA_LABEL);\n\n // 默认 \"copy\" 状态图标。\n const copyIconElement = ownerDocument.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\n copyIconElement.setAttribute(\n \"class\",\n `${CODE_BLOCK_COPY_ICON_CLASS_NAME} ${CODE_BLOCK_COPY_ICON_COPY_CLASS_NAME}`\n );\n copyIconElement.setAttribute(\"viewBox\", \"0 0 20 20\");\n copyIconElement.setAttribute(\"aria-hidden\", \"true\");\n copyIconElement.setAttribute(\"focusable\", \"false\");\n copyIconElement.innerHTML = CODE_BLOCK_COPY_ICON_COPY_PATHS;\n copyButtonElement.append(copyIconElement);\n\n // \"已复制\" 状态图标,初始隐藏,由 CSS 根据 data 属性切换显示。\n const checkIconElement = ownerDocument.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\n checkIconElement.setAttribute(\n \"class\",\n `${CODE_BLOCK_COPY_ICON_CLASS_NAME} ${CODE_BLOCK_COPY_ICON_CHECK_CLASS_NAME}`\n );\n checkIconElement.setAttribute(\"viewBox\", \"0 0 20 20\");\n checkIconElement.setAttribute(\"aria-hidden\", \"true\");\n checkIconElement.setAttribute(\"focusable\", \"false\");\n checkIconElement.innerHTML = CODE_BLOCK_COPY_ICON_CHECK_PATHS;\n copyButtonElement.append(checkIconElement);\n\n return copyButtonElement;\n}\n\n/**\n * 处理代码块复制按钮点击事件。\n * @param event 复制按钮点击事件。\n */\nfunction handleCodeBlockCopyClick(event: MouseEvent): void {\n // 被点击的复制按钮元素。\n const copyButtonElement = event.currentTarget as HTMLButtonElement;\n // 待复制的代码文本。\n const codeText = copyButtonElement.dataset.scribdownCodeSource ?? \"\";\n\n void writeCodeBlockTextToClipboard(codeText).then((isSucceeded) => {\n if (isSucceeded) {\n flashCodeBlockCopyButton(copyButtonElement);\n }\n });\n}\n\n/**\n * 把代码文本写入剪贴板,兼容不支持 navigator.clipboard 的环境。\n * @param codeText 待复制的代码文本。\n * @returns 是否复制成功。\n */\nasync function writeCodeBlockTextToClipboard(codeText: string): Promise<boolean> {\n // 现代浏览器优先使用 navigator.clipboard。\n if (typeof navigator !== \"undefined\" && navigator.clipboard?.writeText) {\n try {\n await navigator.clipboard.writeText(codeText);\n return true;\n } catch {\n // 安全上下文外可能会抛出,回落到 execCommand 兜底。\n }\n }\n\n if (typeof document === \"undefined\") {\n return false;\n }\n\n // 兜底:用临时 textarea + execCommand 完成复制。\n const fallbackTextarea = document.createElement(\"textarea\");\n fallbackTextarea.value = codeText;\n fallbackTextarea.setAttribute(\"readonly\", \"\");\n fallbackTextarea.style.position = \"fixed\";\n fallbackTextarea.style.opacity = \"0\";\n fallbackTextarea.style.pointerEvents = \"none\";\n document.body.append(fallbackTextarea);\n fallbackTextarea.select();\n\n // 兼容旧浏览器的 execCommand 返回值。\n const isSucceeded = document.execCommand(\"copy\");\n\n fallbackTextarea.remove();\n return isSucceeded;\n}\n\n/**\n * 让复制按钮短暂显示已复制状态。\n * @param copyButtonElement 被点击的复制按钮。\n */\nfunction flashCodeBlockCopyButton(copyButtonElement: HTMLButtonElement): void {\n // 切换 data 属性 + aria-label,CSS 会根据 data 属性切换两张 SVG 图标。\n copyButtonElement.dataset.scribdownCodeCopied = \"true\";\n copyButtonElement.setAttribute(\"aria-label\", CODE_BLOCK_COPY_ARIA_LABEL_COPIED);\n\n // 延迟恢复初始状态,给用户一个肉眼可见的反馈窗口。\n window.setTimeout(() => {\n delete copyButtonElement.dataset.scribdownCodeCopied;\n copyButtonElement.setAttribute(\"aria-label\", CODE_BLOCK_COPY_ARIA_LABEL);\n }, CODE_BLOCK_COPY_RESTORE_DELAY_MS);\n}\n\n/**\n * 给单个图片元素绑定加载、失败状态与全图查看行为。\n * @param imageElement 待绑定状态的图片元素。\n */\nfunction bindMarkdownImageState(imageElement: HTMLImageElement): void {\n updateMarkdownImageState(imageElement);\n\n if (imageElement.dataset[IMAGE_HYDRATED_DATA_KEY] === \"true\") {\n return;\n }\n\n imageElement.dataset[IMAGE_HYDRATED_DATA_KEY] = \"true\";\n imageElement.addEventListener(\"load\", handleMarkdownImageLoad);\n imageElement.addEventListener(\"error\", handleMarkdownImageError);\n bindMarkdownImageViewer(imageElement);\n}\n\n/**\n * 处理图片加载成功事件。\n * @param event 图片加载事件。\n */\nfunction handleMarkdownImageLoad(event: Event): void {\n // 触发加载事件的图片元素。\n const imageElement = event.currentTarget as HTMLImageElement;\n\n updateMarkdownImageState(imageElement);\n}\n\n/**\n * 处理图片加载失败事件。\n * @param event 图片加载失败事件。\n */\nfunction handleMarkdownImageError(event: Event): void {\n // 触发失败事件的图片元素。\n const imageElement = event.currentTarget as HTMLImageElement;\n\n updateMarkdownImageState(imageElement);\n}\n\n/**\n * 根据图片当前加载结果更新 frame 状态类。\n * @param imageElement 待更新状态的图片元素。\n */\nfunction updateMarkdownImageState(imageElement: HTMLImageElement): void {\n // 图片外层 frame 元素。\n const frameElement = imageElement.closest<HTMLElement>(`.${IMAGE_FRAME_CLASS_NAME}`);\n\n if (!frameElement) {\n return;\n }\n\n // 当前图片是否已确认加载失败。\n const isFailed = imageElement.complete && imageElement.naturalWidth === 0;\n // 当前图片是否已确认加载完成。\n const isLoaded = imageElement.complete && imageElement.naturalWidth > 0;\n\n frameElement.classList.toggle(IMAGE_FRAME_FAILED_CLASS_NAME, isFailed);\n frameElement.classList.toggle(IMAGE_FRAME_LOADED_CLASS_NAME, isLoaded);\n}\n\n/**\n * 给单个视频元素绑定加载完成与失败回退状态机。\n * @param videoElement 待绑定状态的视频元素。\n */\nfunction bindMarkdownVideoState(videoElement: HTMLVideoElement): void {\n updateMarkdownVideoState(videoElement);\n\n if (videoElement.dataset[VIDEO_HYDRATED_DATA_KEY] === \"true\") {\n return;\n }\n\n videoElement.dataset[VIDEO_HYDRATED_DATA_KEY] = \"true\";\n videoElement.addEventListener(\"loadeddata\", handleMarkdownVideoLoaded);\n videoElement.addEventListener(\"error\", handleMarkdownVideoError);\n\n // 关键步骤:同时监听内部 <source> 的 error,覆盖多源视频在最后一个源失败后才能确认失败的场景。\n videoElement\n .querySelectorAll<HTMLSourceElement>(\"source\")\n .forEach((sourceElement) => {\n sourceElement.addEventListener(\"error\", handleMarkdownVideoSourceError);\n });\n}\n\n/**\n * 处理视频可播放数据就绪事件,刷新 frame 状态类。\n * @param event 视频加载事件。\n */\nfunction handleMarkdownVideoLoaded(event: Event): void {\n // 触发就绪事件的视频元素。\n const videoElement = event.currentTarget as HTMLVideoElement;\n\n updateMarkdownVideoState(videoElement);\n}\n\n/**\n * 处理视频本体加载失败事件。\n * @param event 视频失败事件。\n */\nfunction handleMarkdownVideoError(event: Event): void {\n // 触发失败事件的视频元素。\n const videoElement = event.currentTarget as HTMLVideoElement;\n\n markMarkdownVideoFailed(videoElement);\n}\n\n/**\n * 处理视频内部 <source> 的失败事件。\n * 仅当 networkState 进入 NETWORK_NO_SOURCE,意味着所有候选源均不可用时,才标记失败。\n * @param event source 失败事件。\n */\nfunction handleMarkdownVideoSourceError(event: Event): void {\n // 触发失败事件的 source 元素。\n const sourceElement = event.currentTarget as HTMLElement;\n // 关联的视频宿主元素。\n const videoElement = sourceElement.closest<HTMLVideoElement>(\n `video.${VIDEO_ELEMENT_CLASS_NAME}`\n );\n\n if (!videoElement) {\n return;\n }\n if (videoElement.networkState !== videoElement.NETWORK_NO_SOURCE) {\n return;\n }\n\n markMarkdownVideoFailed(videoElement);\n}\n\n/**\n * 根据视频当前播放状态刷新 frame 状态类。\n * @param videoElement 待更新状态的视频元素。\n */\nfunction updateMarkdownVideoState(videoElement: HTMLVideoElement): void {\n // 视频外层 frame 元素。\n const frameElement = videoElement.closest<HTMLElement>(\n `.${VIDEO_FRAME_CLASS_NAME}`\n );\n\n if (!frameElement) {\n return;\n }\n\n // 当前视频是否已确认加载失败:本体 error 非空,或没有可用源。\n const isFailed =\n videoElement.error !== null ||\n videoElement.networkState === videoElement.NETWORK_NO_SOURCE;\n // 当前视频是否已确认拿到首帧。\n const isLoaded = videoElement.readyState >= videoElement.HAVE_CURRENT_DATA;\n\n frameElement.classList.toggle(VIDEO_FRAME_FAILED_CLASS_NAME, isFailed);\n frameElement.classList.toggle(\n VIDEO_FRAME_LOADED_CLASS_NAME,\n isLoaded && !isFailed\n );\n}\n\n/**\n * 把视频 frame 强制切到失败态,并清除可能已存在的 loaded 标记。\n * @param videoElement 触发失败的视频元素。\n */\nfunction markMarkdownVideoFailed(videoElement: HTMLVideoElement): void {\n // 视频外层 frame 元素。\n const frameElement = videoElement.closest<HTMLElement>(\n `.${VIDEO_FRAME_CLASS_NAME}`\n );\n\n if (!frameElement) {\n return;\n }\n\n frameElement.classList.add(VIDEO_FRAME_FAILED_CLASS_NAME);\n frameElement.classList.remove(VIDEO_FRAME_LOADED_CLASS_NAME);\n}\n\n/**\n * 给图片绑定全图查看入口。\n * @param imageElement 待绑定的图片元素。\n */\nfunction bindMarkdownImageViewer(imageElement: HTMLImageElement): void {\n // 图片外层链接元素,存在时保留原始链接行为。\n const linkElement = imageElement.closest(\"a[href]\");\n\n if (linkElement) {\n return;\n }\n\n imageElement.setAttribute(\"role\", \"button\");\n imageElement.setAttribute(\"tabindex\", \"0\");\n imageElement.setAttribute(\"aria-label\", createMarkdownImageViewerAriaLabel(imageElement));\n imageElement.addEventListener(\"click\", handleMarkdownImageViewerOpenClick);\n imageElement.addEventListener(\"keydown\", handleMarkdownImageViewerOpenKeyDown);\n}\n\n/**\n * 处理图片点击打开全图查看器。\n * @param event 图片点击事件。\n */\nfunction handleMarkdownImageViewerOpenClick(event: MouseEvent): void {\n // 被点击的图片元素。\n const imageElement = event.currentTarget as HTMLImageElement;\n\n if (!canOpenMarkdownImageViewer(imageElement)) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n openMarkdownImageViewer(imageElement);\n}\n\n/**\n * 处理图片键盘打开全图查看器。\n * @param event 图片键盘事件。\n */\nfunction handleMarkdownImageViewerOpenKeyDown(event: KeyboardEvent): void {\n // 当前按键是否是打开操作。\n const isOpenKey = event.key === \"Enter\" || event.key === \" \";\n // 当前聚焦的图片元素。\n const imageElement = event.currentTarget as HTMLImageElement;\n\n if (!isOpenKey || !canOpenMarkdownImageViewer(imageElement)) {\n return;\n }\n\n event.preventDefault();\n openMarkdownImageViewer(imageElement);\n}\n\n/**\n * 判断图片是否可以进入全图查看。\n * @param imageElement 待判断的图片元素。\n * @returns 图片是否已经加载成功且存在有效来源。\n */\nfunction canOpenMarkdownImageViewer(imageElement: HTMLImageElement): boolean {\n // 图片可展示来源。\n const imageSource = getMarkdownImageSource(imageElement);\n\n return (\n imageSource.length > 0 &&\n imageElement.complete &&\n imageElement.naturalWidth > 0 &&\n imageElement.naturalHeight > 0\n );\n}\n\n/**\n * 打开图片全图查看器。\n * @param sourceImageElement 触发查看的原始图片元素。\n */\nfunction openMarkdownImageViewer(sourceImageElement: HTMLImageElement): void {\n // 图片可展示来源。\n const imageSource = getMarkdownImageSource(sourceImageElement);\n\n if (imageSource.length === 0) {\n return;\n }\n\n // 原始图片所在 document。\n const ownerDocument = sourceImageElement.ownerDocument;\n // 当前 document 对应的查看器状态。\n const viewerState = getOrCreateMarkdownImageViewerState(ownerDocument);\n // 原始图片可访问名称。\n const imageLabel = createMarkdownImageViewerCaption(sourceImageElement, imageSource);\n\n viewerState.sourceImageElement = sourceImageElement;\n viewerState.naturalWidth = sourceImageElement.naturalWidth;\n viewerState.naturalHeight = sourceImageElement.naturalHeight;\n viewerState.imageElement.src = imageSource;\n viewerState.imageElement.alt = sourceImageElement.alt;\n viewerState.captionElement.textContent = imageLabel;\n viewerState.dialogElement.setAttribute(\n \"aria-label\",\n createMarkdownImageViewerAriaLabel(sourceImageElement)\n );\n viewerState.viewportElement.scrollLeft = 0;\n viewerState.viewportElement.scrollTop = 0;\n viewerState.zoomValue = IMAGE_VIEWER_DEFAULT_ZOOM;\n\n showMarkdownImageViewerDialog(viewerState);\n requestMarkdownImageViewerLayout(viewerState);\n}\n\n/**\n * 获取或创建当前 document 的图片查看器。\n * @param ownerDocument 图片所在 document。\n * @returns 当前 document 可复用的查看器状态。\n */\nfunction getOrCreateMarkdownImageViewerState(ownerDocument: Document): MarkdownImageViewerState {\n // 已创建的查看器状态。\n const existingViewerState = imageViewerStateByDocument.get(ownerDocument);\n\n if (existingViewerState) {\n return existingViewerState;\n }\n\n // 新创建的查看器状态。\n const createdViewerState = createMarkdownImageViewerState(ownerDocument);\n\n imageViewerStateByDocument.set(ownerDocument, createdViewerState);\n\n return createdViewerState;\n}\n\n/**\n * 创建图片查看器 DOM 与事件绑定。\n * @param ownerDocument 图片所在 document。\n * @returns 图片查看器状态。\n */\nfunction createMarkdownImageViewerState(ownerDocument: Document): MarkdownImageViewerState {\n // 图片查看器 dialog。\n const dialogElement = ownerDocument.createElement(\"dialog\") as HTMLDialogElement;\n // 图片查看器顶部区域。\n const chromeElement = ownerDocument.createElement(\"div\");\n // 图片查看器标题分组(caption + hint)。\n const captionGroupElement = ownerDocument.createElement(\"div\");\n // 图片查看器说明文字。\n const captionElement = ownerDocument.createElement(\"p\");\n // 图片查看器快捷键提示。\n const hintElement = ownerDocument.createElement(\"p\");\n // 图片查看器按钮区域。\n const controlsElement = ownerDocument.createElement(\"div\");\n // 缩小按钮。\n const zoomOutButtonElement = createMarkdownImageViewerButton(ownerDocument, {\n ariaLabel: \"缩小图片\",\n text: IMAGE_VIEWER_ZOOM_OUT_TEXT\n });\n // 当前缩放比例文本。\n const zoomValueElement = ownerDocument.createElement(\"span\");\n // 放大按钮。\n const zoomInButtonElement = createMarkdownImageViewerButton(ownerDocument, {\n ariaLabel: \"放大图片\",\n text: IMAGE_VIEWER_ZOOM_IN_TEXT\n });\n // 重置缩放按钮。\n const resetButtonElement = createMarkdownImageViewerButton(ownerDocument, {\n ariaLabel: \"重置缩放\",\n text: IMAGE_VIEWER_RESET_TEXT\n });\n // 关闭按钮。\n const closeButtonElement = createMarkdownImageViewerButton(ownerDocument, {\n ariaLabel: \"关闭全图查看\",\n className: IMAGE_VIEWER_CLOSE_BUTTON_CLASS_NAME,\n text: IMAGE_VIEWER_CLOSE_TEXT\n });\n // 图片滚动视口。\n const viewportElement = ownerDocument.createElement(\"div\");\n // 查看器内展示的图片。\n const imageElement = ownerDocument.createElement(\"img\");\n // 图片查看器运行时状态。\n const viewerState: MarkdownImageViewerState = {\n captionElement,\n closeButtonElement,\n dialogElement,\n dragStartClientX: 0,\n dragStartClientY: 0,\n dragStartScrollLeft: 0,\n dragStartScrollTop: 0,\n hintElement,\n imageElement,\n isDragging: false,\n naturalHeight: 1,\n naturalWidth: 1,\n resetButtonElement,\n viewportElement,\n zoomInButtonElement,\n zoomOutButtonElement,\n zoomValue: IMAGE_VIEWER_DEFAULT_ZOOM,\n zoomValueElement\n };\n // 当前 document 对应的 window。\n const ownerWindow = ownerDocument.defaultView;\n\n dialogElement.className = IMAGE_VIEWER_DIALOG_CLASS_NAME;\n dialogElement.setAttribute(\"aria-modal\", \"true\");\n dialogElement.setAttribute(\"tabindex\", \"-1\");\n chromeElement.className = IMAGE_VIEWER_CHROME_CLASS_NAME;\n captionGroupElement.className = IMAGE_VIEWER_CAPTION_GROUP_CLASS_NAME;\n captionElement.className = IMAGE_VIEWER_CAPTION_CLASS_NAME;\n hintElement.className = IMAGE_VIEWER_HINT_CLASS_NAME;\n hintElement.textContent = IMAGE_VIEWER_HINT_TEXT;\n controlsElement.className = IMAGE_VIEWER_CONTROLS_CLASS_NAME;\n zoomValueElement.className = IMAGE_VIEWER_ZOOM_VALUE_CLASS_NAME;\n viewportElement.className = IMAGE_VIEWER_VIEWPORT_CLASS_NAME;\n imageElement.className = IMAGE_VIEWER_IMAGE_CLASS_NAME;\n imageElement.decoding = \"async\";\n\n imageViewerStateByDialogElement.set(dialogElement, viewerState);\n controlsElement.append(\n zoomOutButtonElement,\n zoomValueElement,\n zoomInButtonElement,\n resetButtonElement,\n closeButtonElement\n );\n captionGroupElement.append(captionElement, hintElement);\n chromeElement.append(captionGroupElement, controlsElement);\n viewportElement.append(imageElement);\n dialogElement.append(chromeElement, viewportElement);\n ownerDocument.body.append(dialogElement);\n\n dialogElement.addEventListener(\"click\", handleMarkdownImageViewerBackdropClick);\n dialogElement.addEventListener(\"close\", handleMarkdownImageViewerClose);\n dialogElement.addEventListener(\"keydown\", handleMarkdownImageViewerKeyDown);\n viewportElement.addEventListener(\"wheel\", handleMarkdownImageViewerWheel, { passive: false });\n viewportElement.addEventListener(\"pointerdown\", handleMarkdownImageViewerPointerDown);\n viewportElement.addEventListener(\"pointermove\", handleMarkdownImageViewerPointerMove);\n viewportElement.addEventListener(\"pointerup\", handleMarkdownImageViewerPointerUp);\n viewportElement.addEventListener(\"pointercancel\", handleMarkdownImageViewerPointerUp);\n imageElement.addEventListener(\"load\", handleMarkdownImageViewerImageLoad);\n imageElement.addEventListener(\"dragstart\", handleMarkdownImageViewerImageDragStart);\n zoomOutButtonElement.addEventListener(\"click\", handleMarkdownImageViewerZoomOutClick);\n zoomInButtonElement.addEventListener(\"click\", handleMarkdownImageViewerZoomInClick);\n resetButtonElement.addEventListener(\"click\", handleMarkdownImageViewerResetClick);\n closeButtonElement.addEventListener(\"click\", handleMarkdownImageViewerCloseClick);\n\n if (ownerWindow) {\n ownerWindow.addEventListener(\"resize\", handleMarkdownImageViewerWindowResize);\n }\n\n updateMarkdownImageViewerZoom(viewerState, IMAGE_VIEWER_DEFAULT_ZOOM);\n\n return viewerState;\n}\n\n/**\n * 创建图片查看器按钮。\n * @param ownerDocument 按钮所在 document。\n * @param options 按钮文本、可访问名称和修饰类。\n * @returns 图片查看器按钮元素。\n */\nfunction createMarkdownImageViewerButton(\n ownerDocument: Document,\n options: MarkdownImageViewerButtonOptions\n): HTMLButtonElement {\n // 图片查看器按钮元素。\n const buttonElement = ownerDocument.createElement(\"button\");\n\n buttonElement.type = \"button\";\n buttonElement.className = options.className\n ? `${IMAGE_VIEWER_BUTTON_CLASS_NAME} ${options.className}`\n : IMAGE_VIEWER_BUTTON_CLASS_NAME;\n buttonElement.textContent = options.text;\n buttonElement.setAttribute(\"aria-label\", options.ariaLabel);\n\n return buttonElement;\n}\n\n/**\n * 显示图片查看器 dialog。\n * @param viewerState 图片查看器状态。\n */\nfunction showMarkdownImageViewerDialog(viewerState: MarkdownImageViewerState): void {\n if (typeof viewerState.dialogElement.showModal === \"function\") {\n if (!viewerState.dialogElement.open) {\n viewerState.dialogElement.showModal();\n }\n } else {\n viewerState.dialogElement.setAttribute(\"open\", \"\");\n }\n\n viewerState.dialogElement.focus();\n}\n\n/**\n * 延后一次布局计算,确保 dialog 已经进入可测量状态。\n * @param viewerState 图片查看器状态。\n */\nfunction requestMarkdownImageViewerLayout(viewerState: MarkdownImageViewerState): void {\n // 图片查看器所在 window。\n const ownerWindow = viewerState.dialogElement.ownerDocument.defaultView;\n\n if (!ownerWindow) {\n updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);\n return;\n }\n\n ownerWindow.setTimeout(handleMarkdownImageViewerDeferredLayout, 0, viewerState);\n}\n\n/**\n * 处理延后的图片查看器布局计算。\n * @param viewerState 图片查看器状态。\n */\nfunction handleMarkdownImageViewerDeferredLayout(viewerState: MarkdownImageViewerState): void {\n updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);\n}\n\n/**\n * 处理点击遮罩关闭图片查看器。\n * @param event 查看器点击事件。\n */\nfunction handleMarkdownImageViewerBackdropClick(event: MouseEvent): void {\n if (event.target !== event.currentTarget) {\n return;\n }\n\n // 被点击的 dialog。\n const dialogElement = event.currentTarget as HTMLDialogElement;\n // 图片查看器状态。\n const viewerState = imageViewerStateByDialogElement.get(dialogElement);\n\n if (!viewerState) {\n return;\n }\n\n closeMarkdownImageViewer(viewerState);\n}\n\n/**\n * 处理图片查看器关闭后的状态复位。\n * @param event 查看器关闭事件。\n */\nfunction handleMarkdownImageViewerClose(event: Event): void {\n // 被关闭的 dialog。\n const dialogElement = event.currentTarget as HTMLDialogElement;\n // 图片查看器状态。\n const viewerState = imageViewerStateByDialogElement.get(dialogElement);\n\n if (!viewerState) {\n return;\n }\n\n resetMarkdownImageViewerAfterClose(viewerState);\n}\n\n/**\n * 处理查看器内键盘缩放和关闭。\n * @param event 查看器键盘事件。\n */\nfunction handleMarkdownImageViewerKeyDown(event: KeyboardEvent): void {\n // 图片查看器状态。\n const viewerState = getMarkdownImageViewerStateFromEvent(event);\n\n if (!viewerState) {\n return;\n }\n\n if (event.key === \"+\" || event.key === \"=\") {\n event.preventDefault();\n updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + IMAGE_VIEWER_ZOOM_STEP);\n return;\n }\n\n if (event.key === \"-\") {\n event.preventDefault();\n updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue - IMAGE_VIEWER_ZOOM_STEP);\n return;\n }\n\n if (event.key === \"0\") {\n event.preventDefault();\n updateMarkdownImageViewerZoom(viewerState, IMAGE_VIEWER_DEFAULT_ZOOM);\n return;\n }\n\n if (event.key === \"Escape\") {\n event.preventDefault();\n closeMarkdownImageViewer(viewerState);\n }\n}\n\n/**\n * 处理 ctrl/cmd + 滚轮缩放图片。\n * @param event 图片查看器滚轮事件。\n */\nfunction handleMarkdownImageViewerWheel(event: WheelEvent): void {\n if (!event.ctrlKey && !event.metaKey) {\n return;\n }\n\n // 图片查看器状态。\n const viewerState = getMarkdownImageViewerStateFromEvent(event);\n\n if (!viewerState) {\n return;\n }\n\n // 与 deltaY 成比例并夹紧到上限,让触控板细滑动产生细粒度缩放、鼠标滚轮单格仍可见。\n const rawDelta = -event.deltaY * IMAGE_VIEWER_WHEEL_ZOOM_FACTOR;\n const zoomDelta = Math.max(\n -IMAGE_VIEWER_WHEEL_ZOOM_MAX_DELTA,\n Math.min(IMAGE_VIEWER_WHEEL_ZOOM_MAX_DELTA, rawDelta)\n );\n\n event.preventDefault();\n updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + zoomDelta, {\n x: event.clientX,\n y: event.clientY\n });\n}\n\n/**\n * 处理图片视口鼠标按下,开启拖拽平移。\n * @param event 视口指针事件。\n */\nfunction handleMarkdownImageViewerPointerDown(event: PointerEvent): void {\n // 仅响应鼠标主键 / 触摸 / 笔。\n if (event.button !== 0) {\n return;\n }\n\n // 当前事件所在视口。\n const viewportElement = event.currentTarget as HTMLElement;\n // 图片查看器状态。\n const viewerState = getMarkdownImageViewerStateFromEvent(event);\n\n if (!viewerState) {\n return;\n }\n\n // 视口是否存在可滚动空间。\n const canScrollHorizontally = viewportElement.scrollWidth > viewportElement.clientWidth;\n const canScrollVertically = viewportElement.scrollHeight > viewportElement.clientHeight;\n\n if (!canScrollHorizontally && !canScrollVertically) {\n return;\n }\n\n viewerState.isDragging = true;\n viewerState.dragStartClientX = event.clientX;\n viewerState.dragStartClientY = event.clientY;\n viewerState.dragStartScrollLeft = viewportElement.scrollLeft;\n viewerState.dragStartScrollTop = viewportElement.scrollTop;\n viewerState.dialogElement.classList.add(IMAGE_VIEWER_DRAGGING_CLASS_NAME);\n\n if (typeof viewportElement.setPointerCapture === \"function\") {\n viewportElement.setPointerCapture(event.pointerId);\n }\n\n event.preventDefault();\n}\n\n/**\n * 处理图片视口指针移动,跟随鼠标更新滚动位置。\n * @param event 视口指针事件。\n */\nfunction handleMarkdownImageViewerPointerMove(event: PointerEvent): void {\n // 当前事件所在视口。\n const viewportElement = event.currentTarget as HTMLElement;\n // 图片查看器状态。\n const viewerState = getMarkdownImageViewerStateFromEvent(event);\n\n if (!viewerState || !viewerState.isDragging) {\n return;\n }\n\n // 当前位移量:与起点的差值。\n const deltaX = event.clientX - viewerState.dragStartClientX;\n const deltaY = event.clientY - viewerState.dragStartClientY;\n\n viewportElement.scrollLeft = viewerState.dragStartScrollLeft - deltaX;\n viewportElement.scrollTop = viewerState.dragStartScrollTop - deltaY;\n}\n\n/**\n * 处理图片视口指针抬起/取消,结束拖拽。\n * @param event 视口指针事件。\n */\nfunction handleMarkdownImageViewerPointerUp(event: PointerEvent): void {\n // 当前事件所在视口。\n const viewportElement = event.currentTarget as HTMLElement;\n // 图片查看器状态。\n const viewerState = getMarkdownImageViewerStateFromEvent(event);\n\n if (!viewerState || !viewerState.isDragging) {\n return;\n }\n\n viewerState.isDragging = false;\n viewerState.dialogElement.classList.remove(IMAGE_VIEWER_DRAGGING_CLASS_NAME);\n\n if (\n typeof viewportElement.releasePointerCapture === \"function\" &&\n viewportElement.hasPointerCapture(event.pointerId)\n ) {\n viewportElement.releasePointerCapture(event.pointerId);\n }\n}\n\n/**\n * 阻止图片原生拖拽,避免与平移手势冲突。\n * @param event 图片拖拽事件。\n */\nfunction handleMarkdownImageViewerImageDragStart(event: DragEvent): void {\n event.preventDefault();\n}\n\n/**\n * 处理查看器图片加载后尺寸同步。\n * @param event 图片加载事件。\n */\nfunction handleMarkdownImageViewerImageLoad(event: Event): void {\n // 查看器图片元素。\n const imageElement = event.currentTarget as HTMLImageElement;\n // 图片所在 dialog。\n const dialogElement = imageElement.closest<HTMLDialogElement>(\n `.${IMAGE_VIEWER_DIALOG_CLASS_NAME}`\n );\n\n if (!dialogElement) {\n return;\n }\n\n // 图片查看器状态。\n const viewerState = imageViewerStateByDialogElement.get(dialogElement);\n\n if (!viewerState) {\n return;\n }\n\n viewerState.naturalWidth = imageElement.naturalWidth || viewerState.naturalWidth;\n viewerState.naturalHeight = imageElement.naturalHeight || viewerState.naturalHeight;\n updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);\n}\n\n/**\n * 处理窗口尺寸变化后的图片适配。\n * @param event window resize 事件。\n */\nfunction handleMarkdownImageViewerWindowResize(event: Event): void {\n // 触发 resize 的 window。\n const ownerWindow = event.currentTarget as Window;\n // 当前 window 对应的查看器状态。\n const viewerState = imageViewerStateByDocument.get(ownerWindow.document);\n\n if (!viewerState || !isMarkdownImageViewerOpen(viewerState)) {\n return;\n }\n\n updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);\n}\n\n/**\n * 处理缩小按钮点击。\n * @param event 缩小按钮点击事件。\n */\nfunction handleMarkdownImageViewerZoomOutClick(event: MouseEvent): void {\n // 图片查看器状态。\n const viewerState = getMarkdownImageViewerStateFromEvent(event);\n\n if (!viewerState) {\n return;\n }\n\n updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue - IMAGE_VIEWER_ZOOM_STEP);\n}\n\n/**\n * 处理放大按钮点击。\n * @param event 放大按钮点击事件。\n */\nfunction handleMarkdownImageViewerZoomInClick(event: MouseEvent): void {\n // 图片查看器状态。\n const viewerState = getMarkdownImageViewerStateFromEvent(event);\n\n if (!viewerState) {\n return;\n }\n\n updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + IMAGE_VIEWER_ZOOM_STEP);\n}\n\n/**\n * 处理重置缩放按钮点击。\n * @param event 重置按钮点击事件。\n */\nfunction handleMarkdownImageViewerResetClick(event: MouseEvent): void {\n // 图片查看器状态。\n const viewerState = getMarkdownImageViewerStateFromEvent(event);\n\n if (!viewerState) {\n return;\n }\n\n updateMarkdownImageViewerZoom(viewerState, IMAGE_VIEWER_DEFAULT_ZOOM);\n}\n\n/**\n * 处理关闭按钮点击。\n * @param event 关闭按钮点击事件。\n */\nfunction handleMarkdownImageViewerCloseClick(event: MouseEvent): void {\n // 图片查看器状态。\n const viewerState = getMarkdownImageViewerStateFromEvent(event);\n\n if (!viewerState) {\n return;\n }\n\n closeMarkdownImageViewer(viewerState);\n}\n\n/**\n * 根据事件目标找到图片查看器状态。\n * @param event 来自图片查看器内部的事件。\n * @returns 图片查看器状态,不存在时返回 undefined。\n */\nfunction getMarkdownImageViewerStateFromEvent(event: Event): MarkdownImageViewerState | undefined {\n // 当前事件绑定元素。\n const currentTargetElement = event.currentTarget as HTMLElement | null;\n\n if (!currentTargetElement) {\n return undefined;\n }\n\n // 当前元素所属 dialog。\n const dialogElement = currentTargetElement.closest<HTMLDialogElement>(\n `.${IMAGE_VIEWER_DIALOG_CLASS_NAME}`\n );\n\n if (!dialogElement) {\n return undefined;\n }\n\n return imageViewerStateByDialogElement.get(dialogElement);\n}\n\n/**\n * 更新图片查看器缩放比例和展示尺寸。\n * @param viewerState 图片查看器状态。\n * @param nextZoom 目标缩放倍数。\n * @param zoomAnchor 可选的缩放锚点(客户端坐标),不传则以视口中心为锚点。\n */\nfunction updateMarkdownImageViewerZoom(\n viewerState: MarkdownImageViewerState,\n nextZoom: number,\n zoomAnchor?: MarkdownImageViewerZoomAnchor\n): void {\n // 归一化后的缩放倍数。\n const normalizedZoom = clampMarkdownImageViewerZoom(nextZoom);\n // 缩放前的焦点信息,用于恢复光标/视口中心在新尺寸下的相对位置。\n const focalPoint = isMarkdownImageViewerOpen(viewerState)\n ? captureMarkdownImageViewerFocalPoint(viewerState, zoomAnchor)\n : undefined;\n\n viewerState.zoomValue = normalizedZoom;\n viewerState.dialogElement.classList.toggle(\n IMAGE_VIEWER_ZOOMED_CLASS_NAME,\n normalizedZoom > IMAGE_VIEWER_DEFAULT_ZOOM\n );\n viewerState.zoomValueElement.textContent = `${Math.round(normalizedZoom * 100)}%`;\n viewerState.zoomOutButtonElement.disabled = normalizedZoom <= IMAGE_VIEWER_MIN_ZOOM;\n viewerState.zoomInButtonElement.disabled = normalizedZoom >= IMAGE_VIEWER_MAX_ZOOM;\n viewerState.resetButtonElement.disabled = normalizedZoom === IMAGE_VIEWER_DEFAULT_ZOOM;\n updateMarkdownImageViewerImageSize(viewerState);\n\n if (focalPoint) {\n applyMarkdownImageViewerFocalPoint(viewerState, focalPoint);\n }\n}\n\n/**\n * 记录缩放前的焦点:把客户端坐标转成图片内的归一化位置。\n * @param viewerState 图片查看器状态。\n * @param zoomAnchor 客户端坐标锚点,不传则以视口中心。\n * @returns 焦点信息,图片尚未布局完成时返回 undefined。\n */\nfunction captureMarkdownImageViewerFocalPoint(\n viewerState: MarkdownImageViewerState,\n zoomAnchor?: MarkdownImageViewerZoomAnchor\n): MarkdownImageViewerFocalPoint | undefined {\n // 当前图片矩形。\n const preImageRect = viewerState.imageElement.getBoundingClientRect();\n\n if (preImageRect.width <= 0 || preImageRect.height <= 0) {\n return undefined;\n }\n\n // 当前视口矩形。\n const viewportRect = viewerState.viewportElement.getBoundingClientRect();\n // 锚点客户端坐标:未传时取视口中心。\n const anchorClientX = zoomAnchor?.x ?? viewportRect.left + viewportRect.width / 2;\n const anchorClientY = zoomAnchor?.y ?? viewportRect.top + viewportRect.height / 2;\n // 锚点在图片内部的归一化坐标。\n const normalizedX = (anchorClientX - preImageRect.left) / preImageRect.width;\n const normalizedY = (anchorClientY - preImageRect.top) / preImageRect.height;\n\n return {\n anchorClientX,\n anchorClientY,\n normalizedX,\n normalizedY\n };\n}\n\n/**\n * 缩放后调整滚动量,使焦点保持在原客户端位置。\n * @param viewerState 图片查看器状态。\n * @param focalPoint 缩放前记录的焦点信息。\n */\nfunction applyMarkdownImageViewerFocalPoint(\n viewerState: MarkdownImageViewerState,\n focalPoint: MarkdownImageViewerFocalPoint\n): void {\n // 先回到无滚动状态,便于读取布局的自然位置。\n viewerState.viewportElement.scrollLeft = 0;\n viewerState.viewportElement.scrollTop = 0;\n\n // 缩放后图片在客户端坐标系的位置和尺寸。\n const postImageRect = viewerState.imageElement.getBoundingClientRect();\n\n if (postImageRect.width <= 0 || postImageRect.height <= 0) {\n return;\n }\n\n // 期望的图片左上角客户端坐标:让归一化锚点落在原客户端位置。\n const targetImageClientLeft =\n focalPoint.anchorClientX - focalPoint.normalizedX * postImageRect.width;\n const targetImageClientTop =\n focalPoint.anchorClientY - focalPoint.normalizedY * postImageRect.height;\n\n // 利用滚动量补齐自然位置与期望位置的差值,浏览器会自动裁剪到合法范围。\n viewerState.viewportElement.scrollLeft = postImageRect.left - targetImageClientLeft;\n viewerState.viewportElement.scrollTop = postImageRect.top - targetImageClientTop;\n}\n\n/**\n * 根据视口和缩放比例更新查看器图片尺寸。\n * @param viewerState 图片查看器状态。\n */\nfunction updateMarkdownImageViewerImageSize(viewerState: MarkdownImageViewerState): void {\n // 视口可用宽度。\n const viewportWidth = Math.max(\n viewerState.viewportElement.clientWidth * IMAGE_VIEWER_FIT_RATIO,\n 1\n );\n // 视口可用高度。\n const viewportHeight = Math.max(\n viewerState.viewportElement.clientHeight * IMAGE_VIEWER_FIT_RATIO,\n 1\n );\n // 图片原始宽度。\n const naturalWidth = Math.max(viewerState.naturalWidth, 1);\n // 图片原始高度。\n const naturalHeight = Math.max(viewerState.naturalHeight, 1);\n // 默认全图适配比例。\n const fitScale = Math.min(1, viewportWidth / naturalWidth, viewportHeight / naturalHeight);\n // 缩放后的展示宽度。\n const displayWidth = Math.max(1, Math.round(naturalWidth * fitScale * viewerState.zoomValue));\n // 缩放后的展示高度。\n const displayHeight = Math.max(1, Math.round(naturalHeight * fitScale * viewerState.zoomValue));\n\n viewerState.imageElement.style.width = `${displayWidth}px`;\n viewerState.imageElement.style.height = `${displayHeight}px`;\n}\n\n/**\n * 限制图片查看器缩放倍数。\n * @param zoomValue 待限制的缩放倍数。\n * @returns 限制后的缩放倍数。\n */\nfunction clampMarkdownImageViewerZoom(zoomValue: number): number {\n return Math.min(IMAGE_VIEWER_MAX_ZOOM, Math.max(IMAGE_VIEWER_MIN_ZOOM, zoomValue));\n}\n\n/**\n * 关闭图片查看器。\n * @param viewerState 图片查看器状态。\n */\nfunction closeMarkdownImageViewer(viewerState: MarkdownImageViewerState): void {\n if (typeof viewerState.dialogElement.close === \"function\" && viewerState.dialogElement.open) {\n viewerState.dialogElement.close();\n return;\n }\n\n viewerState.dialogElement.removeAttribute(\"open\");\n resetMarkdownImageViewerAfterClose(viewerState);\n}\n\n/**\n * 复位图片查看器关闭后的临时状态。\n * @param viewerState 图片查看器状态。\n */\nfunction resetMarkdownImageViewerAfterClose(viewerState: MarkdownImageViewerState): void {\n viewerState.dialogElement.classList.remove(IMAGE_VIEWER_ZOOMED_CLASS_NAME);\n viewerState.dialogElement.classList.remove(IMAGE_VIEWER_DRAGGING_CLASS_NAME);\n viewerState.isDragging = false;\n viewerState.imageElement.removeAttribute(\"src\");\n viewerState.imageElement.removeAttribute(\"alt\");\n viewerState.imageElement.removeAttribute(\"style\");\n viewerState.captionElement.textContent = \"\";\n viewerState.viewportElement.scrollLeft = 0;\n viewerState.viewportElement.scrollTop = 0;\n viewerState.zoomValue = IMAGE_VIEWER_DEFAULT_ZOOM;\n\n if (viewerState.sourceImageElement?.isConnected) {\n viewerState.sourceImageElement.focus();\n }\n}\n\n/**\n * 判断图片查看器是否处于打开状态。\n * @param viewerState 图片查看器状态。\n * @returns 查看器是否打开。\n */\nfunction isMarkdownImageViewerOpen(viewerState: MarkdownImageViewerState): boolean {\n return viewerState.dialogElement.open || viewerState.dialogElement.hasAttribute(\"open\");\n}\n\n/**\n * 读取图片可展示来源。\n * @param imageElement 图片元素。\n * @returns 图片来源 URL。\n */\nfunction getMarkdownImageSource(imageElement: HTMLImageElement): string {\n return imageElement.currentSrc || imageElement.getAttribute(\"src\") || imageElement.src || \"\";\n}\n\n/**\n * 创建图片查看器可访问名称。\n * @param imageElement 图片元素。\n * @returns 图片查看器可访问名称。\n */\nfunction createMarkdownImageViewerAriaLabel(imageElement: HTMLImageElement): string {\n // 图片 alt 文本。\n const imageAltText = imageElement.alt.trim();\n\n if (imageAltText.length === 0) {\n return IMAGE_VIEWER_FALLBACK_LABEL;\n }\n\n return `${IMAGE_VIEWER_FALLBACK_LABEL}:${imageAltText}`;\n}\n\n/**\n * 创建图片查看器说明文本。\n * @param imageElement 图片元素。\n * @param imageSource 图片来源 URL。\n * @returns 图片查看器说明文本。\n */\nfunction createMarkdownImageViewerCaption(\n imageElement: HTMLImageElement,\n imageSource: string\n): string {\n // 图片 alt 文本。\n const imageAltText = imageElement.alt.trim();\n // 图片 title 文本。\n const imageTitleText = imageElement.title.trim();\n\n return imageAltText || imageTitleText || imageSource;\n}\n\n/**\n * 为 Scribdown 扩展默认 HTML 清洗规则。\n * @returns 支持目录节点 class / nav 的清洗规则。\n */\nfunction createScribdownSanitizeSchema(): typeof defaultSchema {\n // 默认标签白名单。\n const defaultTagNames = defaultSchema.tagNames ?? [];\n // 默认属性白名单。\n const defaultAttributes = defaultSchema.attributes ?? {};\n // details 元素的属性白名单(目录根 + 用户手写折叠块;分支已改用 button,不再用 details)。\n const detailsAttributes = [\n \"open\",\n [\"className\", DETAILS_CLASS_NAME, FRAME_CLASS_NAME, TOC_CLASS_NAME] as [\n string,\n string,\n string,\n string\n ]\n ];\n // summary 元素的属性白名单(仅目录根 summary)。\n const summaryAttributes = [\n ...(defaultAttributes.summary ?? []),\n [\"className\", TOC_SUMMARY_CLASS_NAME] as [string, string]\n ];\n // nav 元素的属性白名单。\n const navAttributes = [\"ariaLabel\", [\"className\", /^scribdown-toc-nav$/u] as [string, RegExp]];\n // a 元素的属性白名单。\n const linkAttributes = [\n \"ariaDescribedBy\",\n \"ariaLabel\",\n \"ariaLabelledBy\",\n \"dataFootnoteBackref\",\n \"dataFootnoteRef\",\n [\"className\", \"data-footnote-backref\", TOC_LINK_CLASS_NAME] as [string, string, string],\n \"href\"\n ];\n // button 元素的属性白名单:仅放行目录分支折叠按钮所需属性,禁止任何事件处理属性。\n const buttonAttributes = [\n \"type\",\n \"ariaExpanded\",\n \"ariaLabel\",\n [\"className\", TOC_TOGGLE_CLASS_NAME] as [string, string]\n ];\n // ol 元素的属性白名单。\n const orderedListAttributes = [\n \"ariaDescribedBy\",\n \"ariaLabel\",\n \"ariaLabelledBy\",\n [\"className\", \"contains-task-list\", /^scribdown-toc-list(?:--nested)?$/u] as [\n string,\n string,\n RegExp\n ]\n ];\n // span 元素的属性白名单:放行标题行内包裹层与图片 / 视频 frame / fallback 的 class。\n const spanAttributes = [\n ...(defaultAttributes.span ?? []),\n [\n \"className\",\n HEADING_MARK_CLASS_NAME,\n IMAGE_FRAME_CLASS_NAME,\n IMAGE_FALLBACK_CLASS_NAME,\n IMAGE_FALLBACK_ICON_CLASS_NAME,\n IMAGE_FALLBACK_TEXT_CLASS_NAME,\n IMAGE_FALLBACK_SOURCE_CLASS_NAME,\n VIDEO_FRAME_CLASS_NAME,\n VIDEO_FALLBACK_CLASS_NAME,\n VIDEO_FALLBACK_ICON_CLASS_NAME,\n VIDEO_FALLBACK_TEXT_CLASS_NAME,\n VIDEO_FALLBACK_SOURCE_CLASS_NAME\n ] as [string, ...string[]]\n ];\n // figure 元素的属性白名单:放行图片 figure 与视频 figure 的 class。\n const figureAttributes = [\n [\"className\", IMAGE_FIGURE_CLASS_NAME, VIDEO_FIGURE_CLASS_NAME] as [\n string,\n string,\n string\n ]\n ];\n // figcaption 元素的属性白名单。\n const figcaptionAttributes = [[\"className\", IMAGE_CAPTION_CLASS_NAME] as [string, string]];\n // img 元素的属性白名单。\n const imageAttributes = [\n ...(defaultAttributes.img ?? []),\n [\"className\", IMAGE_ELEMENT_CLASS_NAME] as [string, string]\n ];\n // video 元素的属性白名单:放行常用播放控制属性与统一类名。\n // 禁止任何事件处理属性(默认 schema 不在白名单的属性会被剥除)。\n const videoAttributes = [\n \"src\",\n \"controls\",\n \"controlsList\",\n \"width\",\n \"height\",\n \"poster\",\n \"preload\",\n \"playsInline\",\n \"muted\",\n \"loop\",\n \"autoPlay\",\n \"crossOrigin\",\n [\"className\", VIDEO_ELEMENT_CLASS_NAME] as [string, string]\n ];\n // source 元素的属性白名单(<video> 多源回退用)。\n const sourceAttributes = [\"src\", \"type\", \"media\"];\n // abbr 元素的属性白名单:保留 title 以展示完整释义。\n const abbrAttributes = [\"title\"];\n // li 元素的属性白名单。\n const listItemAttributes = [\n \"dataTocIndex\",\n [\"className\", \"task-list-item\", /^scribdown-toc-item(?:--(?:branch|depth-[1-6]))?$/u] as [\n string,\n string,\n RegExp\n ]\n ];\n // 通配元素属性白名单:额外放行滚动对齐用的 data-source-line。\n const wildcardAttributes = [...(defaultAttributes[\"*\"] ?? []), SOURCE_LINE_HAST_PROPERTY];\n\n return {\n ...defaultSchema,\n clobberPrefix: \"\",\n tagNames: Array.from(\n new Set([\n ...defaultTagNames,\n \"button\",\n \"details\",\n \"nav\",\n \"summary\",\n \"u\",\n \"mark\",\n \"sub\",\n \"sup\",\n \"kbd\",\n \"abbr\",\n \"small\",\n \"dl\",\n \"dt\",\n \"dd\",\n \"figure\",\n \"figcaption\",\n \"video\",\n \"source\"\n ])\n ),\n attributes: {\n ...defaultAttributes,\n \"*\": wildcardAttributes,\n button: buttonAttributes,\n details: detailsAttributes,\n summary: summaryAttributes,\n nav: navAttributes,\n a: linkAttributes,\n ol: orderedListAttributes,\n li: listItemAttributes,\n span: spanAttributes,\n abbr: abbrAttributes,\n figure: figureAttributes,\n figcaption: figcaptionAttributes,\n img: imageAttributes,\n video: videoAttributes,\n source: sourceAttributes\n }\n };\n}\n\n/**\n * remark 插件:把行内 text 节点中的 ==text== 转换为 <mark> 节点。\n * GFM 本身不支持该语法,因此独立实现以覆盖 fixture 中的高亮用法。\n * @returns Markdown AST 转换器。\n */\nfunction remarkHighlightMark(): (tree: MarkdownNode) => void {\n return (tree: MarkdownNode) => {\n transformHighlightMarks(tree);\n };\n}\n\n/**\n * 块级容器节点类型集合:这些节点的子节点同样是块级节点,需递归下钻标注源码行锚点,\n * 以覆盖列表中的代码块、嵌套引用等场景。\n */\nconst BLOCK_CONTAINER_NODE_TYPES = new Set([\"list\", \"listItem\", \"blockquote\"]);\n\n/**\n * remark 插件:为块级节点标注源码起始行号,递归覆盖嵌套块级结构。\n * 通过 hProperties 注入 data-source-line 属性,供编辑器与预览的双向滚动对齐使用。\n * @returns Markdown AST 转换器。\n */\nfunction remarkSourceLine(): (tree: MarkdownNode) => void {\n return (tree: MarkdownNode) => {\n // 顶层块级节点列表。\n const blockNodes = tree.children ?? [];\n\n blockNodes.forEach((blockNode) => {\n annotateSourceLine(blockNode);\n\n // 关键步骤:列表项、引用等不在顶层,递归下钻为内部块级节点标注源码行锚点。\n annotateNestedBlockSourceLines(blockNode);\n });\n };\n}\n\n/**\n * 为单个节点注入 data-source-line 源码行锚点。\n * @param node 目标 Markdown 节点。\n */\nfunction annotateSourceLine(node: MarkdownNode): void {\n // 当前节点的源码起始行号(1-based)。\n const startLine = node.position?.start.line;\n\n // 仅标注带源码位置的节点;TOC、定义列表、图片 figure 等转换节点已显式保留原段落位置,\n // 真正无源码位置的纯生成节点自动跳过。\n if (typeof startLine !== \"number\") {\n return;\n }\n\n // 节点 HTML 转换元数据容器。\n const nodeData: MarkdownNodeData = node.data ?? {};\n // 节点 hast 属性容器。\n const hProperties: Record<string, unknown> = nodeData.hProperties ?? {};\n\n hProperties[SOURCE_LINE_HAST_PROPERTY] = startLine;\n nodeData.hProperties = hProperties;\n node.data = nodeData;\n}\n\n/**\n * 递归为块级容器节点内部的子节点标注源码行锚点。\n * 覆盖列表项、嵌套子列表、引用内的代码块与嵌套引用等场景。\n * @param node 当前块级节点。\n */\nfunction annotateNestedBlockSourceLines(node: MarkdownNode): void {\n // 仅块级容器节点的子节点为块级节点,非容器节点无需下钻。\n if (!BLOCK_CONTAINER_NODE_TYPES.has(node.type)) {\n return;\n }\n\n // 容器节点的直接子节点。\n const childNodes = node.children ?? [];\n\n childNodes.forEach((childNode) => {\n annotateSourceLine(childNode);\n annotateNestedBlockSourceLines(childNode);\n });\n}\n\n/**\n * 深度优先遍历,把 text 节点中匹配 ==..== 的片段替换为 mark 节点。\n * @param node 当前节点。\n */\nfunction transformHighlightMarks(node: MarkdownNode): void {\n if (!node.children) {\n return;\n }\n\n // 处理后写回的新子节点列表。\n const nextChildren: MarkdownNode[] = [];\n // 子节点索引。\n for (let childIndex = 0; childIndex < node.children.length; childIndex += 1) {\n // 当前子节点。\n const childNode = node.children[childIndex];\n\n if (\n childNode.type === \"text\" &&\n typeof childNode.value === \"string\" &&\n childNode.value.includes(\"==\")\n ) {\n nextChildren.push(...splitHighlightMarks(childNode.value));\n continue;\n }\n\n transformHighlightMarks(childNode);\n nextChildren.push(childNode);\n }\n\n node.children = nextChildren;\n}\n\n/**\n * 拆分 text 字符串,把 ==content== 段落转换成 mark 节点。\n * @param textValue 原始 text 节点的内容。\n * @returns 拆分后的行内节点列表。\n */\nfunction splitHighlightMarks(textValue: string): MarkdownNode[] {\n // 拆分结果。\n const segments: MarkdownNode[] = [];\n // 当前游标位置。\n let cursor = 0;\n\n HIGHLIGHT_MARKER_PATTERN.lastIndex = 0;\n // 上一个匹配结果。\n let match: RegExpExecArray | null = HIGHLIGHT_MARKER_PATTERN.exec(textValue);\n\n while (match !== null) {\n // 当前匹配开始位置之前的普通文本。\n const leadingText = textValue.slice(cursor, match.index);\n\n if (leadingText.length > 0) {\n segments.push({ type: \"text\", value: leadingText });\n }\n\n segments.push({\n type: \"highlightMark\",\n data: {\n hName: \"mark\"\n },\n children: [{ type: \"text\", value: match[1] }]\n });\n\n cursor = match.index + match[0].length;\n match = HIGHLIGHT_MARKER_PATTERN.exec(textValue);\n }\n\n // 尾部剩余文本。\n const trailingText = textValue.slice(cursor);\n if (trailingText.length > 0) {\n segments.push({ type: \"text\", value: trailingText });\n }\n\n return segments;\n}\n\n/**\n * remark 插件:把 term + 下一行冒号定义的段落转换为定义列表。\n * @returns Markdown AST 转换器。\n */\nfunction remarkDefinitionLists(): (tree: MarkdownNode) => void {\n return (tree: MarkdownNode) => {\n transformDefinitionLists(tree);\n };\n}\n\n/**\n * 深度优先遍历,把定义列表段落替换为 dl/dt/dd 结构。\n * @param node 当前节点。\n */\nfunction transformDefinitionLists(node: MarkdownNode): void {\n if (!node.children) {\n return;\n }\n\n // 当前节点的子节点数组。\n const childNodes = node.children;\n // 子节点索引。\n for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {\n // 当前子节点。\n const childNode = childNodes[childIndex];\n // 当前段落转换后的定义列表节点。\n const definitionListNode = createDefinitionListNode(childNode);\n\n if (definitionListNode) {\n childNodes[childIndex] = definitionListNode;\n continue;\n }\n\n transformDefinitionLists(childNode);\n }\n}\n\n/**\n * 尝试把段落转换为定义列表节点。\n * @param node 待转换的 Markdown 节点。\n * @returns 转换后的定义列表节点,不匹配时返回 undefined。\n */\nfunction createDefinitionListNode(node: MarkdownNode): MarkdownNode | undefined {\n if (node.type !== \"paragraph\") {\n return undefined;\n }\n\n // 段落纯文本内容,用于识别定义列表语法。\n const paragraphText = extractNodeText(node);\n // 定义列表匹配结果。\n const definitionMatch = DEFINITION_LIST_PARAGRAPH_PATTERN.exec(paragraphText);\n\n if (!definitionMatch) {\n return undefined;\n }\n\n // 定义项名称。\n const definitionTerm = definitionMatch[1].trim();\n // 定义项说明。\n const definitionDescription = definitionMatch[2].trim();\n\n if (definitionTerm.length === 0 || definitionDescription.length === 0) {\n return undefined;\n }\n\n return {\n type: \"definitionList\",\n // 关键步骤:保留原段落源码位置,使 remarkSourceLine 能为 dl 标注 data-source-line。\n position: node.position,\n data: {\n hName: \"dl\"\n },\n children: [\n {\n type: \"definitionTerm\",\n data: {\n hName: \"dt\"\n },\n children: [{ type: \"text\", value: definitionTerm }]\n },\n {\n type: \"definitionDescription\",\n data: {\n hName: \"dd\"\n },\n children: [{ type: \"text\", value: definitionDescription }]\n }\n ]\n };\n}\n\n/**\n * remark 插件:把独占段落的图片转换为 figure 结构。\n * @returns Markdown AST 转换器。\n */\nfunction remarkImageFigures(): (tree: MarkdownNode) => void {\n return (tree: MarkdownNode) => {\n transformImageFigures(tree);\n };\n}\n\n/**\n * 深度优先遍历,把块级图片段落转换为可样式化的 figure。\n * @param node 当前节点。\n */\nfunction transformImageFigures(node: MarkdownNode): void {\n if (!node.children) {\n return;\n }\n\n // 当前节点的子节点数组。\n const childNodes = node.children;\n // 子节点索引。\n for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {\n // 当前子节点。\n const childNode = childNodes[childIndex];\n // 当前图片段落转换出的 figure 节点。\n const imageFigureNode = createImageFigureNode(childNode);\n\n if (imageFigureNode) {\n childNodes[childIndex] = imageFigureNode;\n continue;\n }\n\n if (isImageNode(childNode)) {\n decorateImageNode(childNode);\n continue;\n }\n\n transformImageFigures(childNode);\n }\n}\n\n/**\n * 尝试把独占图片段落转换为 figure 节点。\n * @param node 待转换的 Markdown 节点。\n * @returns 转换后的 figure 节点,不匹配时返回 undefined。\n */\nfunction createImageFigureNode(node: MarkdownNode): MarkdownNode | undefined {\n if (node.type !== \"paragraph\" || !node.children || node.children.length !== 1) {\n return undefined;\n }\n\n // 段落内唯一的行内节点。\n const onlyChild = node.children[0];\n\n if (!isImageNode(onlyChild)) {\n return undefined;\n }\n\n decorateImageNode(onlyChild);\n\n return {\n type: \"imageFigure\",\n // 关键步骤:保留原段落源码位置,使 remarkSourceLine 能为 figure 标注 data-source-line。\n position: node.position,\n data: {\n hName: \"figure\",\n hProperties: {\n className: [IMAGE_FIGURE_CLASS_NAME]\n }\n },\n children: [createImageFrameNode(onlyChild), ...createImageCaptionNodes(onlyChild)]\n };\n}\n\n/**\n * 给图片节点写入统一 class。\n * @param imageNode 图片节点。\n */\nfunction decorateImageNode(imageNode: MarkdownNode): void {\n imageNode.data = {\n ...imageNode.data,\n hProperties: {\n ...imageNode.data?.hProperties,\n className: [IMAGE_ELEMENT_CLASS_NAME]\n }\n };\n}\n\n/**\n * 创建图片边框容器节点。\n * @param imageNode 图片节点。\n * @returns 图片边框容器节点。\n */\nfunction createImageFrameNode(imageNode: MarkdownNode): MarkdownNode {\n return {\n type: \"imageFrame\",\n data: {\n hName: \"span\",\n hProperties: {\n className: [IMAGE_FRAME_CLASS_NAME]\n }\n },\n children: [imageNode, createImageFallbackNode(imageNode)]\n };\n}\n\n/**\n * 创建图片标题节点列表。\n * @param imageNode 图片节点。\n * @returns 图片标题节点列表。\n */\nfunction createImageCaptionNodes(imageNode: MarkdownNode): MarkdownNode[] {\n // 图片 title 属性文本,用于生成 figcaption。\n const imageTitle = typeof imageNode.title === \"string\" ? imageNode.title.trim() : \"\";\n\n if (imageTitle.length === 0) {\n return [];\n }\n\n return [\n {\n type: \"imageCaption\",\n data: {\n hName: \"figcaption\",\n hProperties: {\n className: [IMAGE_CAPTION_CLASS_NAME]\n }\n },\n children: [{ type: \"text\", value: imageTitle }]\n }\n ];\n}\n\n/**\n * 创建图片失败态占位节点。\n * @param imageNode 图片节点。\n * @returns 图片失败态占位节点。\n */\nfunction createImageFallbackNode(imageNode: MarkdownNode): MarkdownNode {\n // 图片失败态展示的 alt 文本。\n const fallbackText = imageNode.alt?.trim() || \"图片加载失败\";\n // 图片失败态展示的来源路径。\n const fallbackSource = imageNode.url ?? imageNode.identifier ?? \"\";\n\n return {\n type: \"imageFallback\",\n data: {\n hName: \"span\",\n hProperties: {\n className: [IMAGE_FALLBACK_CLASS_NAME]\n }\n },\n children: [\n {\n type: \"imageFallbackIcon\",\n data: {\n hName: \"span\",\n hProperties: {\n className: [IMAGE_FALLBACK_ICON_CLASS_NAME]\n }\n }\n },\n {\n type: \"imageFallbackText\",\n data: {\n hName: \"span\",\n hProperties: {\n className: [IMAGE_FALLBACK_TEXT_CLASS_NAME]\n }\n },\n children: [{ type: \"text\", value: fallbackText }]\n },\n {\n type: \"imageFallbackSource\",\n data: {\n hName: \"span\",\n hProperties: {\n className: [IMAGE_FALLBACK_SOURCE_CLASS_NAME]\n }\n },\n children: [{ type: \"text\", value: fallbackSource }]\n }\n ]\n };\n}\n\n/**\n * 判断节点是否为图片或引用式图片。\n * @param node 待判断的 Markdown 节点。\n * @returns 当前节点是否为图片节点。\n */\nfunction isImageNode(node: MarkdownNode): boolean {\n return node.type === \"image\" || node.type === \"imageReference\";\n}\n\n/**\n * remark 插件:收集标题、生成标题锚点,并把独占一段的 [TOC] 替换为目录。\n * @returns Markdown AST 转换器。\n */\nfunction remarkTableOfContents(): (tree: MarkdownNode) => void {\n return (tree: MarkdownNode) => {\n // 标题条目,同时会在收集时写入 heading id。\n const tocHeadings = collectTocHeadings(tree);\n\n // 关键步骤:用目录节点替换所有独占一段的 [TOC]。\n replaceTocMarkers(tree, tocHeadings);\n };\n}\n\n/**\n * 收集文档标题并给标题节点写入稳定 id。\n * @param tree Markdown 根节点。\n * @returns 可用于渲染目录的标题列表。\n */\nfunction collectTocHeadings(tree: MarkdownNode): TocHeading[] {\n // 标题 slug 使用次数,用于处理重复标题。\n const headingSlugCounts = new Map<string, number>();\n // 收集到的目录标题条目。\n const tocHeadings: TocHeading[] = [];\n // 各标题层级的计数器,用于生成 1 / 1.1 / 1.1.1 这类目录序号。\n const headingIndexCounts = Array.from({ length: 7 }, () => 0);\n // 文档内最浅标题层级,用作目录序号的根层级。\n let rootHeadingDepth: number | undefined;\n\n visitMarkdownNode(tree, (node: MarkdownNode) => {\n if (node.type !== \"heading\") {\n return;\n }\n\n // 标题层级默认回退到二级标题。\n const headingDepth = node.depth ?? 2;\n // 从标题行内节点提取纯文本。\n const headingText = extractNodeText(node).trim();\n // 为标题生成去重后的锚点。\n const headingId = createUniqueSlug(headingText, headingSlugCounts, tocHeadings.length + 1);\n // 根据标题层级生成目录编号。\n const headingIndex = createHeadingIndex(\n headingDepth,\n headingIndexCounts,\n rootHeadingDepth ?? headingDepth\n );\n\n rootHeadingDepth = rootHeadingDepth ?? headingDepth;\n\n node.data = {\n ...node.data,\n hProperties: {\n ...node.data?.hProperties,\n id: headingId\n }\n };\n\n // 关键步骤:把标题行内内容包进一个 inline 元素,\n // 让样式可以借助 box-decoration-break 在多行间复制高亮背景。\n node.children = [\n {\n type: \"headingMark\",\n data: {\n hName: \"span\",\n hProperties: {\n className: [HEADING_MARK_CLASS_NAME]\n }\n },\n children: node.children ?? []\n }\n ];\n\n tocHeadings.push({\n depth: headingDepth,\n id: headingId,\n index: headingIndex,\n text: headingText || headingId\n });\n });\n\n return tocHeadings;\n}\n\n/**\n * 深度优先遍历 Markdown AST。\n * @param node 当前节点。\n * @param visitor 节点访问函数。\n */\nfunction visitMarkdownNode(node: MarkdownNode, visitor: (node: MarkdownNode) => void): void {\n visitor(node);\n\n if (!node.children) {\n return;\n }\n\n // 子节点数组。\n const childNodes = node.children;\n // 子节点索引。\n for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {\n visitMarkdownNode(childNodes[childIndex], visitor);\n }\n}\n\n/**\n * 将 [TOC] 段落替换为目录节点。\n * @param node 当前节点。\n * @param tocHeadings 目录标题条目。\n */\nfunction replaceTocMarkers(node: MarkdownNode, tocHeadings: TocHeading[]): void {\n if (!node.children) {\n return;\n }\n\n // 当前节点的子节点数组。\n const childNodes = node.children;\n // 子节点索引。\n for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {\n // 当前子节点。\n const childNode = childNodes[childIndex];\n\n if (isTocMarkerParagraph(childNode)) {\n childNodes[childIndex] = createTocNode(tocHeadings, childNode);\n continue;\n }\n\n replaceTocMarkers(childNode, tocHeadings);\n }\n}\n\n/**\n * 判断段落是否是独占的 [TOC] 标记。\n * @param node 待判断节点。\n * @returns 当前节点是否为目录占位段落。\n */\nfunction isTocMarkerParagraph(node: MarkdownNode): boolean {\n if (node.type !== \"paragraph\" || !node.children || node.children.length !== 1) {\n return false;\n }\n\n // 段落内唯一的行内节点。\n const onlyChild = node.children[0];\n\n return onlyChild.type === \"text\" && TOC_MARKER_PATTERN.test(onlyChild.value ?? \"\");\n}\n\n/**\n * 创建目录容器节点。\n * @param tocHeadings 目录标题条目。\n * @param markerNode [TOC] 标记段落节点,用于保留源码位置。\n * @returns 可被 remark-rehype 转换为 details 的 Markdown 节点。\n */\nfunction createTocNode(tocHeadings: TocHeading[], markerNode: MarkdownNode): MarkdownNode {\n // 层级化后的目录条目,用于生成可折叠分支。\n const tocTree = createTocTree(tocHeadings);\n\n return {\n type: \"toc\",\n // 关键步骤:保留 [TOC] 标记段落的源码位置,使 remarkSourceLine 能为目录标注 data-source-line。\n position: markerNode.position,\n data: {\n hName: \"details\",\n hProperties: {\n // 关键步骤:目录根 opt-in 手绘边框,再由 toc.css 叠加目录自身样式。\n className: [TOC_CLASS_NAME, FRAME_CLASS_NAME]\n }\n },\n children: [\n {\n type: \"tocSummary\",\n data: {\n hName: \"summary\",\n hProperties: {\n className: [TOC_SUMMARY_CLASS_NAME]\n }\n },\n children: [\n {\n type: \"text\",\n value: TOC_SUMMARY_TEXT\n }\n ]\n },\n {\n type: \"tocNav\",\n data: {\n hName: \"nav\",\n hProperties: {\n ariaLabel: TOC_ARIA_LABEL,\n className: [TOC_NAV_CLASS_NAME]\n }\n },\n children: [createTocListNode(tocTree, false)]\n }\n ]\n };\n}\n\n/**\n * 将扁平标题列表转换为目录树。\n * @param tocHeadings 扁平目录标题条目。\n * @returns 可生成嵌套目录的树形条目。\n */\nfunction createTocTree(tocHeadings: TocHeading[]): TocTreeItem[] {\n // 目录根节点集合。\n const rootItems: TocTreeItem[] = [];\n // 当前遍历路径上的分支栈。\n const itemStack: TocTreeItem[] = [];\n\n // 目录标题索引。\n for (let headingIndex = 0; headingIndex < tocHeadings.length; headingIndex += 1) {\n // 当前遍历到的标题条目。\n const tocHeading = tocHeadings[headingIndex];\n // 当前标题转换后的树节点。\n const tocTreeItem: TocTreeItem = {\n ...tocHeading,\n children: []\n };\n\n // 关键步骤:回退到比当前标题更浅的父级。\n while (itemStack.length > 0 && itemStack[itemStack.length - 1].depth >= tocTreeItem.depth) {\n itemStack.pop();\n }\n\n if (itemStack.length === 0) {\n rootItems.push(tocTreeItem);\n } else {\n itemStack[itemStack.length - 1].children.push(tocTreeItem);\n }\n\n itemStack.push(tocTreeItem);\n }\n\n return rootItems;\n}\n\n/**\n * 创建目录列表节点。\n * @param tocItems 当前层级的目录条目。\n * @param isNested 是否为嵌套列表。\n * @returns Markdown 列表节点。\n */\nfunction createTocListNode(tocItems: TocTreeItem[], isNested: boolean): MarkdownNode {\n // 当前目录列表需要输出的类名。\n const tocListClassNames = isNested\n ? [TOC_LIST_CLASS_NAME, TOC_LIST_NESTED_CLASS_NAME]\n : [TOC_LIST_CLASS_NAME];\n\n return {\n type: \"list\",\n ordered: true,\n spread: false,\n data: {\n hProperties: {\n className: tocListClassNames\n }\n },\n children: tocItems.map(createTocListItem)\n };\n}\n\n/**\n * 创建目录列表项。\n * @param tocItem 目录树条目。\n * @returns Markdown 列表项节点。\n */\nfunction createTocListItem(tocItem: TocTreeItem): MarkdownNode {\n // 当前条目是否拥有可折叠的子层级。\n const hasChildren = tocItem.children.length > 0;\n // 当前目录条目的 class 列表。\n const tocItemClassNames = createTocItemClassNames(tocItem.depth, hasChildren);\n\n // 关键步骤:叶子与分支的标题都用同一个 <a href=\"#id\">(createTocLinkNode)、共用同一套跳转;\n // 分支只是额外在标题前放一个「独立的折叠按钮」、标题后放嵌套子列表。\n // 折叠按钮与标题链接分离,从结构上消除「点标题既跳转又折叠」的冲突。\n const itemChildren: MarkdownNode[] = hasChildren\n ? [createTocToggleNode(), createTocLinkNode(tocItem), createTocListNode(tocItem.children, true)]\n : [createTocLinkNode(tocItem)];\n\n return {\n type: \"listItem\",\n spread: false,\n data: {\n hProperties: {\n dataTocIndex: tocItem.index,\n className: tocItemClassNames\n }\n },\n children: itemChildren\n };\n}\n\n/**\n * 生成目录条目所需的 class 列表,inline TOC 与 toolbar 抽屉共用。\n * @param depth 标题原始层级。\n * @param hasChildren 是否拥有可折叠子层级。\n * @returns class 列表。\n */\nfunction createTocItemClassNames(depth: number, hasChildren: boolean): string[] {\n return [\n TOC_ITEM_CLASS_PREFIX,\n `${TOC_ITEM_CLASS_PREFIX}--depth-${depth}`,\n ...(hasChildren ? [TOC_ITEM_BRANCH_CLASS_NAME] : [])\n ];\n}\n\n/**\n * 创建目录分支的折叠按钮节点(与标题链接分离,专职展开/折叠)。\n * 默认 aria-expanded=\"true\"(展开),运行时由 {@link hydrateToc} 绑定点击切换。\n * @returns 可被 remark-rehype 转换为 button 的 Markdown 节点。\n */\nfunction createTocToggleNode(): MarkdownNode {\n return {\n type: \"tocToggle\",\n data: {\n hName: \"button\",\n hProperties: {\n type: \"button\",\n className: [TOC_TOGGLE_CLASS_NAME],\n ariaExpanded: \"true\",\n ariaLabel: TOC_TOGGLE_ARIA_LABEL\n }\n },\n children: []\n };\n}\n\n/**\n * 创建目录条目的标题跳转链接(叶子与分支共用)。\n * 仅产出原生 <a href=\"#id\">,由宿主锚点处理(webview 拦截器 / 浏览器原生 hash)统一滚动,\n * 渲染器不再附加点击监听,避免与宿主重复触发滚动。\n * @param tocItem 目录树条目。\n * @returns Markdown 链接节点。\n */\nfunction createTocLinkNode(tocItem: TocTreeItem): MarkdownNode {\n return {\n type: \"link\",\n url: `#${tocItem.id}`,\n data: {\n hProperties: {\n className: [TOC_LINK_CLASS_NAME]\n }\n },\n children: [\n {\n type: \"text\",\n value: tocItem.text\n }\n ]\n };\n}\n\n/**\n * 从已渲染的 DOM 中收集带 id 的标题,转换为目录条目。\n * 与 inline TOC 共用 {@link createHeadingIndex} 生成层级编号,使 toolbar 抽屉与文档内目录的层级逻辑一致。\n * @param rootElement 含 Markdown 渲染结果的根节点。\n * @returns 扁平目录标题条目。\n */\nfunction collectTocHeadingsFromDom(rootElement: ParentNode): TocHeading[] {\n /** Markdown 渲染容器;rootElement 自身或其后代命中 .scribdown-markdown 时即视为容器,否则视为未渲染。 */\n const markdownContainer =\n rootElement instanceof Element && rootElement.matches(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME}`)\n ? rootElement\n : rootElement.querySelector<HTMLElement>(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME}`);\n if (!markdownContainer) {\n return [];\n }\n\n /** 渲染区域内所有带 id 的标题元素。 */\n const headingElements = Array.from(\n markdownContainer.querySelectorAll<HTMLHeadingElement>(\n \"h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]\"\n )\n );\n\n /** 收集结果。 */\n const tocHeadings: TocHeading[] = [];\n /** 各标题层级的计数器。 */\n const headingIndexCounts = Array.from({ length: 7 }, () => 0);\n /** 文档内首个标题的层级,作为目录序号根层级。 */\n let rootHeadingDepth: number | undefined;\n\n for (const headingElement of headingElements) {\n // 从标签名解析当前标题层级(h1..h6)。\n const headingDepth = parseInt(headingElement.tagName.slice(1), 10);\n if (!Number.isFinite(headingDepth)) {\n continue;\n }\n\n // 标题可见文本。\n const headingText = headingElement.textContent?.trim() ?? \"\";\n // 标题锚点 id,由 inline TOC 渲染时写入。\n const headingId = headingElement.id;\n // 与 inline TOC 共用的层级编号生成逻辑。\n const headingIndex = createHeadingIndex(\n headingDepth,\n headingIndexCounts,\n rootHeadingDepth ?? headingDepth\n );\n rootHeadingDepth = rootHeadingDepth ?? headingDepth;\n\n tocHeadings.push({\n depth: headingDepth,\n id: headingId,\n index: headingIndex,\n text: headingText || headingId\n });\n }\n\n return tocHeadings;\n}\n\n/**\n * 用与 inline TOC 完全一致的结构构建目录 nav 节点(toolbar 侧栏复用)。\n * 标题统一为原生 <a href=\"#id\">(跳转交给宿主锚点处理),分支额外带独立折叠按钮 + 嵌套列表;\n * 折叠交互由挂载后调用的 {@link hydrateToc} 统一绑定。\n * @param ownerDocument 目标 document。\n * @param tocTreeItems 目录树根节点集合。\n * @returns 含完整目录列表的 nav 元素。\n */\nfunction createTocNavElement(ownerDocument: Document, tocTreeItems: TocTreeItem[]): HTMLElement {\n const navElement = ownerDocument.createElement(\"nav\");\n navElement.setAttribute(\"aria-label\", TOC_ARIA_LABEL);\n navElement.className = TOC_NAV_CLASS_NAME;\n navElement.appendChild(createTocListElement(ownerDocument, tocTreeItems, false));\n return navElement;\n}\n\n/**\n * 构造目录列表元素,与 inline TOC 的 ol 结构保持一致。\n * @param ownerDocument 目标 document。\n * @param tocItems 当前层级的目录条目。\n * @param isNested 是否为嵌套列表。\n * @param options 共享构造选项。\n * @returns ol 列表元素。\n */\nfunction createTocListElement(\n ownerDocument: Document,\n tocItems: TocTreeItem[],\n isNested: boolean\n): HTMLOListElement {\n const listElement = ownerDocument.createElement(\"ol\");\n listElement.className = isNested\n ? `${TOC_LIST_CLASS_NAME} ${TOC_LIST_NESTED_CLASS_NAME}`\n : TOC_LIST_CLASS_NAME;\n\n for (const tocItem of tocItems) {\n listElement.appendChild(createTocListItemElement(ownerDocument, tocItem));\n }\n\n return listElement;\n}\n\n/**\n * 构造目录列表项(li):叶子与分支共用同一个标题链接 <a href=\"#id\">。\n * 分支额外在标题前插入「独立折叠按钮」、标题后插入嵌套子列表;不再用 <details>/<summary>,\n * 从结构上消除「点标题既跳转又折叠」的冲突与 offsetX 像素 hack。\n * 跳转交给宿主锚点处理(不挂渲染器点击监听,避免与宿主重复触发滚动);\n * 折叠交互由 {@link hydrateToc} 在挂载后统一绑定。\n * @param ownerDocument 目标 document。\n * @param tocItem 当前目录条目。\n * @returns li 元素。\n */\nfunction createTocListItemElement(ownerDocument: Document, tocItem: TocTreeItem): HTMLLIElement {\n /** 当前条目是否拥有可折叠的子层级。 */\n const hasChildren = tocItem.children.length > 0;\n\n const itemElement = ownerDocument.createElement(\"li\");\n itemElement.className = createTocItemClassNames(tocItem.depth, hasChildren).join(\" \");\n itemElement.dataset.tocIndex = tocItem.index;\n\n // 分支:标题前插入独立折叠按钮(与 inline TOC 同构,hydrate 时绑定展开/折叠)。\n if (hasChildren) {\n const toggleElement = ownerDocument.createElement(\"button\");\n toggleElement.type = \"button\";\n toggleElement.className = TOC_TOGGLE_CLASS_NAME;\n toggleElement.setAttribute(\"aria-expanded\", \"true\");\n toggleElement.setAttribute(\"aria-label\", TOC_TOGGLE_ARIA_LABEL);\n itemElement.appendChild(toggleElement);\n }\n\n // 标题链接:叶子与分支完全相同,仅产出原生 <a href=\"#id\">,跳转交给宿主锚点处理。\n const linkElement = ownerDocument.createElement(\"a\");\n linkElement.className = TOC_LINK_CLASS_NAME;\n linkElement.href = `#${tocItem.id}`;\n linkElement.textContent = tocItem.text;\n itemElement.appendChild(linkElement);\n\n // 分支:标题后插入嵌套子列表。\n if (hasChildren) {\n itemElement.appendChild(createTocListElement(ownerDocument, tocItem.children, true));\n }\n\n return itemElement;\n}\n\n/**\n * 为目录绑定运行时交互(inline TOC 与 toolbar 侧栏共用):\n * 1. 分支折叠按钮的展开/折叠;\n * 2. 标题链接的跳转 —— 在冒泡阶段拦截原生 hash 跳转,改用注入的 scrollToHeading 滚动到标题\n * (webview 宿主拦截器对 .scribdown-toc-link 放行,跳转滚动完全交给这里)。\n * 滚动实现由宿主注入(缺省原生平滑),渲染器只负责调用——平台差异落在 apps/* 各宿主。\n * 幂等:已绑定过的元素(带 hydrate 标记)跳过,可安全在每次 hydrate / 挂载时重复调用。\n * @param rootElement 含目录结构的根节点。\n * @param scrollToHeading 跳转到目标标题的滚动实现(由宿主注入)。\n */\nfunction hydrateToc(\n rootElement: ParentNode,\n scrollToHeading: (targetElement: HTMLElement) => void\n): void {\n // 根节点内所有目录折叠按钮。\n const toggleElements = rootElement.querySelectorAll<HTMLButtonElement>(\n `button.${TOC_TOGGLE_CLASS_NAME}`\n );\n\n toggleElements.forEach((toggleElement) => {\n if (toggleElement.dataset[TOC_TOGGLE_HYDRATED_DATA_KEY] === \"true\") {\n return;\n }\n toggleElement.dataset[TOC_TOGGLE_HYDRATED_DATA_KEY] = \"true\";\n\n toggleElement.addEventListener(\"click\", () => {\n // 按钮所属的目录条目(承载折叠态 class,并包含待显隐的嵌套列表)。\n const itemElement = toggleElement.closest<HTMLLIElement>(`.${TOC_ITEM_CLASS_PREFIX}`);\n if (!itemElement) {\n return;\n }\n // 切换后是否处于折叠态。\n const nextCollapsed = !itemElement.classList.contains(TOC_ITEM_COLLAPSED_CLASS_NAME);\n itemElement.classList.toggle(TOC_ITEM_COLLAPSED_CLASS_NAME, nextCollapsed);\n // 关键步骤:aria-expanded 与折叠态保持同步,供无障碍读屏与 CSS 箭头朝向使用。\n toggleElement.setAttribute(\"aria-expanded\", String(!nextCollapsed));\n });\n });\n\n // 根节点内所有目录标题跳转链接。\n const linkElements = rootElement.querySelectorAll<HTMLAnchorElement>(\n `a.${TOC_LINK_CLASS_NAME}`\n );\n\n linkElements.forEach((linkElement) => {\n if (linkElement.dataset[TOC_LINK_HYDRATED_DATA_KEY] === \"true\") {\n return;\n }\n linkElement.dataset[TOC_LINK_HYDRATED_DATA_KEY] = \"true\";\n\n linkElement.addEventListener(\"click\", (event) => {\n // 关键步骤:拦截原生 hash 跳转(webview 下会触发被 CSP 拦截的 iframe 导航),\n // 改由宿主注入的 scrollToHeading 滚动定位。\n event.preventDefault();\n // 链接所属 document。\n const ownerDocument = linkElement.ownerDocument;\n // 目标标题 id(去掉前导 #)。\n const rawTargetId = (linkElement.getAttribute(\"href\") ?? \"\").slice(1);\n if (!rawTargetId) {\n return;\n }\n // 目标标题元素:优先按原文匹配,失败再尝试解码兜底(兼容被编码的中文 id)。\n let targetElement = ownerDocument.getElementById(rawTargetId);\n if (!targetElement) {\n try {\n targetElement = ownerDocument.getElementById(decodeURIComponent(rawTargetId));\n } catch {\n targetElement = null;\n }\n }\n if (targetElement) {\n scrollToHeading(targetElement);\n }\n });\n });\n}\n\n/**\n * 从节点及其子节点中提取可读文本。\n * @param node 当前节点。\n * @returns 当前节点的纯文本内容。\n */\nfunction extractNodeText(node: MarkdownNode): string {\n if (node.type === \"text\" || node.type === \"inlineCode\") {\n return node.value ?? \"\";\n }\n\n if (isImageNode(node)) {\n return node.alt ?? \"\";\n }\n\n if (!node.children) {\n return \"\";\n }\n\n return node.children.map(extractNodeText).join(\"\");\n}\n\n/**\n * 生成去重后的标题 slug。\n * @param headingText 标题文本。\n * @param slugCounts 已使用 slug 计数。\n * @param fallbackIndex 空标题回退序号。\n * @returns 唯一标题锚点。\n */\nfunction createUniqueSlug(\n headingText: string,\n slugCounts: Map<string, number>,\n fallbackIndex: number\n): string {\n // 标准化后的基础 slug。\n const normalizedSlug = normalizeSlugText(headingText);\n // 空标题的回退 slug。\n const fallbackSlug = `${EMPTY_HEADING_SLUG_PREFIX}-${fallbackIndex}`;\n // 本次使用的基础 slug。\n const baseSlug = normalizedSlug || fallbackSlug;\n // 当前 slug 已出现次数。\n const usedCount = slugCounts.get(baseSlug) ?? 0;\n\n slugCounts.set(baseSlug, usedCount + 1);\n\n if (usedCount === 0) {\n return baseSlug;\n }\n\n return `${baseSlug}-${usedCount}`;\n}\n\n/**\n * 根据标题层级生成目录编号。\n * @param headingDepth 当前标题层级。\n * @param headingIndexCounts 各标题层级已出现次数。\n * @param rootHeadingDepth 目录根标题层级。\n * @returns 层级化目录编号。\n */\nfunction createHeadingIndex(\n headingDepth: number,\n headingIndexCounts: number[],\n rootHeadingDepth: number\n): string {\n // 起始层级不能超过当前标题层级。\n const startDepth = Math.min(rootHeadingDepth, headingDepth);\n\n headingIndexCounts[headingDepth] += 1;\n\n // 关键步骤:当前层级之后的子层级计数失效。\n for (let depthIndex = headingDepth + 1; depthIndex < headingIndexCounts.length; depthIndex += 1) {\n headingIndexCounts[depthIndex] = 0;\n }\n\n // 补齐被跳过的父级,避免出现 1.0.1 这种编号。\n for (let depthIndex = startDepth; depthIndex < headingDepth; depthIndex += 1) {\n if (headingIndexCounts[depthIndex] === 0) {\n headingIndexCounts[depthIndex] = 1;\n }\n }\n\n return headingIndexCounts.slice(startDepth, headingDepth + 1).join(\".\");\n}\n\n/**\n * 将标题文本标准化为 URL 片段。\n * @param headingText 标题文本。\n * @returns 标准化后的 slug。\n */\nfunction normalizeSlugText(headingText: string): string {\n return headingText\n .trim()\n .toLowerCase()\n .replace(/<[^>]+>/g, \"\")\n .replace(/[^\\p{L}\\p{N}\\s_-]+/gu, \"\")\n .replace(/\\s+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n\n/**\n * hast 节点的最小结构,覆盖元素 / 文本两类,\n * 仅暴露视频 figure 包装需要的字段,避免引入 @types/hast。\n */\ninterface HastNode {\n type: string;\n tagName?: string;\n properties?: Record<string, unknown>;\n value?: string;\n children?: HastNode[];\n}\n\n/**\n * rehype 插件:为内容折叠块注入手绘边框 class,实现样式层的 opt-in。\n * 用户手写的原生 <details>(非目录用)打上 scribdown-details + scribdown-frame;\n * 目录根的 scribdown-frame 已在 mdast 阶段注入,目录根在此跳过(分支已改用 button,无 details)。\n * 该插件运行在 rehype-raw 之后、rehype-sanitize 之前,注入的 class 由 sanitize 白名单放行。\n * @returns hast 转换器。\n */\nfunction rehypeFrameClass(): (tree: HastNode) => void {\n return (tree: HastNode) => {\n tagContentDetails(tree);\n };\n}\n\n/**\n * 深度优先遍历 hast 树,为内容折叠块注入边框 class。\n * @param node 当前 hast 节点。\n */\nfunction tagContentDetails(node: HastNode): void {\n if (node.type === \"element\" && node.tagName === \"details\") {\n // 当前 details 已有的 class 列表。\n const classNames = normalizeClassNames(node.properties?.className);\n // 目录根使用自己的命名空间,不当作内容折叠块处理。\n const isTocDetails = classNames.includes(TOC_CLASS_NAME);\n if (!isTocDetails) {\n addClassNames(node, [DETAILS_CLASS_NAME, FRAME_CLASS_NAME]);\n }\n }\n\n if (Array.isArray(node.children)) {\n for (const childNode of node.children) {\n tagContentDetails(childNode);\n }\n }\n}\n\n/**\n * 将 hast 节点的 className 属性标准化为字符串数组。\n * @param className hast 节点上的 className 属性原值。\n * @returns 标准化后的 class 名数组。\n */\nfunction normalizeClassNames(className: unknown): string[] {\n if (Array.isArray(className)) {\n return className.filter((item): item is string => typeof item === \"string\");\n }\n if (typeof className === \"string\") {\n return className.split(/\\s+/u).filter(Boolean);\n }\n return [];\n}\n\n/**\n * 向 hast 元素追加 class 名(去重)。\n * @param node 目标 hast 元素。\n * @param classNamesToAdd 待追加的 class 名列表。\n */\nfunction addClassNames(node: HastNode, classNamesToAdd: string[]): void {\n // 合并去重后的 class 列表。\n const mergedClassNames = normalizeClassNames(node.properties?.className);\n for (const classNameToAdd of classNamesToAdd) {\n if (!mergedClassNames.includes(classNameToAdd)) {\n mergedClassNames.push(classNameToAdd);\n }\n }\n node.properties = { ...node.properties, className: mergedClassNames };\n}\n\n/**\n * rehype 插件:把渲染后的 <video> 元素包装为 figure 结构,\n * 与图片 figure 保持视觉与失败回退状态机一致。\n * 该插件运行在 rehype-raw 之后、rehype-sanitize 之前,结构清洗在后兜底。\n * @returns hast 转换器。\n */\nfunction rehypeVideoFigures(): (tree: HastNode) => void {\n return (tree: HastNode) => {\n transformVideoFigures(tree);\n };\n}\n\n/**\n * 深度优先遍历 hast 树,把 video 节点替换为 video figure 结构。\n * @param node 当前 hast 节点。\n */\nfunction transformVideoFigures(node: HastNode): void {\n if (!Array.isArray(node.children)) {\n return;\n }\n\n // 当前节点的子节点数组。\n const childNodes = node.children;\n // 子节点索引。\n for (let childIndex = 0; childIndex < childNodes.length; childIndex += 1) {\n // 当前子节点。\n const childNode = childNodes[childIndex];\n\n if (isHastVideoElement(childNode)) {\n childNodes[childIndex] = createVideoFigureHast(childNode);\n continue;\n }\n\n // <p><video/></p> 形式(行内 HTML 解析的常见结果):把整段替换为 figure。\n /** 仅包含单个 video 的段落剥离结果。 */\n const standaloneVideo = extractStandaloneParagraphVideo(childNode);\n if (standaloneVideo) {\n childNodes[childIndex] = createVideoFigureHast(standaloneVideo);\n continue;\n }\n\n transformVideoFigures(childNode);\n }\n}\n\n/**\n * 判断 hast 节点是否为 <video> 元素。\n * @param node 待判断的 hast 节点。\n * @returns 是否为 video 元素。\n */\nfunction isHastVideoElement(node: HastNode): boolean {\n return node.type === \"element\" && node.tagName === \"video\";\n}\n\n/**\n * 尝试从只包含单个 <video> 的 <p> 段落中剥离出该 video 节点。\n * @param node 待检查的 hast 节点。\n * @returns 剥离出的 video 节点;不匹配则返回 undefined。\n */\nfunction extractStandaloneParagraphVideo(node: HastNode): HastNode | undefined {\n if (\n node.type !== \"element\" ||\n node.tagName !== \"p\" ||\n !Array.isArray(node.children)\n ) {\n return undefined;\n }\n\n /** 段落内忽略纯空白文本节点后的有效子节点列表。 */\n const significantChildren = node.children.filter((childNode) => {\n if (childNode.type === \"text\" && typeof childNode.value === \"string\") {\n return childNode.value.trim().length > 0;\n }\n return true;\n });\n\n if (\n significantChildren.length === 1 &&\n isHastVideoElement(significantChildren[0])\n ) {\n return significantChildren[0];\n }\n\n return undefined;\n}\n\n/**\n * 把 video 节点装饰类名后包装为 figure + frame + fallback 的 hast 结构。\n * @param videoNode 原始 video 节点。\n * @returns 新的 figure hast 节点。\n */\nfunction createVideoFigureHast(videoNode: HastNode): HastNode {\n decorateHastVideoElement(videoNode);\n\n /** 用于失败态展示的源 URL。 */\n const sourceUrl = readHastVideoSourceUrl(videoNode);\n\n return {\n type: \"element\",\n tagName: \"figure\",\n properties: { className: [VIDEO_FIGURE_CLASS_NAME] },\n children: [\n {\n type: \"element\",\n tagName: \"span\",\n properties: { className: [VIDEO_FRAME_CLASS_NAME] },\n children: [videoNode, createVideoFallbackHast(sourceUrl)]\n }\n ]\n };\n}\n\n/**\n * 给 video 节点的 className 数组追加统一类名(保留原有 class)。\n * @param videoNode 待装饰的 video 节点。\n */\nfunction decorateHastVideoElement(videoNode: HastNode): void {\n /** video 节点上的属性容器。 */\n const properties = videoNode.properties ?? {};\n /** 现有 className,可能是数组、字符串或缺失。 */\n const existingClassName = properties.className;\n /** 规范化后的 className 数组。 */\n const classList: string[] = Array.isArray(existingClassName)\n ? existingClassName.map(String)\n : typeof existingClassName === \"string\"\n ? [existingClassName]\n : [];\n\n if (!classList.includes(VIDEO_ELEMENT_CLASS_NAME)) {\n classList.push(VIDEO_ELEMENT_CLASS_NAME);\n }\n\n properties.className = classList;\n videoNode.properties = properties;\n}\n\n/**\n * 读取 video 节点的源 URL,优先使用 src 属性,其次回退到首个 <source> 子节点。\n * @param videoNode video 节点。\n * @returns 源 URL,未找到时返回空串。\n */\nfunction readHastVideoSourceUrl(videoNode: HastNode): string {\n /** video 节点上的属性容器。 */\n const properties = videoNode.properties ?? {};\n if (typeof properties.src === \"string\") {\n return properties.src;\n }\n\n if (Array.isArray(videoNode.children)) {\n for (const childNode of videoNode.children) {\n if (\n childNode.type === \"element\" &&\n childNode.tagName === \"source\" &&\n typeof childNode.properties?.src === \"string\"\n ) {\n return childNode.properties.src as string;\n }\n }\n }\n\n return \"\";\n}\n\n/**\n * 构造视频失败态占位 hast 节点。\n * @param sourceUrl 视频源 URL,用于失败态尾部展示。\n * @returns 失败态占位 hast 节点。\n */\nfunction createVideoFallbackHast(sourceUrl: string): HastNode {\n return {\n type: \"element\",\n tagName: \"span\",\n properties: { className: [VIDEO_FALLBACK_CLASS_NAME] },\n children: [\n {\n type: \"element\",\n tagName: \"span\",\n properties: { className: [VIDEO_FALLBACK_ICON_CLASS_NAME] },\n children: []\n },\n {\n type: \"element\",\n tagName: \"span\",\n properties: { className: [VIDEO_FALLBACK_TEXT_CLASS_NAME] },\n children: [{ type: \"text\", value: VIDEO_FALLBACK_DEFAULT_TEXT }]\n },\n {\n type: \"element\",\n tagName: \"span\",\n properties: { className: [VIDEO_FALLBACK_SOURCE_CLASS_NAME] },\n children: [{ type: \"text\", value: sourceUrl }]\n }\n ]\n };\n}\n\n// ─── Page Toolbar ────────────────────────────────────────────────────────────\n\n// 页面宽度预设列表(label 用于按钮徽章,value 为 CSS max-width 值)。\nconst TOOLBAR_WIDTH_PRESETS: Array<{ label: string; value: string }> = [\n { label: \"680\", value: \"680px\" },\n { label: \"840\", value: \"840px\" },\n { label: \"1080\", value: \"1080px\" },\n { label: \"100%\", value: \"100%\" }\n];\n\n// 默认内容宽度。\nconst TOOLBAR_DEFAULT_WIDTH = \"840px\";\n\n/**\n * 从 localStorage 读取已保存的内容宽度,不可用时返回默认值。\n * @returns 宽度 CSS 值字符串。\n */\nfunction loadContentWidth(): string {\n try {\n return localStorage.getItem(CONTENT_WIDTH_STORAGE_KEY) ?? TOOLBAR_DEFAULT_WIDTH;\n } catch {\n return TOOLBAR_DEFAULT_WIDTH;\n }\n}\n\n/**\n * 将内容宽度写入 localStorage。\n * @param value 宽度 CSS 值字符串。\n */\nfunction saveContentWidth(value: string): void {\n try {\n localStorage.setItem(CONTENT_WIDTH_STORAGE_KEY, value);\n } catch {\n // localStorage 不可用时静默跳过。\n }\n}\n\n/**\n * 把内容宽度应用到文档根节点的 CSS 自定义属性。\n * @param ownerDocument 目标 document。\n * @param value 宽度 CSS 值字符串。\n */\nfunction applyContentWidth(ownerDocument: Document, value: string): void {\n ownerDocument.documentElement.style.setProperty(\"--scribdown-content-width\", value);\n}\n\n/**\n * 创建一个工具栏按钮。\n * @param ownerDocument 目标 document。\n * @param ariaLabel 可访问名称(同时用作 tooltip)。\n * @param svgContent 按钮内嵌 SVG 字符串。\n * @returns 按钮元素。\n */\nfunction createPageToolbarBtn(\n ownerDocument: Document,\n ariaLabel: string,\n svgContent: string\n): HTMLButtonElement {\n const btn = ownerDocument.createElement(\"button\");\n btn.type = \"button\";\n btn.className = SCRIBDOWN_TOOLBAR_BTN_CLASS_NAME;\n btn.setAttribute(\"aria-label\", ariaLabel);\n btn.innerHTML = svgContent;\n return btn;\n}\n\n/**\n * 工具栏目录按钮的内嵌 SVG 字符串。\n */\nconst TOOLBAR_TOC_ICON_SVG =\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">' +\n '<line x1=\"3\" y1=\"5\" x2=\"15\" y2=\"5\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>' +\n '<line x1=\"3\" y1=\"9\" x2=\"11\" y2=\"9\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>' +\n '<line x1=\"3\" y1=\"13\" x2=\"13\" y2=\"13\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>' +\n \"</svg>\";\n\n/**\n * 工具栏「更多」按钮的内嵌 SVG 字符串(三个横向点)。\n */\nconst TOOLBAR_MORE_ICON_SVG =\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">' +\n '<circle cx=\"4\" cy=\"9\" r=\"1.4\" fill=\"currentColor\"/>' +\n '<circle cx=\"9\" cy=\"9\" r=\"1.4\" fill=\"currentColor\"/>' +\n '<circle cx=\"14\" cy=\"9\" r=\"1.4\" fill=\"currentColor\"/>' +\n \"</svg>\";\n\n/**\n * 「回到顶部」菜单项的内嵌 SVG 字符串。\n */\nconst TOOLBAR_BACK_TOP_ICON_SVG =\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">' +\n '<path d=\"M9 14V5M9 5L5 9M9 5l4 4\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>' +\n '<line x1=\"4\" y1=\"3.5\" x2=\"14\" y2=\"3.5\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>' +\n \"</svg>\";\n\n/**\n * 「切换页面宽度」菜单项的内嵌 SVG 字符串。\n */\nconst TOOLBAR_WIDTH_ICON_SVG =\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">' +\n '<path d=\"M3 6v6M15 6v6\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>' +\n '<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\"/>' +\n \"</svg>\";\n\n/**\n * 「关于」菜单项的内嵌 SVG 字符串(信息图标)。\n */\nconst TOOLBAR_ABOUT_ICON_SVG =\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">' +\n '<circle cx=\"9\" cy=\"9\" r=\"6.5\" stroke=\"currentColor\" stroke-width=\"1.6\"/>' +\n '<path d=\"M9 8v4\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>' +\n '<circle cx=\"9\" cy=\"5.6\" r=\"0.9\" fill=\"currentColor\"/>' +\n \"</svg>\";\n\n/**\n * 每个 container 上当前「当前章节指示器」scrollspy 的清理函数。\n * 重复挂载工具栏前先调用,断开旧的 scroll/resize 监听,避免监听堆积与对已卸载 DOM 的写入。\n */\nconst pageToolbarScrollspyTeardowns = new WeakMap<Element, () => void>();\n\n/**\n * 在指定容器内构造并挂载浮动工具栏:横向「目录」+「更多」两个入口。\n * 「更多」下拉菜单承载回到顶部、切换页面宽度等次要功能。\n * 重复调用时先清理 container 内的旧实例,保证每次 hydrate 只有一个工具栏。\n * 浏览器环境检查由对外的 {@link mountMarkdownToolbar} 完成。\n * @param ownerDocument 目标 document。\n * @param container 工具栏与目录抽屉的物理挂载点,同时作为目录采集作用域。\n * @param scrollToHeading 目录跳转到目标标题的滚动实现(由宿主注入)。\n */\nfunction mountPageToolbar(\n ownerDocument: Document,\n container: Element,\n scrollToHeading: (targetElement: HTMLElement) => void\n): void {\n // 关键步骤:重挂载前先断开上一轮 scrollspy 的窗口监听,避免重复绑定。\n pageToolbarScrollspyTeardowns.get(container)?.();\n pageToolbarScrollspyTeardowns.delete(container);\n\n // 移除 container 作用域内的旧实例,避免重渲染后重复挂载。\n container.querySelector(`:scope > .${SCRIBDOWN_TOOLBAR_CLASS_NAME}`)?.remove();\n container.querySelector(`:scope > .${SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME}`)?.remove();\n\n // 关键步骤:把 container 已有内容包进 .scribdown-content-area > .scribdown-content-scroll,\n // 形成统一布局:container 是 flex 容器(TOC + 正文横向并列),content-area flex:1 占满剩余宽度,\n // 其内部的 content-scroll 以 absolute+inset:0 铺满并承载横向 / 纵向滚动(外层不滚动)。\n // 幂等:复用已存在的 wrapper / 滚动层,避免重复包装。\n container.classList.add(SCRIBDOWN_TOC_HOST_CLASS_NAME);\n\n /** 正文列容器(flex item,占满 TOC 之外的剩余宽度,作为滚动层的定位上下文)。 */\n let contentArea = container.querySelector(\n `:scope > .${SCRIBDOWN_CONTENT_AREA_CLASS_NAME}`\n ) as HTMLElement | null;\n if (!contentArea) {\n contentArea = ownerDocument.createElement(\"div\");\n contentArea.className = SCRIBDOWN_CONTENT_AREA_CLASS_NAME;\n container.appendChild(contentArea);\n }\n\n /** 正文内部滚动层(absolute+inset:0 铺满 content-area,正文实际在此滚动)。 */\n let contentScroll = contentArea.querySelector(\n `:scope > .${SCRIBDOWN_CONTENT_SCROLL_CLASS_NAME}`\n ) as HTMLElement | null;\n if (!contentScroll) {\n contentScroll = ownerDocument.createElement(\"div\");\n // 关键步骤:正文滚动层与目录侧栏共用同一套细滚动条样式,显式 opt-in 引入。\n contentScroll.className = `${SCRIBDOWN_CONTENT_SCROLL_CLASS_NAME} ${SCRIBDOWN_THIN_SCROLLBAR_CLASS_NAME}`;\n contentArea.appendChild(contentScroll);\n }\n\n // 关键步骤:把散落在 container / content-area 下的「裸正文内容」(剔除工具栏 / 侧栏 / 两层 wrapper 自身)\n // 全部归集进滚动层;首次挂载时正文原本直接挂在 container 下,需移入,幂等情况下已在层内不会重复搬动。\n const contentScrollElement = contentScroll;\n const contentAreaElement = contentArea;\n [...Array.from(container.children), ...Array.from(contentArea.children)].forEach((child) => {\n if (\n child === contentAreaElement ||\n child === contentScrollElement ||\n child.classList.contains(SCRIBDOWN_TOOLBAR_CLASS_NAME) ||\n child.classList.contains(SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME)\n ) {\n return;\n }\n contentScrollElement.appendChild(child);\n });\n\n const ownerWindow = ownerDocument.defaultView;\n\n /** 工具栏容器(横向布局,仅承载「目录」「更多」两个入口)。 */\n const toolbar = ownerDocument.createElement(\"div\");\n toolbar.className = SCRIBDOWN_TOOLBAR_CLASS_NAME;\n\n // ── 目录抽屉(按钮回调需要引用,先建好)──\n /** 目录浮动面板。 */\n const tocPanel = ownerDocument.createElement(\"div\");\n // 关键步骤:目录侧栏与正文滚动层共用同一套细滚动条样式,显式 opt-in 引入。\n tocPanel.className = `${SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME} ${SCRIBDOWN_THIN_SCROLLBAR_CLASS_NAME}`;\n\n const tocTitle = ownerDocument.createElement(\"div\");\n tocTitle.className = \"scribdown-toolbar-toc-panel-title\";\n\n /** 抽屉顶部标题文本节点。 */\n const tocTitleText = ownerDocument.createElement(\"span\");\n tocTitleText.textContent = \"目录\";\n tocTitle.appendChild(tocTitleText);\n\n /** 抽屉关闭按钮(右上角 ×)。 */\n const tocCloseBtn = ownerDocument.createElement(\"button\");\n tocCloseBtn.type = \"button\";\n tocCloseBtn.className = \"scribdown-toolbar-toc-panel-close\";\n tocCloseBtn.setAttribute(\"aria-label\", \"关闭目录\");\n tocCloseBtn.innerHTML =\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">' +\n '<path d=\"M4 4l8 8M12 4l-8 8\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>' +\n \"</svg>\";\n /**\n * 切换目录侧栏的展开态:margin-left 由 CSS 配合 `.is-open` 在 flex 流中切换收起/展开。\n * @param open 是否打开侧栏。\n */\n const setTocOpen = (open: boolean): void => {\n tocPanel.classList.toggle(\"is-open\", open);\n };\n\n tocCloseBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n setTocOpen(false);\n });\n tocTitle.appendChild(tocCloseBtn);\n\n tocPanel.appendChild(tocTitle);\n\n // 关键步骤:与 inline [TOC] 共用层级数据 + DOM 结构,确保两处目录的层级渲染逻辑一致。\n const tocHeadings = collectTocHeadingsFromDom(container);\n if (tocHeadings.length === 0) {\n const emptyElement = ownerDocument.createElement(\"p\");\n emptyElement.className = \"scribdown-toolbar-toc-panel-empty\";\n emptyElement.textContent = \"暂无标题\";\n tocPanel.appendChild(emptyElement);\n } else {\n const tocTree = createTocTree(tocHeadings);\n // 关键步骤:与 inline [TOC] 共用同一个 createTocNavElement —— 叶子/分支同元素、同跳转。\n // 标题为原生 <a href=\"#id\">;折叠按钮与标题跳转随后由 hydrateToc 绑定,滚动用注入实现。\n const tocNavElement = createTocNavElement(ownerDocument, tocTree);\n tocPanel.appendChild(tocNavElement);\n hydrateToc(tocNavElement, scrollToHeading);\n }\n\n // ── 目录按钮(工具栏第 1 个入口) ──\n /** 目录切换按钮。 */\n const tocBtn = createPageToolbarBtn(ownerDocument, \"目录\", TOOLBAR_TOC_ICON_SVG);\n\n // ── 更多按钮(工具栏第 2 个入口) ──\n /** 「更多」按钮,点击展开下拉菜单。 */\n const moreBtn = createPageToolbarBtn(ownerDocument, \"更多\", TOOLBAR_MORE_ICON_SVG);\n moreBtn.classList.add(`${SCRIBDOWN_TOOLBAR_BTN_CLASS_NAME}--more`);\n moreBtn.setAttribute(\"aria-haspopup\", \"menu\");\n moreBtn.setAttribute(\"aria-expanded\", \"false\");\n\n // ── 更多下拉菜单 ──\n /** 「更多」下拉菜单容器。 */\n const menu = ownerDocument.createElement(\"div\");\n menu.className = SCRIBDOWN_TOOLBAR_MENU_CLASS_NAME;\n menu.setAttribute(\"role\", \"menu\");\n\n // 菜单项:切换页面宽度(下拉选择)\n // 关键步骤:宽度初始 index 取自 localStorage,兜底到默认宽度档位。\n let widthIndex = TOOLBAR_WIDTH_PRESETS.findIndex((p) => p.value === loadContentWidth());\n if (widthIndex === -1) {\n widthIndex = TOOLBAR_WIDTH_PRESETS.findIndex((p) => p.value === TOOLBAR_DEFAULT_WIDTH);\n }\n\n /** 宽度切换分组容器(包裹触发行与可折叠的档位列表)。 */\n const widthGroup = ownerDocument.createElement(\"div\");\n widthGroup.className = SCRIBDOWN_TOOLBAR_MENU_GROUP_CLASS_NAME;\n\n /** 宽度下拉触发行:左侧 icon + 文字、右侧当前档位值 + chevron。 */\n const widthTrigger = ownerDocument.createElement(\"button\");\n widthTrigger.type = \"button\";\n widthTrigger.className = `${SCRIBDOWN_TOOLBAR_MENU_ITEM_CLASS_NAME} ${SCRIBDOWN_TOOLBAR_MENU_ITEM_CLASS_NAME}--select`;\n widthTrigger.setAttribute(\"role\", \"menuitem\");\n widthTrigger.setAttribute(\"aria-haspopup\", \"listbox\");\n widthTrigger.setAttribute(\"aria-expanded\", \"false\");\n\n /** 触发行内当前档位值展示节点,切换档位时同步更新文本。 */\n const widthValueElement = ownerDocument.createElement(\"span\");\n widthValueElement.className = \"scribdown-toolbar-menu-select-value\";\n widthValueElement.textContent = TOOLBAR_WIDTH_PRESETS[widthIndex].label;\n\n widthTrigger.innerHTML = `${TOOLBAR_WIDTH_ICON_SVG}<span class=\"scribdown-toolbar-menu-select-label\">页面宽度</span>`;\n widthTrigger.appendChild(widthValueElement);\n widthTrigger.insertAdjacentHTML(\n \"beforeend\",\n '<svg class=\"scribdown-toolbar-menu-select-chevron\" width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" aria-hidden=\"true\">' +\n '<path d=\"M3 4.5l3 3 3-3\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>' +\n \"</svg>\"\n );\n\n widthGroup.appendChild(widthTrigger);\n\n /** 宽度档位列表(垂直 listbox,默认折叠,is-open 时展开)。 */\n const widthChoices = ownerDocument.createElement(\"div\");\n widthChoices.className = \"scribdown-toolbar-menu-group-choices\";\n widthChoices.setAttribute(\"role\", \"listbox\");\n\n /** 已渲染的宽度子项 DOM 列表,用于切换 aria-selected。 */\n const widthSubItems: HTMLButtonElement[] = [];\n TOOLBAR_WIDTH_PRESETS.forEach((preset, idx) => {\n /** 单个宽度档位选项按钮。 */\n const subItem = ownerDocument.createElement(\"button\");\n subItem.type = \"button\";\n subItem.className = SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_CLASS_NAME;\n subItem.setAttribute(\"role\", \"option\");\n subItem.setAttribute(\"aria-selected\", String(idx === widthIndex));\n // 关键步骤:左侧勾选位预留固定宽度的占位,未选中时不可见,避免标签左右抖动。\n subItem.innerHTML =\n '<span class=\"scribdown-toolbar-menu-sub-item-mark\" aria-hidden=\"true\">' +\n '<svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">' +\n '<path d=\"M2.5 6.5l2.5 2.5 4.5-5\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>' +\n \"</svg>\" +\n `</span><span class=\"scribdown-toolbar-menu-sub-item-label\">${preset.label}</span>`;\n subItem.addEventListener(\"click\", () => {\n widthIndex = idx;\n applyContentWidth(ownerDocument, preset.value);\n saveContentWidth(preset.value);\n widthValueElement.textContent = preset.label;\n widthSubItems.forEach((item, i) => {\n item.setAttribute(\"aria-selected\", String(i === idx));\n });\n // 选完后收起下拉,更多菜单整体保持开启便于继续操作其他项。\n closeWidthSelect();\n });\n widthChoices.appendChild(subItem);\n widthSubItems.push(subItem);\n });\n widthGroup.appendChild(widthChoices);\n\n /** 关闭宽度下拉并同步 aria 状态。 */\n const closeWidthSelect = (): void => {\n widthGroup.classList.remove(\"is-open\");\n widthTrigger.setAttribute(\"aria-expanded\", \"false\");\n };\n\n widthTrigger.addEventListener(\"click\", () => {\n const opened = widthGroup.classList.toggle(\"is-open\");\n widthTrigger.setAttribute(\"aria-expanded\", String(opened));\n });\n\n /** 关闭「更多」菜单并同步 aria 状态,同时重置嵌套的宽度下拉。 */\n const closeMoreMenu = (): void => {\n menu.classList.remove(\"is-open\");\n moreBtn.setAttribute(\"aria-expanded\", \"false\");\n closeWidthSelect();\n };\n\n // 菜单项:回到顶部(声明顺序晚于 closeMoreMenu,便于在点击回调里关闭整个菜单)。\n /** 「回到顶部」菜单项。 */\n const backTopItem = ownerDocument.createElement(\"button\");\n backTopItem.type = \"button\";\n backTopItem.className = SCRIBDOWN_TOOLBAR_MENU_ITEM_CLASS_NAME;\n backTopItem.setAttribute(\"role\", \"menuitem\");\n backTopItem.innerHTML = `${TOOLBAR_BACK_TOP_ICON_SVG}<span>回到顶部</span>`;\n backTopItem.addEventListener(\"click\", () => {\n // 关键步骤:正文滚动容器是 .scribdown-content-scroll,优先滚动它回到顶部;\n // 同时兜底滚动 window,兼容仍以整页滚动的宿主(无害的空操作)。\n contentScroll?.scrollTo({ top: 0, behavior: \"smooth\" });\n ownerWindow?.scrollTo({ top: 0, behavior: \"smooth\" });\n closeMoreMenu();\n });\n\n // 菜单项:关于(点击在新标签页打开项目 GitHub Pages 主页)。\n /** 「关于」菜单项。 */\n const aboutItem = ownerDocument.createElement(\"button\");\n aboutItem.type = \"button\";\n aboutItem.className = SCRIBDOWN_TOOLBAR_MENU_ITEM_CLASS_NAME;\n aboutItem.setAttribute(\"role\", \"menuitem\");\n aboutItem.innerHTML = `${TOOLBAR_ABOUT_ICON_SVG}<span>关于</span>`;\n aboutItem.addEventListener(\"click\", () => {\n // 关键步骤:新标签页打开项目主页,noopener/noreferrer 阻断被打开页对本页的引用。\n ownerWindow?.open(PROJECT_HOMEPAGE_URL, \"_blank\", \"noopener,noreferrer\");\n closeMoreMenu();\n });\n\n // 关键步骤:菜单项按可见顺序追加 —— 回到顶部 → 切换页面宽度(下拉)→ 关于。\n menu.appendChild(backTopItem);\n menu.appendChild(widthGroup);\n menu.appendChild(aboutItem);\n\n tocBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n /** 切换前 tocPanel 是否打开。 */\n const wasOpen = tocPanel.classList.contains(\"is-open\");\n setTocOpen(!wasOpen);\n // 打开目录侧栏时同时收起更多菜单,避免视觉重叠。\n if (!wasOpen) {\n closeMoreMenu();\n }\n });\n\n moreBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const opened = menu.classList.toggle(\"is-open\");\n moreBtn.setAttribute(\"aria-expanded\", String(opened));\n });\n\n // 点击「更多」菜单外部时收起菜单;目录侧栏不参与外部点击关闭,\n // 仅由目录按钮再次点击或侧栏顶部 × 主动关闭,避免阅读时误触收起。\n ownerDocument.addEventListener(\"click\", (e) => {\n const target = e.target as Node;\n if (!menu.contains(target) && target !== moreBtn) {\n closeMoreMenu();\n }\n });\n\n // ── 当前章节指示器(工具栏最左侧)──\n // 关键步骤:收集正文标题做 scrollspy,随滚动展示「当前可视区」所属标题;\n // 窄条最宽 30px、超出省略号,hover 时由 ::after 读取 aria-label 显示全文。\n /** 正文 Markdown 容器,作为标题采集作用域。 */\n const markdownContainer = container.matches(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME}`)\n ? (container as HTMLElement)\n : container.querySelector<HTMLElement>(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME}`);\n /** 正文内全部带 id 的标题元素,按文档顺序排列,用于判定当前章节。 */\n const headingElements = markdownContainer\n ? Array.from(\n markdownContainer.querySelectorAll<HTMLHeadingElement>(\n \"h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]\"\n )\n )\n : [];\n\n if (headingElements.length > 0) {\n /** 判定线:标题 top 越过工具栏下沿(距视口顶 48px)即视为已进入当前章节。 */\n const ACTIVE_HEADING_OFFSET = 48;\n\n /** 指示器容器(position:relative 承载 hover tip),点击跳转到当前章节标题。 */\n const currentHeading = ownerDocument.createElement(\"div\");\n currentHeading.className = SCRIBDOWN_TOOLBAR_CURRENT_CLASS_NAME;\n // 关键步骤:作为可点击元素暴露给辅助技术,并支持键盘聚焦 / 触发。\n currentHeading.setAttribute(\"role\", \"button\");\n currentHeading.tabIndex = 0;\n\n /** 指示器可见文本节点(max-width 30px,超出省略号)。 */\n const currentHeadingText = ownerDocument.createElement(\"span\");\n currentHeadingText.className = \"scribdown-toolbar-current-text\";\n currentHeading.appendChild(currentHeadingText);\n\n /** 上一次写入的标题文本,相同则跳过 DOM 写入。 */\n let lastHeadingText = \"\";\n /** 当前命中的标题元素,供点击跳转使用。 */\n let activeHeadingElement: HTMLHeadingElement = headingElements[0];\n\n // ── 目录侧栏「当前章节」高亮(复用同一份 scrollspy 结果)──\n // 关键步骤:建立「正文标题元素 → 目录链接」映射,使 scrollspy 命中标题后可反查并高亮对应目录项。\n // 链接目标 id 解析与 hydrateToc 一致:优先原文匹配,失败再解码兜底(兼容被编码的中文 id)。\n /** 正文标题元素 → 目录侧栏对应跳转链接的映射。 */\n const tocLinkByHeading = new Map<HTMLHeadingElement, HTMLAnchorElement>();\n tocPanel\n .querySelectorAll<HTMLAnchorElement>(`a.${TOC_LINK_CLASS_NAME}`)\n .forEach((linkElement) => {\n /** 链接目标标题 id(去掉前导 #)。 */\n const rawTargetId = (linkElement.getAttribute(\"href\") ?? \"\").slice(1);\n if (!rawTargetId) {\n return;\n }\n /** 链接指向的标题元素,按原文匹配失败时解码兜底。 */\n let targetHeading = ownerDocument.getElementById(rawTargetId);\n if (!targetHeading) {\n try {\n targetHeading = ownerDocument.getElementById(decodeURIComponent(rawTargetId));\n } catch {\n targetHeading = null;\n }\n }\n if (targetHeading instanceof HTMLHeadingElement) {\n tocLinkByHeading.set(targetHeading, linkElement);\n }\n });\n\n /** 当前高亮的目录链接;undefined 表示尚未高亮任何项。 */\n let activeTocLink: HTMLAnchorElement | undefined;\n\n /**\n * 把目录侧栏「当前章节」高亮切换到指定标题对应的链接上。\n * 按链接元素去重,未变化时直接返回,避免每帧重复读写 DOM。\n * @param headingElement 当前命中的正文标题元素。\n */\n const setActiveTocLink = (headingElement: HTMLHeadingElement): void => {\n /** 命中标题对应的目录链接,无对应项时为 undefined。 */\n const nextLink = tocLinkByHeading.get(headingElement);\n if (nextLink === activeTocLink) {\n return;\n }\n if (activeTocLink) {\n activeTocLink.classList.remove(TOC_LINK_ACTIVE_CLASS_NAME);\n activeTocLink.removeAttribute(\"aria-current\");\n }\n if (nextLink) {\n nextLink.classList.add(TOC_LINK_ACTIVE_CLASS_NAME);\n // 关键步骤:aria-current 把「当前所在位置」语义同步给读屏,与高亮类保持一致。\n nextLink.setAttribute(\"aria-current\", \"location\");\n }\n activeTocLink = nextLink;\n };\n\n /**\n * 计算当前可视区所属标题并刷新指示器文本 + tip。\n * 取文档顺序中最后一个 top ≤ 判定线的标题;都未越线时回退到首个标题。\n */\n const updateCurrentHeading = (): void => {\n /** 命中的当前标题元素,默认首个标题。 */\n let activeElement: HTMLHeadingElement = headingElements[0];\n for (const headingElement of headingElements) {\n if (headingElement.getBoundingClientRect().top <= ACTIVE_HEADING_OFFSET) {\n activeElement = headingElement;\n } else {\n break;\n }\n }\n // 关键步骤:始终同步当前标题引用(即便文本未变),保证点击跳转目标准确。\n activeHeadingElement = activeElement;\n // 关键步骤:同步目录侧栏高亮到当前章节;按元素去重,独立于下方的文本去重短路。\n setActiveTocLink(activeElement);\n /** 当前标题可见文本。 */\n const headingText = activeElement.textContent?.trim() ?? \"\";\n if (headingText === lastHeadingText) {\n return;\n }\n lastHeadingText = headingText;\n currentHeadingText.textContent = headingText;\n // 关键步骤:全文写入 aria-label,hover 时由 ::after 读取展示完整标题。\n currentHeading.setAttribute(\"aria-label\", headingText);\n };\n\n /** 点击 / 回车:平滑滚动到当前章节标题,与「回到顶部」一致采用 smooth 行为。 */\n const jumpToActiveHeading = (): void => {\n activeHeadingElement.scrollIntoView({ behavior: \"smooth\", block: \"start\" });\n };\n currentHeading.addEventListener(\"click\", jumpToActiveHeading);\n currentHeading.addEventListener(\"keydown\", (event) => {\n // 关键步骤:role=button 需自行处理 Enter / Space 触发。\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n jumpToActiveHeading();\n }\n });\n\n /** rAF 节流句柄,避免滚动时每个事件都同步读取 rect。 */\n let scrollRafId = 0;\n /** 滚动/缩放回调:在一帧内合并一次计算。 */\n const onScrollOrResize = (): void => {\n if (scrollRafId) {\n return;\n }\n scrollRafId =\n ownerWindow?.requestAnimationFrame(() => {\n scrollRafId = 0;\n updateCurrentHeading();\n }) ?? 0;\n };\n\n // 关键步骤:正文已改为在 .scribdown-content-area 内部滚动(整页 window 不再滚动),\n // scroll 事件不冒泡,故用「捕获阶段」在 window 上监听,可同时捕获到内部滚动容器派发的 scroll。\n ownerWindow?.addEventListener(\"scroll\", onScrollOrResize, {\n passive: true,\n capture: true\n });\n ownerWindow?.addEventListener(\"resize\", onScrollOrResize, { passive: true });\n // 首次同步一次当前章节。\n updateCurrentHeading();\n\n // 关键步骤:登记清理函数,下次重挂载工具栏前断开窗口监听(capture 标志需与注册时一致)。\n pageToolbarScrollspyTeardowns.set(container, () => {\n if (scrollRafId) {\n ownerWindow?.cancelAnimationFrame(scrollRafId);\n }\n ownerWindow?.removeEventListener(\"scroll\", onScrollOrResize, { capture: true });\n ownerWindow?.removeEventListener(\"resize\", onScrollOrResize);\n });\n\n // 指示器置于工具栏最左侧(目录按钮之前)。\n toolbar.appendChild(currentHeading);\n }\n\n toolbar.appendChild(tocBtn);\n toolbar.appendChild(moreBtn);\n // 关键步骤:菜单挂在工具栏内部,便于以工具栏为锚点做绝对定位。\n toolbar.appendChild(menu);\n\n // 关键步骤:toolbar 仍为 fixed 浮动元素,append 到 container 末尾;\n // tocPanel 是 flex item,必须插在 contentArea 之前才能视觉上位于正文左侧。\n container.appendChild(toolbar);\n container.insertBefore(tocPanel, contentArea);\n}\n","import type { LanguageRegistration } from \"shiki/core\";\n\n/**\n * 单个 grammar 模块的默认导出形态。\n * `@shikijs/langs/<id>` 默认导出形如 `LanguageRegistration[]`。\n */\ntype GrammarModule = { default: LanguageRegistration[] };\n\n/**\n * 懒加载注册表中的语言导入函数。\n * 调用时返回 grammar 模块,再交由 highlighter.loadLanguage 注册。\n */\ntype GrammarLoader = () => Promise<GrammarModule>;\n\n/**\n * 高亮器初始化时即刻拉取的语言(覆盖 .md 文档中最高频的代码块语种)。\n * 使用动态 import 而非 静态 import:grammar JSON 仍作为独立 chunk 异步加载,\n * 不会内联进主 bundle,但 createHighlighterCore 会并发 await 全部,渲染前已就绪。\n */\nexport const CODE_HIGHLIGHTER_EAGER_LOADERS: GrammarLoader[] = [\n () => import(\"@shikijs/langs/bash\"),\n () => import(\"@shikijs/langs/css\"),\n () => import(\"@shikijs/langs/diff\"),\n () => import(\"@shikijs/langs/html\"),\n () => import(\"@shikijs/langs/javascript\"),\n () => import(\"@shikijs/langs/json\"),\n () => import(\"@shikijs/langs/markdown\"),\n () => import(\"@shikijs/langs/typescript\"),\n () => import(\"@shikijs/langs/yaml\")\n];\n\n/**\n * 懒加载语言注册表:id 归一化(小写)→ 动态 import 函数。\n * 仅列出此处的语言会被 Vite 打成异步 chunk;其他语言会回退为纯文本。\n * 同一 grammar 的多个别名指向同一 import,避免重复 chunk。\n * 已在 eager 列表中的语言不需要在此重复(其 grammar 自带的 aliases 会在 loadLanguage 时一并注册)。\n */\nexport const CODE_HIGHLIGHTER_LAZY_LANGS: Record<string, GrammarLoader> = {\n // 前端框架与扩展\n jsx: () => import(\"@shikijs/langs/jsx\"),\n tsx: () => import(\"@shikijs/langs/tsx\"),\n vue: () => import(\"@shikijs/langs/vue\"),\n svelte: () => import(\"@shikijs/langs/svelte\"),\n astro: () => import(\"@shikijs/langs/astro\"),\n mdx: () => import(\"@shikijs/langs/mdx\"),\n // 样式\n scss: () => import(\"@shikijs/langs/scss\"),\n sass: () => import(\"@shikijs/langs/sass\"),\n less: () => import(\"@shikijs/langs/less\"),\n postcss: () => import(\"@shikijs/langs/postcss\"),\n stylus: () => import(\"@shikijs/langs/stylus\"),\n // 查询 / 数据 / 标记\n graphql: () => import(\"@shikijs/langs/graphql\"),\n gql: () => import(\"@shikijs/langs/graphql\"),\n sql: () => import(\"@shikijs/langs/sql\"),\n xml: () => import(\"@shikijs/langs/xml\"),\n regexp: () => import(\"@shikijs/langs/regexp\"),\n regex: () => import(\"@shikijs/langs/regexp\"),\n // 系统语言\n c: () => import(\"@shikijs/langs/c\"),\n cpp: () => import(\"@shikijs/langs/cpp\"),\n \"c++\": () => import(\"@shikijs/langs/cpp\"),\n csharp: () => import(\"@shikijs/langs/csharp\"),\n cs: () => import(\"@shikijs/langs/csharp\"),\n java: () => import(\"@shikijs/langs/java\"),\n go: () => import(\"@shikijs/langs/go\"),\n rust: () => import(\"@shikijs/langs/rust\"),\n rs: () => import(\"@shikijs/langs/rust\"),\n swift: () => import(\"@shikijs/langs/swift\"),\n kotlin: () => import(\"@shikijs/langs/kotlin\"),\n kt: () => import(\"@shikijs/langs/kotlin\"),\n scala: () => import(\"@shikijs/langs/scala\"),\n \"objective-c\": () => import(\"@shikijs/langs/objective-c\"),\n objc: () => import(\"@shikijs/langs/objective-c\"),\n \"objective-cpp\": () => import(\"@shikijs/langs/objective-cpp\"),\n // 脚本语言\n python: () => import(\"@shikijs/langs/python\"),\n py: () => import(\"@shikijs/langs/python\"),\n ruby: () => import(\"@shikijs/langs/ruby\"),\n rb: () => import(\"@shikijs/langs/ruby\"),\n perl: () => import(\"@shikijs/langs/perl\"),\n lua: () => import(\"@shikijs/langs/lua\"),\n powershell: () => import(\"@shikijs/langs/powershell\"),\n ps: () => import(\"@shikijs/langs/powershell\"),\n ps1: () => import(\"@shikijs/langs/powershell\"),\n fish: () => import(\"@shikijs/langs/fish\"),\n php: () => import(\"@shikijs/langs/php\"),\n r: () => import(\"@shikijs/langs/r\"),\n // 配置 / 数据\n toml: () => import(\"@shikijs/langs/toml\"),\n ini: () => import(\"@shikijs/langs/ini\"),\n dockerfile: () => import(\"@shikijs/langs/dockerfile\"),\n docker: () => import(\"@shikijs/langs/dockerfile\"),\n makefile: () => import(\"@shikijs/langs/make\"),\n make: () => import(\"@shikijs/langs/make\"),\n cmake: () => import(\"@shikijs/langs/cmake\"),\n nginx: () => import(\"@shikijs/langs/nginx\"),\n properties: () => import(\"@shikijs/langs/properties\"),\n dotenv: () => import(\"@shikijs/langs/dotenv\"),\n hcl: () => import(\"@shikijs/langs/hcl\"),\n terraform: () => import(\"@shikijs/langs/terraform\"),\n tf: () => import(\"@shikijs/langs/terraform\"),\n nix: () => import(\"@shikijs/langs/nix\"),\n json5: () => import(\"@shikijs/langs/json5\"),\n jsonc: () => import(\"@shikijs/langs/jsonc\"),\n csv: () => import(\"@shikijs/langs/csv\"),\n tsv: () => import(\"@shikijs/langs/tsv\"),\n // 函数式 / 其他\n dart: () => import(\"@shikijs/langs/dart\"),\n elixir: () => import(\"@shikijs/langs/elixir\"),\n erlang: () => import(\"@shikijs/langs/erlang\"),\n haskell: () => import(\"@shikijs/langs/haskell\"),\n hs: () => import(\"@shikijs/langs/haskell\"),\n clojure: () => import(\"@shikijs/langs/clojure\"),\n clj: () => import(\"@shikijs/langs/clojure\"),\n ocaml: () => import(\"@shikijs/langs/ocaml\"),\n fsharp: () => import(\"@shikijs/langs/fsharp\"),\n vb: () => import(\"@shikijs/langs/vb\"),\n asm: () => import(\"@shikijs/langs/asm\"),\n llvm: () => import(\"@shikijs/langs/llvm\"),\n solidity: () => import(\"@shikijs/langs/solidity\"),\n prisma: () => import(\"@shikijs/langs/prisma\"),\n proto: () => import(\"@shikijs/langs/proto\"),\n // 文档标记\n latex: () => import(\"@shikijs/langs/latex\"),\n tex: () => import(\"@shikijs/langs/latex\"),\n asciidoc: () => import(\"@shikijs/langs/asciidoc\"),\n adoc: () => import(\"@shikijs/langs/asciidoc\"),\n // 着色器 / 二进制\n glsl: () => import(\"@shikijs/langs/glsl\"),\n wasm: () => import(\"@shikijs/langs/wasm\"),\n wat: () => import(\"@shikijs/langs/wasm\")\n};\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/code/code-block-chrome.ts","../src/code/code-highlight.ts","../src/code/code-highlighter-langs.ts","../src/code/mermaid.ts","../src/code/mermaid-viewer.ts","../src/core/viewer-shared.ts","../src/core/source-line.ts","../src/media/images.ts","../src/core/ast.ts","../src/media/image-viewer.ts","../src/media/videos.ts","../src/sanitize.ts","../src/syntax/definition-lists.ts","../src/syntax/frame.ts","../src/syntax/frontmatter.ts","../src/syntax/highlight-mark.ts","../src/syntax/toc.ts","../src/toolbar.ts"],"sourcesContent":["/**\n * @scribdown/markdown-renderer 的公共入口:\n * renderMarkdown(Markdown → 安全 HTML)、hydrateMarkdown(DOM 交互 hydration)\n * 与 mountMarkdownToolbar(浮动工具栏挂载)。\n */\n\nimport rehypeRaw from \"rehype-raw\";\nimport rehypeSanitize from \"rehype-sanitize\";\nimport rehypeStringify from \"rehype-stringify\";\nimport remarkFrontmatter from \"remark-frontmatter\";\nimport remarkGfm from \"remark-gfm\";\nimport remarkParse from \"remark-parse\";\nimport remarkRehype from \"remark-rehype\";\nimport { unified } from \"unified\";\n\nimport { hydrateCodeBlocks } from \"./code/code-block-chrome\";\nimport { highlightMarkdownCodeBlocks } from \"./code/code-highlight\";\nimport { hydrateMermaidBlocks } from \"./code/mermaid\";\nimport { remarkSourceLine } from \"./core/source-line\";\nimport { hydrateMarkdownImages, remarkImageFigures } from \"./media/images\";\nimport { hydrateMarkdownVideos, rehypeVideoFigures } from \"./media/videos\";\nimport { createScribdownSanitizeSchema, sanitizeHtmlWithDomPurify } from \"./sanitize\";\nimport { remarkDefinitionLists } from \"./syntax/definition-lists\";\nimport { rehypeFrameClass } from \"./syntax/frame\";\nimport { remarkFrontmatterMetadata } from \"./syntax/frontmatter\";\nimport { remarkHighlightMark } from \"./syntax/highlight-mark\";\nimport { hydrateToc, remarkTableOfContents } from \"./syntax/toc\";\nimport { applyContentWidth, loadContentWidth, mountPageToolbar } from \"./toolbar\";\n\n/**\n * 将 Markdown 文本渲染为安全 HTML。\n * 渲染链路固定开启代码高亮与 HTML sanitize(rehype 结构清洗 + DOMPurify),\n * 库不再对外暴露这些细节开关,保证所有宿主拿到一致的预览输出。\n * @param markdownText 输入的 Markdown 文本。\n * @returns 可挂载到 DOM 容器的 HTML 字符串。\n */\nexport async function renderMarkdown(markdownText: string): Promise<string> {\n // 渲染流水线:先解析 Markdown 与 GFM 行内标记,再转换为 HTML AST。\n // allowDangerousHtml + rehypeRaw 让 fixture 中的 <u> / <sub> / <sup> / <kbd> 等行内 HTML 保留下来。\n // 关键步骤:rehypeSanitize 在 stringify 前对结构做白名单清洗。\n const processor = unified()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkFrontmatter)\n .use(remarkFrontmatterMetadata)\n .use(remarkHighlightMark)\n .use(remarkDefinitionLists)\n .use(remarkTableOfContents)\n .use(remarkImageFigures)\n .use(remarkSourceLine)\n .use(remarkRehype, { allowDangerousHtml: true })\n .use(rehypeRaw)\n .use(rehypeFrameClass)\n .use(rehypeVideoFigures)\n .use(rehypeSanitize, createScribdownSanitizeSchema())\n .use(rehypeStringify);\n\n /** unified 渲染输出的 HTML 文本(已经过 rehype 结构清洗)。 */\n const renderedHtml = String(await processor.process(markdownText));\n\n // 关键步骤:rehype 清洗后再用 DOMPurify 做一次字符串级 sanitize,双重保险。\n const sanitizedHtml = sanitizeHtmlWithDomPurify(renderedHtml);\n\n // 关键步骤:sanitize 之后再做 Shiki 高亮,把 token span 添加到信任过滤后的代码体内。\n return highlightMarkdownCodeBlocks(sanitizedHtml);\n}\n\n/**\n * Markdown 交互 hydration 的可注入选项,用于让各宿主落地平台差异,\n * 渲染器自身保持跨端一致(仅调用注入的实现)。\n */\nexport interface MarkdownInteractionOptions {\n /**\n * 目录条目跳转到目标标题的滚动实现,由宿主注入以适配各端平滑滚动差异。\n * 缺省使用原生 `scrollIntoView({ behavior: \"smooth\" })`(标准浏览器即丝滑);\n * 若宿主原生平滑被降级(如 VS Code webview 对真实锚点点击会瞬时),\n * 可注入自定义实现(如手动 requestAnimationFrame 动画)。\n * @param targetElement 目标标题元素。\n */\n scrollToHeading?: (targetElement: HTMLElement) => void;\n}\n\n/**\n * 默认目录跳转滚动实现:原生平滑 scrollIntoView。\n * @param targetElement 目标标题元素。\n */\nfunction defaultScrollToHeading(targetElement: HTMLElement): void {\n targetElement.scrollIntoView({ behavior: \"smooth\", block: \"start\" });\n}\n\n/**\n * 对已渲染到 DOM 的 Markdown 内容执行交互 hydration。\n * 处理图片放大查看、视频占位、Mermaid、代码块复制等运行时行为。\n * 不挂载浮动工具栏;如需工具栏请额外调用 {@link mountMarkdownToolbar}。\n * @param rootElement 包含 Markdown 渲染结果的根节点。\n * @param options 可注入交互选项(如目录跳转滚动实现)。\n */\nexport function hydrateMarkdown(\n rootElement: ParentNode,\n options: MarkdownInteractionOptions = {}\n): void {\n hydrateMarkdownImages(rootElement);\n hydrateMarkdownVideos(rootElement);\n // 关键步骤:mermaid 必须先于代码块 hydrate,避免被通用 code chrome 包装。\n hydrateMermaidBlocks(rootElement);\n hydrateCodeBlocks(rootElement);\n // 关键步骤:为行内 [TOC] 绑定折叠与标题跳转;滚动实现由宿主注入,缺省原生平滑。\n hydrateToc(rootElement, options.scrollToHeading ?? defaultScrollToHeading);\n}\n\n/**\n * 在指定 DOM 容器上挂载 Scribdown 浮动工具栏(回到顶部 / 目录 / 页面宽度切换)。\n * 工具栏 DOM 会直接 append 到 `container` 内,便于宿主控制生命周期:\n * 移除 / 替换 container 即可一并卸载工具栏,不会污染 `<body>`。\n * 视觉上工具栏使用 `position: fixed`,位置始终相对视口,与挂载点的 CSS 上下文无关。\n *\n * 仅在浏览器环境生效;非浏览器环境(如 Node.js 单测)或 SSR 阶段直接跳过。\n * 重复调用会先清理 container 内的旧实例,可在每次 {@link hydrateMarkdown} 后安全重新挂载。\n * @param container 目标挂载容器;同时作为目录采集与点击外部关闭的作用域。\n * @param options 可注入交互选项(如目录跳转滚动实现)。\n */\nexport function mountMarkdownToolbar(\n container: Element,\n options: MarkdownInteractionOptions = {}\n): void {\n /** 容器所属的 document。 */\n const ownerDocument = container.ownerDocument;\n if (!ownerDocument) {\n return;\n }\n if (typeof ownerDocument.defaultView?.scrollTo !== \"function\") {\n return;\n }\n // 关键步骤:恢复上次保存的内容宽度,再挂载工具栏。\n applyContentWidth(ownerDocument, loadContentWidth());\n mountPageToolbar(ownerDocument, container, options.scrollToHeading ?? defaultScrollToHeading);\n}\n","/**\n * 代码块 chrome hydration:把渲染后的 pre 包装为带语言标签、\n * 复制按钮与固定行号列的 figure 结构。\n */\n\nimport {\n CODE_BLOCK_BODY_CLASS_NAME,\n CODE_BLOCK_CHROME_CLASS_NAME,\n CODE_BLOCK_CLASS_NAME,\n CODE_BLOCK_COPY_CLASS_NAME,\n CODE_BLOCK_COPY_ICON_CHECK_CLASS_NAME,\n CODE_BLOCK_COPY_ICON_CLASS_NAME,\n CODE_BLOCK_COPY_ICON_COPY_CLASS_NAME,\n CODE_BLOCK_GUTTER_CLASS_NAME,\n CODE_BLOCK_GUTTER_LINE_CLASS_NAME,\n CODE_BLOCK_LANG_CLASS_NAME,\n CODE_BLOCK_LINE_CLASS_NAME,\n SOURCE_LINE_DATA_ATTRIBUTE\n} from \"@scribdown/shared\";\n\n// 代码块空行占位文本,避免空 span 被浏览器完全折叠。\nconst CODE_BLOCK_EMPTY_LINE_PLACEHOLDER = \"\\u200b\";\n\n// 代码块运行时已绑定标记的 dataset 键。\nconst CODE_BLOCK_HYDRATED_DATA_KEY = \"scribdownCodeBlockHydrated\";\n\n// 代码语言标识 → 展示标签的映射。\nconst CODE_LANGUAGE_DISPLAY_NAMES: Record<string, string> = {\n bash: \"Bash\",\n c: \"C\",\n cpp: \"C++\",\n csharp: \"C#\",\n css: \"CSS\",\n diff: \"Diff\",\n go: \"Go\",\n graphql: \"GraphQL\",\n html: \"HTML\",\n java: \"Java\",\n javascript: \"JavaScript\",\n js: \"JavaScript\",\n json: \"JSON\",\n jsx: \"JSX\",\n kotlin: \"Kotlin\",\n less: \"Less\",\n markdown: \"Markdown\",\n md: \"Markdown\",\n php: \"PHP\",\n python: \"Python\",\n py: \"Python\",\n ruby: \"Ruby\",\n rust: \"Rust\",\n rs: \"Rust\",\n sass: \"Sass\",\n scss: \"SCSS\",\n sh: \"Shell\",\n shell: \"Shell\",\n sql: \"SQL\",\n swift: \"Swift\",\n toml: \"TOML\",\n ts: \"TypeScript\",\n tsx: \"TSX\",\n typescript: \"TypeScript\",\n vue: \"Vue\",\n xml: \"XML\",\n yaml: \"YAML\",\n yml: \"YAML\",\n zsh: \"Zsh\"\n};\n\n// 代码块语言标识缺省值。\nconst CODE_LANGUAGE_DEFAULT_LABEL = \"Text\";\n\n// 代码块复制按钮已复制状态恢复延迟,单位毫秒。\nconst CODE_BLOCK_COPY_RESTORE_DELAY_MS = 1600;\n\n// 代码块复制按钮默认可访问名称。\nconst CODE_BLOCK_COPY_ARIA_LABEL = \"复制代码\";\n\n// 代码块复制按钮已复制可访问名称。\nconst CODE_BLOCK_COPY_ARIA_LABEL_COPIED = \"已复制\";\n\n/**\n * 把渲染后的代码块包装为带语言标签、复制按钮与行号的 chrome 结构。\n * @param rootElement 包含 Markdown 渲染结果的根节点。\n */\nfunction hydrateCodeBlocks(rootElement: ParentNode): void {\n // 当前根节点内的所有未绑定 chrome 的 pre 元素。\n const preElements = rootElement.querySelectorAll<HTMLPreElement>(\"pre\");\n\n preElements.forEach(decorateCodeBlock);\n}\n\n/**\n * 给单个 pre 元素加上 chrome 结构与复制行为。\n * @param preElement 渲染产出的 pre 元素。\n */\nfunction decorateCodeBlock(preElement: HTMLPreElement): void {\n // 已经包裹过 figure 时直接跳过,避免重复 hydrate。\n if (preElement.parentElement?.classList.contains(CODE_BLOCK_CLASS_NAME)) {\n return;\n }\n\n if (preElement.dataset[CODE_BLOCK_HYDRATED_DATA_KEY] === \"true\") {\n return;\n }\n\n // pre 内部的 code 元素,无 code 子节点时认定为非代码块。\n const codeElement = preElement.querySelector<HTMLElement>(\":scope > code\");\n\n if (!codeElement) {\n return;\n }\n\n preElement.dataset[CODE_BLOCK_HYDRATED_DATA_KEY] = \"true\";\n\n // 当前 pre 所属的 document,用于创建子节点。\n const ownerDocument = preElement.ownerDocument;\n // 代码块语言标识。\n const languageId = resolveCodeBlockLanguageId(codeElement);\n // 语言展示标签。\n const languageLabel = resolveCodeBlockLanguageLabel(languageId);\n\n // 关键步骤:在替换 DOM 前抓取原始代码文本,便于复制按钮使用。\n const originalCodeText = codeElement.textContent ?? \"\";\n\n // 把代码内容按行拆分为带类名的 span,并得到总行数用于固定行号列。\n const codeLineCount = rewriteCodeBlockLines(codeElement, ownerDocument);\n\n // 顶部 chrome 区域,承载语言标签与复制按钮。\n const chromeElement = ownerDocument.createElement(\"div\");\n chromeElement.className = CODE_BLOCK_CHROME_CLASS_NAME;\n\n // 语言标签元素。\n const langElement = ownerDocument.createElement(\"span\");\n langElement.className = CODE_BLOCK_LANG_CLASS_NAME;\n langElement.textContent = languageLabel;\n chromeElement.append(langElement);\n\n // 复制按钮元素。\n const copyButtonElement = createCodeBlockCopyButton(ownerDocument);\n chromeElement.append(copyButtonElement);\n\n // 外层 figure 容器,让 chrome 与 pre 在同一手绘边框内。\n const figureElement = ownerDocument.createElement(\"figure\");\n figureElement.className = CODE_BLOCK_CLASS_NAME;\n figureElement.dataset.scribdownCodeLang = languageId;\n\n // 正文容器,用于独立绘制代码区分隔线和纸面。\n const bodyElement = ownerDocument.createElement(\"div\");\n bodyElement.className = CODE_BLOCK_BODY_CLASS_NAME;\n\n // 固定宽度行号列,作为 pre 的兄弟节点独立布局,不跟随代码横向滚动。\n const gutterElement = createCodeBlockLineNumberGutter(ownerDocument, codeLineCount);\n\n // 把 pre 替换为 figure,再把 chrome + body + 原 pre 放进 figure。\n preElement.replaceWith(figureElement);\n bodyElement.append(gutterElement, preElement);\n figureElement.append(chromeElement, bodyElement);\n\n // 关键步骤:把源码行锚点从内层 code 迁移到最外层 figure,\n // 使编辑器光标定位的高亮能覆盖整个代码块(含顶部 chrome)。\n // remark-rehype 把 code 节点的 data-source-line 落在 code 元素上,而非 pre。\n const codeBlockSourceLine = codeElement.getAttribute(SOURCE_LINE_DATA_ATTRIBUTE);\n\n if (codeBlockSourceLine !== null) {\n figureElement.setAttribute(SOURCE_LINE_DATA_ATTRIBUTE, codeBlockSourceLine);\n codeElement.removeAttribute(SOURCE_LINE_DATA_ATTRIBUTE);\n }\n\n // 在按钮上记录原始代码文本,复制时直接读取,无需再走 DOM。\n copyButtonElement.dataset.scribdownCodeSource = originalCodeText;\n copyButtonElement.addEventListener(\"click\", handleCodeBlockCopyClick);\n}\n\n/**\n * 解析 pre > code 节点的语言标识。\n * @param codeElement pre 内部的 code 元素。\n * @returns 语言标识,未识别时返回空字符串。\n */\nfunction resolveCodeBlockLanguageId(codeElement: HTMLElement): string {\n // class 列表中以 language- 开头的类名记录了语言标识。\n const languageClassName = Array.from(codeElement.classList).find((className) =>\n className.startsWith(\"language-\")\n );\n\n if (!languageClassName) {\n return \"\";\n }\n\n return languageClassName.slice(\"language-\".length).toLowerCase();\n}\n\n/**\n * 把语言标识转换为可读展示标签。\n * @param languageId 语言标识。\n * @returns 用于 chrome 的语言展示文本。\n */\nfunction resolveCodeBlockLanguageLabel(languageId: string): string {\n if (!languageId) {\n return CODE_LANGUAGE_DEFAULT_LABEL;\n }\n\n // 优先使用预定义映射,未命中时退化为首字母大写。\n const mappedLabel = CODE_LANGUAGE_DISPLAY_NAMES[languageId];\n\n if (mappedLabel) {\n return mappedLabel;\n }\n\n return languageId.charAt(0).toUpperCase() + languageId.slice(1);\n}\n\n/**\n * 把代码内容按行拆分为带类名的 span。\n * Shiki 已经输出 `<span class=\"line\">` 时直接复用并补充类名,否则做纯文本拆分。\n * @param codeElement pre 内部的 code 元素。\n * @param ownerDocument 当前 document。\n * @returns 当前代码块拆分后的总行数。\n */\nfunction rewriteCodeBlockLines(codeElement: HTMLElement, ownerDocument: Document): number {\n // Shiki 高亮输出的行 span 列表。\n const shikiLineElements = codeElement.querySelectorAll<HTMLElement>(\":scope > span.line\");\n\n if (shikiLineElements.length > 0) {\n // Shiki 行节点数组,用于重排 code 子节点并移除行间格式化换行。\n const shikiLineNodes = Array.from(shikiLineElements);\n\n shikiLineElements.forEach((lineElement) => {\n lineElement.classList.add(CODE_BLOCK_LINE_CLASS_NAME);\n // 空行使用零宽占位,行高统一继承 pre 的设置。\n if (lineElement.textContent === \"\") {\n lineElement.textContent = CODE_BLOCK_EMPTY_LINE_PLACEHOLDER;\n }\n });\n\n // 关键步骤:删除 Shiki 在行 span 之间输出的换行文本节点,避免 pre 把它们渲染成额外行距。\n codeElement.replaceChildren(...shikiLineNodes);\n return shikiLineNodes.length;\n }\n\n // 代码块的全部文本内容。\n const codeText = codeElement.textContent ?? \"\";\n // 去掉结尾多余换行,避免最后多出一个空行号。\n const trimmedCodeText = codeText.replace(/\\n+$/u, \"\");\n // 按换行符切分得到每一行原文。\n const codeLines = trimmedCodeText.split(\"\\n\");\n\n // 清空 code 现有子节点,重新生成行容器。\n while (codeElement.firstChild) {\n codeElement.removeChild(codeElement.firstChild);\n }\n\n codeLines.forEach((lineText) => {\n // 当前行的 span 容器,依赖 CSS `display: block` 提供视觉换行。\n const lineElement = ownerDocument.createElement(\"span\");\n lineElement.className = CODE_BLOCK_LINE_CLASS_NAME;\n // 空行使用零宽占位,行高统一继承 pre 的设置。\n if (lineText.length === 0) {\n lineElement.textContent = CODE_BLOCK_EMPTY_LINE_PLACEHOLDER;\n } else {\n lineElement.textContent = lineText;\n }\n codeElement.append(lineElement);\n });\n\n return codeLines.length;\n}\n\n/**\n * 创建代码块左侧固定行号列,保证行号区不参与横向滚动。\n * @param ownerDocument 当前 document。\n * @param lineCount 代码总行数。\n * @returns 已填充行号文本的固定列容器。\n */\nfunction createCodeBlockLineNumberGutter(\n ownerDocument: Document,\n lineCount: number\n): HTMLDivElement {\n // 行号固定列容器。\n const gutterElement = ownerDocument.createElement(\"div\");\n gutterElement.className = CODE_BLOCK_GUTTER_CLASS_NAME;\n\n // 当前循环行索引(0-based),用于生成 1-based 行号文本。\n for (let lineIndex = 0; lineIndex < lineCount; lineIndex += 1) {\n // 当前行号文本(1-based)。\n const lineNumberText = String(lineIndex + 1);\n // 单行行号节点。\n const lineNumberElement = ownerDocument.createElement(\"span\");\n lineNumberElement.className = CODE_BLOCK_GUTTER_LINE_CLASS_NAME;\n lineNumberElement.textContent = lineNumberText;\n gutterElement.append(lineNumberElement);\n }\n\n return gutterElement;\n}\n\n/**\n * \"复制\"态图标的 SVG 内部路径(双层叠放的矩形 + 复笔),\n * 直接内嵌以便用 currentColor 跟随按钮 hover / focus 颜色。\n */\nconst CODE_BLOCK_COPY_ICON_COPY_PATHS =\n '<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\"/>' +\n '<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\"/>' +\n '<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\"/>' +\n '<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\"/>';\n\n/**\n * \"已复制\"态图标的 SVG 内部路径(对勾 + 复笔),currentColor 由按钮状态控制。\n */\nconst CODE_BLOCK_COPY_ICON_CHECK_PATHS =\n '<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\"/>' +\n '<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\"/>';\n\n/**\n * 创建代码块复制按钮节点,仅包含图标,无文字标签。\n * @param ownerDocument 当前 document。\n * @returns 已配置类名与 aria 信息的按钮元素。\n */\nfunction createCodeBlockCopyButton(ownerDocument: Document): HTMLButtonElement {\n // 复制按钮元素。\n const copyButtonElement = ownerDocument.createElement(\"button\");\n copyButtonElement.type = \"button\";\n copyButtonElement.className = CODE_BLOCK_COPY_CLASS_NAME;\n copyButtonElement.setAttribute(\"aria-label\", CODE_BLOCK_COPY_ARIA_LABEL);\n\n // 默认 \"copy\" 状态图标。\n const copyIconElement = ownerDocument.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\n copyIconElement.setAttribute(\n \"class\",\n `${CODE_BLOCK_COPY_ICON_CLASS_NAME} ${CODE_BLOCK_COPY_ICON_COPY_CLASS_NAME}`\n );\n copyIconElement.setAttribute(\"viewBox\", \"0 0 20 20\");\n copyIconElement.setAttribute(\"aria-hidden\", \"true\");\n copyIconElement.setAttribute(\"focusable\", \"false\");\n copyIconElement.innerHTML = CODE_BLOCK_COPY_ICON_COPY_PATHS;\n copyButtonElement.append(copyIconElement);\n\n // \"已复制\" 状态图标,初始隐藏,由 CSS 根据 data 属性切换显示。\n const checkIconElement = ownerDocument.createElementNS(\"http://www.w3.org/2000/svg\", \"svg\");\n checkIconElement.setAttribute(\n \"class\",\n `${CODE_BLOCK_COPY_ICON_CLASS_NAME} ${CODE_BLOCK_COPY_ICON_CHECK_CLASS_NAME}`\n );\n checkIconElement.setAttribute(\"viewBox\", \"0 0 20 20\");\n checkIconElement.setAttribute(\"aria-hidden\", \"true\");\n checkIconElement.setAttribute(\"focusable\", \"false\");\n checkIconElement.innerHTML = CODE_BLOCK_COPY_ICON_CHECK_PATHS;\n copyButtonElement.append(checkIconElement);\n\n return copyButtonElement;\n}\n\n/**\n * 处理代码块复制按钮点击事件。\n * @param event 复制按钮点击事件。\n */\nfunction handleCodeBlockCopyClick(event: MouseEvent): void {\n // 被点击的复制按钮元素。\n const copyButtonElement = event.currentTarget as HTMLButtonElement;\n // 待复制的代码文本。\n const codeText = copyButtonElement.dataset.scribdownCodeSource ?? \"\";\n\n void writeCodeBlockTextToClipboard(codeText).then((isSucceeded) => {\n if (isSucceeded) {\n flashCodeBlockCopyButton(copyButtonElement);\n }\n });\n}\n\n/**\n * 把代码文本写入剪贴板,兼容不支持 navigator.clipboard 的环境。\n * @param codeText 待复制的代码文本。\n * @returns 是否复制成功。\n */\nasync function writeCodeBlockTextToClipboard(codeText: string): Promise<boolean> {\n // 现代浏览器优先使用 navigator.clipboard。\n if (typeof navigator !== \"undefined\" && navigator.clipboard?.writeText) {\n try {\n await navigator.clipboard.writeText(codeText);\n return true;\n } catch {\n // 安全上下文外可能会抛出,回落到 execCommand 兜底。\n }\n }\n\n if (typeof document === \"undefined\") {\n return false;\n }\n\n // 兜底:用临时 textarea + execCommand 完成复制。\n const fallbackTextarea = document.createElement(\"textarea\");\n fallbackTextarea.value = codeText;\n fallbackTextarea.setAttribute(\"readonly\", \"\");\n fallbackTextarea.style.position = \"fixed\";\n fallbackTextarea.style.opacity = \"0\";\n fallbackTextarea.style.pointerEvents = \"none\";\n document.body.append(fallbackTextarea);\n fallbackTextarea.select();\n\n // 兼容旧浏览器的 execCommand 返回值。\n const isSucceeded = document.execCommand(\"copy\");\n\n fallbackTextarea.remove();\n return isSucceeded;\n}\n\n/**\n * 让复制按钮短暂显示已复制状态。\n * @param copyButtonElement 被点击的复制按钮。\n */\nfunction flashCodeBlockCopyButton(copyButtonElement: HTMLButtonElement): void {\n // 切换 data 属性 + aria-label,CSS 会根据 data 属性切换两张 SVG 图标。\n copyButtonElement.dataset.scribdownCodeCopied = \"true\";\n copyButtonElement.setAttribute(\"aria-label\", CODE_BLOCK_COPY_ARIA_LABEL_COPIED);\n\n // 延迟恢复初始状态,给用户一个肉眼可见的反馈窗口。\n window.setTimeout(() => {\n delete copyButtonElement.dataset.scribdownCodeCopied;\n copyButtonElement.setAttribute(\"aria-label\", CODE_BLOCK_COPY_ARIA_LABEL);\n }, CODE_BLOCK_COPY_RESTORE_DELAY_MS);\n}\n\nexport { hydrateCodeBlocks, CODE_BLOCK_HYDRATED_DATA_KEY };\n","/**\n * 代码块语法高亮:Shiki 单例高亮器(eager + lazy grammar 加载)\n * 与渲染 HTML 字符串级别的代码块替换。\n */\n\nimport githubLightTheme from \"@shikijs/themes/github-light\";\nimport { type HighlighterCore, createHighlighterCore } from \"shiki/core\";\nimport { createOnigurumaEngine } from \"shiki/engine/oniguruma\";\n\nimport {\n CODE_HIGHLIGHTER_EAGER_LOADERS,\n CODE_HIGHLIGHTER_LAZY_LANGS\n} from \"./code-highlighter-langs\";\nimport { MERMAID_LANGUAGE_ID } from \"./mermaid\";\n\n// Shiki 高亮主题名(与 githubLightTheme 默认导出对应),用于 codeToHtml 选项。\nconst CODE_HIGHLIGHTER_THEME = \"github-light\";\n\n// 单例 Shiki 高亮器的初始化 Promise,确保整个进程只初始化一次。\nlet codeHighlighterPromise: Promise<HighlighterCore> | undefined;\n\n// 标记懒加载语言的请求去重:同一语言并发命中时复用同一 import Promise。\nconst codeHighlighterLazyLoadPromises = new Map<string, Promise<void>>();\n\n// 匹配渲染后 `<pre ...><code class=\"language-X\" ...>...</code></pre>` 的正则,用于 Shiki 替换。\n// 捕获 pre 与 code(class 之后)的附加属性(如 data-source-line),替换时原样保留,\n// 避免 remarkSourceLine 注入的 data-source-line 破坏匹配导致代码块不被高亮。\nconst CODE_BLOCK_HIGHLIGHT_PATTERN =\n /<pre([^>]*)><code class=\"language-([\\w-]+)\"([^>]*)>([\\s\\S]*?)<\\/code><\\/pre>/g;\n\n// 提取 Shiki HTML 输出中 `<code>...</code>` 之间内容的正则。\nconst SHIKI_CODE_INNER_PATTERN = /<code[^>]*>([\\s\\S]*?)<\\/code>/u;\n\n/**\n * 取得(必要时初始化)Shiki 单例高亮器。\n * 使用 shiki/core + 显式 grammar 列表,避免默认 bundle 把 200+ 语言全部打进 dist。\n * @returns 已就绪的 Shiki 高亮器实例。\n */\nasync function getCodeHighlighter(): Promise<HighlighterCore> {\n if (!codeHighlighterPromise) {\n codeHighlighterPromise = createHighlighterCore({\n themes: [githubLightTheme],\n // eager loaders 通过动态 import 拆为独立 chunk,初始化时并发拉取;\n // 主 bundle 不再内联 grammar JSON,体积更小,多个 grammar 也可并行加载。\n langs: CODE_HIGHLIGHTER_EAGER_LOADERS,\n // oniguruma 引擎 + wasm;shiki/wasm 内部走 base64 内联,扩展环境无需额外资源声明。\n engine: createOnigurumaEngine(import(\"shiki/wasm\"))\n });\n }\n\n return codeHighlighterPromise;\n}\n\n/**\n * 把渲染 HTML 中的代码块替换为 Shiki 高亮后的结构。\n * @param html 已渲染(可能已 sanitize)的 HTML 文本。\n * @returns 含高亮 span 的 HTML 文本。\n */\nasync function highlightMarkdownCodeBlocks(html: string): Promise<string> {\n if (!CODE_BLOCK_HIGHLIGHT_PATTERN.test(html)) {\n return html;\n }\n // RegExp 是 global 状态,重置 lastIndex 以保证多次调用一致。\n CODE_BLOCK_HIGHLIGHT_PATTERN.lastIndex = 0;\n\n // 取得 Shiki 单例高亮器。\n const highlighter = await getCodeHighlighter();\n // 替换累积器:从输入 HTML 起逐段拼接处理结果。\n let resultHtml = \"\";\n // 上一段未处理 HTML 的结束索引。\n let lastIndex = 0;\n\n for (const match of html.matchAll(CODE_BLOCK_HIGHLIGHT_PATTERN)) {\n const [matched, preAttributes, rawLanguage, codeAttributes, encodedCode] = match;\n const matchStart = match.index ?? 0;\n\n resultHtml += html.slice(lastIndex, matchStart);\n resultHtml += await highlightSingleCodeBlock(highlighter, {\n rawLanguage,\n encodedCode,\n matchedHtml: matched,\n preAttributes,\n codeAttributes\n });\n lastIndex = matchStart + matched.length;\n }\n\n resultHtml += html.slice(lastIndex);\n return resultHtml;\n}\n\n/**\n * 单个代码块的 Shiki 高亮入参。\n */\ninterface CodeBlockHighlightInput {\n /** 代码块标注的语言标识。 */\n rawLanguage: string;\n /** 已 HTML 转义的代码内容。 */\n encodedCode: string;\n /** 原始匹配片段,作为兜底返回值。 */\n matchedHtml: string;\n /** pre 标签上的附加属性文本,替换时原样保留。 */\n preAttributes: string;\n /** code 标签上 class 之后的附加属性文本(如 data-source-line),替换时原样保留。 */\n codeAttributes: string;\n}\n\n/**\n * 高亮单个代码块,必要时按需加载语言。\n * @param highlighter Shiki 高亮器实例。\n * @param input 代码块高亮入参。\n * @returns 高亮后的代码块 HTML。\n */\nasync function highlightSingleCodeBlock(\n highlighter: HighlighterCore,\n input: CodeBlockHighlightInput\n): Promise<string> {\n // 代码块标注的语言标识。\n const { rawLanguage, encodedCode, matchedHtml, preAttributes, codeAttributes } = input;\n // 归一化语言标识:去除大小写差异,方便匹配 Shiki 内置名。\n const normalizedLanguage = rawLanguage.toLowerCase();\n\n // 关键步骤:mermaid 代码块跳过 Shiki 高亮,保留原始源码文本,\n // 后续 hydrate 阶段会读取 textContent 调用 mermaid 渲染。\n if (normalizedLanguage === MERMAID_LANGUAGE_ID) {\n return matchedHtml;\n }\n // Shiki 中真实可用的语言标识,找不到时退回 \"text\"。\n const resolvedLanguage = await ensureHighlighterLanguage(highlighter, normalizedLanguage);\n\n // 还原 HTML 转义,得到 Shiki 期望的原始代码。\n const codeText = decodeHtmlEntities(encodedCode);\n\n try {\n // Shiki 输出形如 `<pre class=\"shiki ...\"><code><span class=\"line\">...</span></code></pre>`。\n const highlightedHtml = highlighter.codeToHtml(codeText, {\n lang: resolvedLanguage,\n theme: CODE_HIGHLIGHTER_THEME\n });\n // 仅保留 `<code>` 内部内容,外层 `<pre><code>` 沿用原结构,便于 hydrate 识别。\n const innerMatch = highlightedHtml.match(SHIKI_CODE_INNER_PATTERN);\n const innerHtml = innerMatch?.[1] ?? encodedCode;\n\n return `<pre${preAttributes}><code class=\"language-${rawLanguage}\"${codeAttributes}>${innerHtml}</code></pre>`;\n } catch {\n // Shiki 调用异常时(语言未注册等)原样返回,避免影响整体渲染。\n return matchedHtml;\n }\n}\n\n/**\n * 在 Shiki 高亮器里确保给定语言可用,必要时动态加载。\n * @param highlighter Shiki 高亮器实例。\n * @param normalizedLanguage 已归一化的语言标识。\n * @returns 高亮时可使用的语言标识。\n */\nasync function ensureHighlighterLanguage(\n highlighter: HighlighterCore,\n normalizedLanguage: string\n): Promise<string> {\n if (normalizedLanguage.length === 0) {\n return \"text\";\n }\n\n // highlighter.getLoadedLanguages() 自带别名解析(grammar 自身声明的 aliases 也会被登记)。\n if (highlighter.getLoadedLanguages().includes(normalizedLanguage)) {\n return normalizedLanguage;\n }\n\n /** 从懒加载注册表查找对应 grammar import 函数。 */\n const lazyLoader = CODE_HIGHLIGHTER_LAZY_LANGS[normalizedLanguage];\n if (!lazyLoader) {\n return \"text\";\n }\n\n // 并发命中同一语言时复用同一 Promise,避免重复 import / loadLanguage。\n let pending = codeHighlighterLazyLoadPromises.get(normalizedLanguage);\n if (!pending) {\n pending = (async () => {\n const grammarModule = await lazyLoader();\n await highlighter.loadLanguage(grammarModule.default);\n })();\n codeHighlighterLazyLoadPromises.set(normalizedLanguage, pending);\n }\n\n try {\n await pending;\n return normalizedLanguage;\n } catch {\n // 关键步骤:失败的加载不留在缓存里,下次渲染可重试(与 mermaid loader 的失败重置策略一致),\n // 避免一次网络抖动后该语言永久退回纯文本。\n codeHighlighterLazyLoadPromises.delete(normalizedLanguage);\n return \"text\";\n }\n}\n\n// HTML 命名实体与对应字符的映射,覆盖代码块中常见的转义结果。\nconst HTML_NAMED_ENTITIES: Record<string, string> = {\n lt: \"<\",\n gt: \">\",\n quot: '\"',\n apos: \"'\",\n amp: \"&\"\n};\n\n// 匹配命名 / 十进制 / 十六进制三种字符实体;hex / dec 大小写均兼容。\nconst HTML_ENTITY_PATTERN = /&(?:(lt|gt|quot|apos|amp)|#x([0-9a-fA-F]+)|#(\\d+));/g;\n\n/**\n * 还原 HTML 文本中的字符实体(命名 + 十进制 + 十六进制)。\n *\n * 关键步骤:单次左到右扫描,避免“先解 `&amp;` 再解 `&lt;`”导致的二次解码漏洞,\n * 同时识别 `&#x3C;` 等十六进制实体(hast-util-to-html 在 Node 环境下会输出该形式)。\n *\n * @param encodedText HTML 转义后的文本。\n * @returns 还原后的原始文本。\n */\nfunction decodeHtmlEntities(encodedText: string): string {\n return encodedText.replace(\n HTML_ENTITY_PATTERN,\n (matched, namedEntity?: string, hexCodePoint?: string, decimalCodePoint?: string) => {\n if (namedEntity) {\n return HTML_NAMED_ENTITIES[namedEntity] ?? matched;\n }\n if (hexCodePoint) {\n return safeFromCodePoint(parseInt(hexCodePoint, 16), matched);\n }\n if (decimalCodePoint) {\n return safeFromCodePoint(parseInt(decimalCodePoint, 10), matched);\n }\n return matched;\n }\n );\n}\n\n/**\n * 安全地把 Unicode 码点转成字符,越界 / NaN 时回退原始片段,避免抛错。\n * @param codePoint Unicode 码点(10/16 进制解析后的数值)。\n * @param fallback 解析失败时返回的原始 HTML 片段。\n * @returns 对应字符或原始片段。\n */\nfunction safeFromCodePoint(codePoint: number, fallback: string): string {\n if (!Number.isFinite(codePoint) || codePoint < 0 || codePoint > 0x10ffff) {\n return fallback;\n }\n try {\n return String.fromCodePoint(codePoint);\n } catch {\n return fallback;\n }\n}\n\nexport { highlightMarkdownCodeBlocks };\n","import type { LanguageRegistration } from \"shiki/core\";\n\n/**\n * 单个 grammar 模块的默认导出形态。\n * `@shikijs/langs/<id>` 默认导出形如 `LanguageRegistration[]`。\n */\ntype GrammarModule = { default: LanguageRegistration[] };\n\n/**\n * 懒加载注册表中的语言导入函数。\n * 调用时返回 grammar 模块,再交由 highlighter.loadLanguage 注册。\n */\ntype GrammarLoader = () => Promise<GrammarModule>;\n\n/**\n * 高亮器初始化时即刻拉取的语言。\n * 浏览器扩展冷启动对包加载敏感,这里保持为空;具体 grammar 按代码块语言懒加载。\n */\nexport const CODE_HIGHLIGHTER_EAGER_LOADERS: GrammarLoader[] = [];\n\n/**\n * 懒加载语言注册表:id 归一化(小写)→ 动态 import 函数。\n * 仅列出此处的语言会被 Vite 打成异步 chunk;其他语言会回退为纯文本。\n * 同一 grammar 的多个别名指向同一 import,避免重复 chunk。\n * 已在 eager 列表中的语言不需要在此重复(其 grammar 自带的 aliases 会在 loadLanguage 时一并注册)。\n */\nexport const CODE_HIGHLIGHTER_LAZY_LANGS: Record<string, GrammarLoader> = {\n // 高频基础语言\n bash: () => import(\"@shikijs/langs/bash\"),\n sh: () => import(\"@shikijs/langs/bash\"),\n shell: () => import(\"@shikijs/langs/bash\"),\n css: () => import(\"@shikijs/langs/css\"),\n diff: () => import(\"@shikijs/langs/diff\"),\n html: () => import(\"@shikijs/langs/html\"),\n js: () => import(\"@shikijs/langs/javascript\"),\n javascript: () => import(\"@shikijs/langs/javascript\"),\n json: () => import(\"@shikijs/langs/json\"),\n markdown: () => import(\"@shikijs/langs/markdown\"),\n md: () => import(\"@shikijs/langs/markdown\"),\n ts: () => import(\"@shikijs/langs/typescript\"),\n typescript: () => import(\"@shikijs/langs/typescript\"),\n yaml: () => import(\"@shikijs/langs/yaml\"),\n yml: () => import(\"@shikijs/langs/yaml\"),\n // 前端框架与扩展\n jsx: () => import(\"@shikijs/langs/jsx\"),\n tsx: () => import(\"@shikijs/langs/tsx\"),\n vue: () => import(\"@shikijs/langs/vue\"),\n svelte: () => import(\"@shikijs/langs/svelte\"),\n astro: () => import(\"@shikijs/langs/astro\"),\n mdx: () => import(\"@shikijs/langs/mdx\"),\n // 样式\n scss: () => import(\"@shikijs/langs/scss\"),\n sass: () => import(\"@shikijs/langs/sass\"),\n less: () => import(\"@shikijs/langs/less\"),\n postcss: () => import(\"@shikijs/langs/postcss\"),\n stylus: () => import(\"@shikijs/langs/stylus\"),\n // 查询 / 数据 / 标记\n graphql: () => import(\"@shikijs/langs/graphql\"),\n gql: () => import(\"@shikijs/langs/graphql\"),\n sql: () => import(\"@shikijs/langs/sql\"),\n xml: () => import(\"@shikijs/langs/xml\"),\n regexp: () => import(\"@shikijs/langs/regexp\"),\n regex: () => import(\"@shikijs/langs/regexp\"),\n // 系统语言\n c: () => import(\"@shikijs/langs/c\"),\n cpp: () => import(\"@shikijs/langs/cpp\"),\n \"c++\": () => import(\"@shikijs/langs/cpp\"),\n csharp: () => import(\"@shikijs/langs/csharp\"),\n cs: () => import(\"@shikijs/langs/csharp\"),\n java: () => import(\"@shikijs/langs/java\"),\n go: () => import(\"@shikijs/langs/go\"),\n rust: () => import(\"@shikijs/langs/rust\"),\n rs: () => import(\"@shikijs/langs/rust\"),\n swift: () => import(\"@shikijs/langs/swift\"),\n kotlin: () => import(\"@shikijs/langs/kotlin\"),\n kt: () => import(\"@shikijs/langs/kotlin\"),\n scala: () => import(\"@shikijs/langs/scala\"),\n \"objective-c\": () => import(\"@shikijs/langs/objective-c\"),\n objc: () => import(\"@shikijs/langs/objective-c\"),\n \"objective-cpp\": () => import(\"@shikijs/langs/objective-cpp\"),\n // 脚本语言\n python: () => import(\"@shikijs/langs/python\"),\n py: () => import(\"@shikijs/langs/python\"),\n ruby: () => import(\"@shikijs/langs/ruby\"),\n rb: () => import(\"@shikijs/langs/ruby\"),\n perl: () => import(\"@shikijs/langs/perl\"),\n lua: () => import(\"@shikijs/langs/lua\"),\n powershell: () => import(\"@shikijs/langs/powershell\"),\n ps: () => import(\"@shikijs/langs/powershell\"),\n ps1: () => import(\"@shikijs/langs/powershell\"),\n fish: () => import(\"@shikijs/langs/fish\"),\n php: () => import(\"@shikijs/langs/php\"),\n r: () => import(\"@shikijs/langs/r\"),\n // 配置 / 数据\n toml: () => import(\"@shikijs/langs/toml\"),\n ini: () => import(\"@shikijs/langs/ini\"),\n dockerfile: () => import(\"@shikijs/langs/dockerfile\"),\n docker: () => import(\"@shikijs/langs/dockerfile\"),\n makefile: () => import(\"@shikijs/langs/make\"),\n make: () => import(\"@shikijs/langs/make\"),\n cmake: () => import(\"@shikijs/langs/cmake\"),\n nginx: () => import(\"@shikijs/langs/nginx\"),\n properties: () => import(\"@shikijs/langs/properties\"),\n dotenv: () => import(\"@shikijs/langs/dotenv\"),\n hcl: () => import(\"@shikijs/langs/hcl\"),\n terraform: () => import(\"@shikijs/langs/terraform\"),\n tf: () => import(\"@shikijs/langs/terraform\"),\n nix: () => import(\"@shikijs/langs/nix\"),\n json5: () => import(\"@shikijs/langs/json5\"),\n jsonc: () => import(\"@shikijs/langs/jsonc\"),\n csv: () => import(\"@shikijs/langs/csv\"),\n tsv: () => import(\"@shikijs/langs/tsv\"),\n // 函数式 / 其他\n dart: () => import(\"@shikijs/langs/dart\"),\n elixir: () => import(\"@shikijs/langs/elixir\"),\n erlang: () => import(\"@shikijs/langs/erlang\"),\n haskell: () => import(\"@shikijs/langs/haskell\"),\n hs: () => import(\"@shikijs/langs/haskell\"),\n clojure: () => import(\"@shikijs/langs/clojure\"),\n clj: () => import(\"@shikijs/langs/clojure\"),\n ocaml: () => import(\"@shikijs/langs/ocaml\"),\n fsharp: () => import(\"@shikijs/langs/fsharp\"),\n vb: () => import(\"@shikijs/langs/vb\"),\n asm: () => import(\"@shikijs/langs/asm\"),\n llvm: () => import(\"@shikijs/langs/llvm\"),\n solidity: () => import(\"@shikijs/langs/solidity\"),\n prisma: () => import(\"@shikijs/langs/prisma\"),\n proto: () => import(\"@shikijs/langs/proto\"),\n // 文档标记\n latex: () => import(\"@shikijs/langs/latex\"),\n tex: () => import(\"@shikijs/langs/latex\"),\n asciidoc: () => import(\"@shikijs/langs/asciidoc\"),\n adoc: () => import(\"@shikijs/langs/asciidoc\"),\n // 着色器 / 二进制\n glsl: () => import(\"@shikijs/langs/glsl\"),\n wasm: () => import(\"@shikijs/langs/wasm\"),\n wat: () => import(\"@shikijs/langs/wasm\")\n};\n","/**\n * Mermaid 图表 hydration:把 mermaid 代码块替换为图表 figure 结构,\n * 按需加载 mermaid 主包并在 live DOM 阶段触发异步渲染,失败时展示 fallback。\n */\n\nimport {\n MERMAID_BODY_CLASS_NAME,\n MERMAID_CANVAS_CLASS_NAME,\n MERMAID_CHROME_CLASS_NAME,\n MERMAID_FALLBACK_CLASS_NAME,\n MERMAID_FALLBACK_ICON_CLASS_NAME,\n MERMAID_FALLBACK_SOURCE_CLASS_NAME,\n MERMAID_FALLBACK_TEXT_CLASS_NAME,\n MERMAID_FIGURE_CLASS_NAME,\n MERMAID_FIGURE_FAILED_CLASS_NAME,\n MERMAID_FIGURE_LOADED_CLASS_NAME,\n MERMAID_FIGURE_LOADING_CLASS_NAME,\n MERMAID_FULLSCREEN_BUTTON_CLASS_NAME,\n MERMAID_LABEL_CLASS_NAME,\n SOURCE_LINE_DATA_ATTRIBUTE\n} from \"@scribdown/shared\";\n\nimport { CODE_BLOCK_HYDRATED_DATA_KEY } from \"./code-block-chrome\";\nimport { MERMAID_LABEL_TEXT, openMarkdownMermaidViewer } from \"./mermaid-viewer\";\n\n// Mermaid 代码块的语言标识,对应 fixture 中的 ```mermaid。\nconst MERMAID_LANGUAGE_ID = \"mermaid\";\n\n// Mermaid 已 hydrate 标记的 dataset 键(仅表示结构已构建)。\nconst MERMAID_HYDRATED_DATA_KEY = \"scribdownMermaidHydrated\";\n\n// Mermaid 渲染已启动标记,避免在 live DOM 重复触发 mermaid.render。\nconst MERMAID_RENDER_STARTED_DATA_KEY = \"scribdownMermaidRenderStarted\";\n\n// Mermaid 源码寄存在 figure 上的 dataset 键,供延后的 live-DOM 渲染读取。\nconst MERMAID_SOURCE_DATA_KEY = \"scribdownMermaidSourceText\";\n\n// Mermaid 失败态默认文案。\nconst MERMAID_FALLBACK_DEFAULT_TEXT = \"图表渲染失败\";\n\n// Mermaid SVG 节点宿主元素 id 前缀,确保多图表 id 唯一。\nconst MERMAID_RENDER_ID_PREFIX = \"scribdown-mermaid-\";\n\n// Mermaid 渲染顺序计数器,配合前缀生成全局唯一 id。\nlet mermaidRenderIdCounter = 0;\n\n// Mermaid 全屏按钮可访问名称。\nconst MERMAID_FULLSCREEN_BUTTON_ARIA_LABEL = \"全屏查看图表\";\n\n// 是否允许 mermaid 在 figure 右下角悬浮显示全屏按钮(仅 loaded 态显示)。\nconst MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY = \"scribdownMermaidFullscreenReady\";\n\n// Mermaid 全屏查看器存放原始 SVG 字符串的 dataset 键。\nconst MERMAID_VIEWER_SOURCE_DATA_KEY = \"scribdownMermaidSource\";\n\n/**\n * Mermaid 渲染句柄缓存:仅在浏览器环境(含 VS Code webview)下加载,\n * 避免在 Node 单元测试环境触发 mermaid 依赖加载。\n */\nlet mermaidLoaderPromise: Promise<MermaidApi | undefined> | undefined;\n\n/**\n * Mermaid 11+ 的最小 API 子集,仅声明渲染必需成员,避免引入巨大类型。\n */\ninterface MermaidApi {\n initialize: (config: Record<string, unknown>) => void;\n parse: (source: string) => Promise<unknown> | unknown;\n render: (\n id: string,\n source: string,\n container?: Element\n ) => Promise<{ svg: string; bindFunctions?: (element: Element) => void }>;\n}\n\n/**\n * 把渲染后的 mermaid 代码块转换为图表 figure 容器,并按需触发异步渲染。\n *\n * 拆分为两步:\n * 1. {@link decorateMermaidBlock} 同步把 `<pre><code>` 替换为 figure 结构,把源码寄存在 dataset 上。\n * 2. {@link kickOffPendingMermaidRenders} 仅对真正落入 live DOM 的 figure 触发 mermaid.render,\n * 避免 VS Code 预览路径里 hydrate 跑在 detached 节点上、被随后的 morphdom 丢弃。\n *\n * @param rootElement 包含 Markdown 渲染结果的根节点。\n */\nfunction hydrateMermaidBlocks(rootElement: ParentNode): void {\n // 当前根节点内所有未 hydrate 的 mermaid 代码块。\n const mermaidCodeElements = rootElement.querySelectorAll<HTMLElement>(\n `pre > code.language-${MERMAID_LANGUAGE_ID}`\n );\n\n mermaidCodeElements.forEach((codeElement) => {\n // 对应的 pre 容器。\n const preElement = codeElement.parentElement as HTMLPreElement | null;\n if (!preElement) {\n return;\n }\n\n // 若 pre 已经被代码块 chrome 包裹,跳过避免重复处理。\n if (preElement.dataset[CODE_BLOCK_HYDRATED_DATA_KEY] === \"true\") {\n return;\n }\n\n decorateMermaidBlock(preElement, codeElement);\n });\n\n kickOffPendingMermaidRenders(rootElement);\n}\n\n/**\n * 针对 live DOM 中仍处于 loading 态的 mermaid figure 启动 mermaid.render。\n * 未连接到 document 的 figure 直接跳过,等下一次 hydrate(live DOM 阶段)再触发。\n * @param rootElement 包含 Markdown 渲染结果的根节点。\n */\nfunction kickOffPendingMermaidRenders(rootElement: ParentNode): void {\n // 仍在 loading 态的 figure 集合。\n const pendingFigures = rootElement.querySelectorAll<HTMLElement>(\n `.${MERMAID_FIGURE_CLASS_NAME}.${MERMAID_FIGURE_LOADING_CLASS_NAME}`\n );\n\n pendingFigures.forEach((figureElement) => {\n // 仅在已挂载到 document 时启动渲染,避免在 detached 节点上空跑。\n if (!figureElement.isConnected) {\n return;\n }\n if (figureElement.dataset[MERMAID_RENDER_STARTED_DATA_KEY] === \"true\") {\n return;\n }\n\n // 画布与 mermaid 源码均从 figure 结构 / dataset 上恢复。\n const canvasElement = figureElement.querySelector<HTMLElement>(\n `.${MERMAID_CANVAS_CLASS_NAME}`\n );\n const mermaidSource = figureElement.dataset[MERMAID_SOURCE_DATA_KEY] ?? \"\";\n\n if (!canvasElement || mermaidSource.length === 0) {\n return;\n }\n\n figureElement.dataset[MERMAID_RENDER_STARTED_DATA_KEY] = \"true\";\n void renderMermaidIntoCanvas(figureElement, canvasElement, mermaidSource);\n });\n}\n\n/**\n * 把 pre + code 替换为 mermaid figure 结构,并异步渲染 SVG。\n * @param preElement 原始代码块 pre 元素。\n * @param codeElement 原始代码块 code 元素。\n */\nfunction decorateMermaidBlock(preElement: HTMLPreElement, codeElement: HTMLElement): void {\n // 已经 hydrate 过的代码块直接跳过。\n if (preElement.dataset[MERMAID_HYDRATED_DATA_KEY] === \"true\") {\n return;\n }\n preElement.dataset[MERMAID_HYDRATED_DATA_KEY] = \"true\";\n\n // 当前 pre 所属 document。\n const ownerDocument = preElement.ownerDocument;\n // 关键步骤:在替换 DOM 前抓取原始 mermaid 源码文本。\n const mermaidSource = (codeElement.textContent ?? \"\").replace(/\\n+$/u, \"\");\n\n // 外层 figure 容器,承载顶部标签与图表正文。\n const figureElement = ownerDocument.createElement(\"figure\");\n figureElement.className = `${MERMAID_FIGURE_CLASS_NAME} ${MERMAID_FIGURE_LOADING_CLASS_NAME}`;\n\n // 顶部 chrome,仅承载 Mermaid 类型标签。\n const chromeElement = ownerDocument.createElement(\"div\");\n chromeElement.className = MERMAID_CHROME_CLASS_NAME;\n\n const labelElement = ownerDocument.createElement(\"span\");\n labelElement.className = MERMAID_LABEL_CLASS_NAME;\n labelElement.textContent = MERMAID_LABEL_TEXT;\n chromeElement.append(labelElement);\n\n // 正文容器,承载 SVG 画布与失败态。\n const bodyElement = ownerDocument.createElement(\"div\");\n bodyElement.className = MERMAID_BODY_CLASS_NAME;\n\n // 用于挂载 SVG 的画布节点。\n const canvasElement = ownerDocument.createElement(\"div\");\n canvasElement.className = MERMAID_CANVAS_CLASS_NAME;\n canvasElement.setAttribute(\"role\", \"img\");\n canvasElement.setAttribute(\"aria-label\", MERMAID_LABEL_TEXT);\n\n // 右下角悬浮全屏按钮,渲染成功后再启用。\n const fullscreenButtonElement = ownerDocument.createElement(\"button\");\n fullscreenButtonElement.type = \"button\";\n fullscreenButtonElement.className = MERMAID_FULLSCREEN_BUTTON_CLASS_NAME;\n fullscreenButtonElement.setAttribute(\"aria-label\", MERMAID_FULLSCREEN_BUTTON_ARIA_LABEL);\n // 渲染过程中先禁用,避免点击空白图表。\n fullscreenButtonElement.disabled = true;\n fullscreenButtonElement.innerHTML = MERMAID_FULLSCREEN_ICON_SVG;\n fullscreenButtonElement.addEventListener(\"click\", handleMermaidFullscreenButtonClick);\n\n bodyElement.append(canvasElement, fullscreenButtonElement);\n figureElement.append(chromeElement, bodyElement);\n\n // 关键步骤:把源码行锚点从 code 迁移到 figure,对齐编辑器双向滚动。\n const sourceLine = codeElement.getAttribute(SOURCE_LINE_DATA_ATTRIBUTE);\n if (sourceLine !== null) {\n figureElement.setAttribute(SOURCE_LINE_DATA_ATTRIBUTE, sourceLine);\n }\n\n // 关键步骤:把源码寄存在 figure 上,留给 live-DOM 阶段读取后真正触发 mermaid 渲染。\n // 不在此处直接 await render:VS Code 预览首先在 detached 节点上 hydrate,\n // 之后才把 figure 合并进 live DOM,提前渲染的结果会被 morphdom 丢弃。\n figureElement.dataset[MERMAID_SOURCE_DATA_KEY] = mermaidSource;\n\n preElement.replaceWith(figureElement);\n}\n\n/**\n * 加载并初始化 mermaid 实例,浏览器环境外返回 undefined。\n * @returns mermaid API 句柄。\n */\nasync function loadMermaid(): Promise<MermaidApi | undefined> {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n return undefined;\n }\n\n if (!mermaidLoaderPromise) {\n mermaidLoaderPromise = (async () => {\n // 动态导入:仅在确实出现 mermaid 块时才下载 mermaid 主包。\n const mermaidModule = (await import(\"mermaid\")) as { default: MermaidApi };\n const mermaidApi = mermaidModule.default;\n // startOnLoad=false 由 hydrate 主动控制渲染时机;securityLevel=strict 阻断脚本注入。\n // useMaxWidth=false:让 mermaid 输出带固有宽高属性的 SVG,避免它内联 width:100%/max-width\n // 强行覆盖 CSS,从而把缩放完全交给画布上的 max-width/max-height:100% 配合 viewBox 等比适配。\n mermaidApi.initialize({\n startOnLoad: false,\n securityLevel: \"strict\",\n theme: \"default\",\n flowchart: { useMaxWidth: false },\n sequence: { useMaxWidth: false },\n class: { useMaxWidth: false },\n state: { useMaxWidth: false },\n gantt: { useMaxWidth: false },\n er: { useMaxWidth: false },\n journey: { useMaxWidth: false },\n pie: { useMaxWidth: false },\n requirement: { useMaxWidth: false },\n c4: { useMaxWidth: false },\n mindmap: { useMaxWidth: false },\n timeline: { useMaxWidth: false },\n gitGraph: { useMaxWidth: false },\n quadrantChart: { useMaxWidth: false },\n xyChart: { useMaxWidth: false },\n sankey: { useMaxWidth: false },\n block: { useMaxWidth: false }\n });\n return mermaidApi;\n })().catch((loadError: unknown) => {\n // 加载失败后重置 promise,给下次渲染重试机会。\n mermaidLoaderPromise = undefined;\n throw loadError;\n });\n }\n\n return mermaidLoaderPromise;\n}\n\n/**\n * 异步把 mermaid 源码渲染为 SVG 注入指定画布,失败时切换到 fallback 态。\n * @param figureElement mermaid 外层 figure 容器。\n * @param canvasElement SVG 挂载点。\n * @param mermaidSource mermaid 源码文本。\n */\nasync function renderMermaidIntoCanvas(\n figureElement: HTMLElement,\n canvasElement: HTMLElement,\n mermaidSource: string\n): Promise<void> {\n try {\n const mermaidApi = await loadMermaid();\n if (!mermaidApi) {\n // 非浏览器环境:保留 loading 类名但不抛错,避免单测污染。\n return;\n }\n\n mermaidRenderIdCounter += 1;\n const renderId = `${MERMAID_RENDER_ID_PREFIX}${mermaidRenderIdCounter}`;\n const { svg, bindFunctions } = await mermaidApi.render(renderId, mermaidSource);\n\n canvasElement.innerHTML = svg;\n bindFunctions?.(canvasElement);\n figureElement.classList.remove(MERMAID_FIGURE_LOADING_CLASS_NAME);\n figureElement.classList.add(MERMAID_FIGURE_LOADED_CLASS_NAME);\n\n // 关键步骤:记录原始 SVG 文本,全屏查看器以同样的源码注入,避免引用同一 DOM。\n figureElement.dataset[MERMAID_VIEWER_SOURCE_DATA_KEY] = svg;\n figureElement.dataset[MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY] = \"true\";\n\n // 渲染成功后启用全屏按钮。\n const fullscreenButtonElement = figureElement.querySelector<HTMLButtonElement>(\n `.${MERMAID_FULLSCREEN_BUTTON_CLASS_NAME}`\n );\n if (fullscreenButtonElement) {\n fullscreenButtonElement.disabled = false;\n }\n } catch (renderError: unknown) {\n showMermaidFallback(figureElement, canvasElement, mermaidSource, renderError);\n }\n}\n\n/**\n * 切换 mermaid 容器到失败态:隐藏画布、展示错误摘要与源码。\n * @param figureElement mermaid 外层 figure。\n * @param canvasElement SVG 画布节点。\n * @param mermaidSource 原始 mermaid 源码。\n * @param renderError mermaid 抛出的错误。\n */\nfunction showMermaidFallback(\n figureElement: HTMLElement,\n canvasElement: HTMLElement,\n mermaidSource: string,\n renderError: unknown\n): void {\n const ownerDocument = figureElement.ownerDocument;\n figureElement.classList.remove(MERMAID_FIGURE_LOADING_CLASS_NAME);\n figureElement.classList.add(MERMAID_FIGURE_FAILED_CLASS_NAME);\n\n // 失败态:移除右下角全屏按钮,避免对无图表的容器开启查看器。\n const fullscreenButtonElement = figureElement.querySelector<HTMLButtonElement>(\n `.${MERMAID_FULLSCREEN_BUTTON_CLASS_NAME}`\n );\n fullscreenButtonElement?.remove();\n\n // 失败态:清空 SVG 画布并替换为错误说明 + 源码块。\n canvasElement.replaceChildren();\n\n // mermaid 抛错时通常会污染 document 末尾的临时 div,需要清理。\n cleanupOrphanMermaidNodes(ownerDocument);\n\n const fallbackElement = ownerDocument.createElement(\"div\");\n fallbackElement.className = MERMAID_FALLBACK_CLASS_NAME;\n\n const iconElement = ownerDocument.createElement(\"span\");\n iconElement.className = MERMAID_FALLBACK_ICON_CLASS_NAME;\n iconElement.setAttribute(\"aria-hidden\", \"true\");\n fallbackElement.append(iconElement);\n\n const textElement = ownerDocument.createElement(\"p\");\n textElement.className = MERMAID_FALLBACK_TEXT_CLASS_NAME;\n textElement.textContent = MERMAID_FALLBACK_DEFAULT_TEXT;\n fallbackElement.append(textElement);\n\n // mermaid 错误对象常带可读 message,附在源码块前给排查使用。\n const errorMessage = extractErrorMessage(renderError);\n if (errorMessage) {\n const messageElement = ownerDocument.createElement(\"p\");\n messageElement.className = MERMAID_FALLBACK_SOURCE_CLASS_NAME;\n messageElement.textContent = errorMessage;\n fallbackElement.append(messageElement);\n }\n\n // 源码 pre:失败时把原文展示给用户便于复制修改。\n if (mermaidSource.length > 0) {\n const sourceElement = ownerDocument.createElement(\"pre\");\n sourceElement.className = MERMAID_FALLBACK_SOURCE_CLASS_NAME;\n sourceElement.textContent = mermaidSource;\n fallbackElement.append(sourceElement);\n }\n\n canvasElement.replaceWith(fallbackElement);\n}\n\n/**\n * 提取 mermaid 渲染错误的可读文本。\n * @param renderError mermaid 抛出的错误对象。\n * @returns 错误描述文本,未识别时返回空串。\n */\nfunction extractErrorMessage(renderError: unknown): string {\n if (renderError instanceof Error) {\n return renderError.message;\n }\n if (typeof renderError === \"string\") {\n return renderError;\n }\n return \"\";\n}\n\n/**\n * 清理 mermaid 渲染失败时残留在文档尾部的临时节点。\n * Mermaid 在 render 抛错时不一定会移除自己挂在 body 上的占位 div。\n * @param ownerDocument 当前 document。\n */\nfunction cleanupOrphanMermaidNodes(ownerDocument: Document): void {\n // mermaid 在 document.body 末尾创建临时容器,id 以 d 开头或包含 render id 前缀。\n const orphanNodes = ownerDocument.querySelectorAll<HTMLElement>(\n `body > [id^=\"${MERMAID_RENDER_ID_PREFIX}\"], body > div[id^=\"d\"][id*=\"mermaid\"]`\n );\n orphanNodes.forEach((orphanNode) => {\n orphanNode.remove();\n });\n}\n\n/**\n * 右下角全屏按钮使用的 SVG 图标(两个对角线箭头组成的方框)。\n */\nconst MERMAID_FULLSCREEN_ICON_SVG =\n '<svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" aria-hidden=\"true\" focusable=\"false\">' +\n '<path d=\"M4 9 V4 H9 M15 4 H20 V9 M20 15 V20 H15 M9 20 H4 V15\" ' +\n 'fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>' +\n \"</svg>\";\n\n/**\n * 处理 mermaid 全屏按钮点击:找到所属 figure,把 SVG 源码注入全屏查看器。\n * @param event 全屏按钮点击事件。\n */\nfunction handleMermaidFullscreenButtonClick(event: MouseEvent): void {\n // 触发事件的按钮元素。\n const buttonElement = event.currentTarget as HTMLButtonElement;\n // 所属的 mermaid figure。\n const figureElement = buttonElement.closest<HTMLElement>(`.${MERMAID_FIGURE_CLASS_NAME}`);\n\n if (!figureElement) {\n return;\n }\n\n if (figureElement.dataset[MERMAID_FULLSCREEN_AVAILABLE_DATA_KEY] !== \"true\") {\n return;\n }\n\n // 之前渲染缓存下来的 SVG 源码。\n const svgSource = figureElement.dataset[MERMAID_VIEWER_SOURCE_DATA_KEY] ?? \"\";\n\n if (svgSource.length === 0) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n openMarkdownMermaidViewer(figureElement.ownerDocument, svgSource);\n}\n\nexport { hydrateMermaidBlocks, MERMAID_LANGUAGE_ID };\n","/**\n * Mermaid 全屏查看器:每个 document 复用一个 dialog 单例,\n * 注入渲染缓存的 SVG 源码,支持缩放、拖拽平移与键盘操作。\n */\n\nimport {\n MERMAID_VIEWER_BUTTON_CLASS_NAME,\n MERMAID_VIEWER_CANVAS_CLASS_NAME,\n MERMAID_VIEWER_CAPTION_CLASS_NAME,\n MERMAID_VIEWER_CHROME_CLASS_NAME,\n MERMAID_VIEWER_CLOSE_BUTTON_CLASS_NAME,\n MERMAID_VIEWER_CONTROLS_CLASS_NAME,\n MERMAID_VIEWER_DIALOG_CLASS_NAME,\n MERMAID_VIEWER_DRAGGING_CLASS_NAME,\n MERMAID_VIEWER_VIEWPORT_CLASS_NAME,\n MERMAID_VIEWER_ZOOMED_CLASS_NAME,\n MERMAID_VIEWER_ZOOM_VALUE_CLASS_NAME\n} from \"@scribdown/shared\";\n\nimport {\n VIEWER_CLOSE_TEXT,\n VIEWER_DEFAULT_ZOOM,\n VIEWER_FIT_RATIO,\n VIEWER_MAX_ZOOM,\n VIEWER_MIN_ZOOM,\n VIEWER_RESET_TEXT,\n VIEWER_WHEEL_ZOOM_FACTOR,\n VIEWER_WHEEL_ZOOM_MAX_DELTA,\n VIEWER_ZOOM_IN_TEXT,\n VIEWER_ZOOM_OUT_TEXT,\n VIEWER_ZOOM_STEP,\n clampMarkdownViewerZoom,\n type MarkdownViewerButtonOptions,\n type MarkdownViewerFocalPoint,\n type MarkdownViewerZoomAnchor\n} from \"../core/viewer-shared\";\n\n// Mermaid 顶部展示标签(figure chrome 与全屏查看器 caption 共用)。\n// 安家在查看器侧而非 mermaid.ts:mermaid.ts 已依赖本模块的 openMarkdownMermaidViewer,\n// 反向导出会构成循环依赖。\nconst MERMAID_LABEL_TEXT = \"Mermaid\";\n\n// 用于按 document 缓存 mermaid 全屏查看器单例的映射。\nconst mermaidViewerStateByDocument = new WeakMap<Document, MarkdownMermaidViewerState>();\n\n// 用于按 dialog 元素反查查看器状态,便于事件回调读取。\nconst mermaidViewerStateByDialogElement = new WeakMap<\n HTMLDialogElement,\n MarkdownMermaidViewerState\n>();\n\n/**\n * Mermaid 全屏查看器运行时状态。\n */\ninterface MarkdownMermaidViewerState {\n /** 全屏 dialog 容器。 */\n dialogElement: HTMLDialogElement;\n /** 顶部 caption 节点。 */\n captionElement: HTMLElement;\n /** 缩放比例显示节点。 */\n zoomValueElement: HTMLElement;\n /** 缩小按钮。 */\n zoomOutButtonElement: HTMLButtonElement;\n /** 放大按钮。 */\n zoomInButtonElement: HTMLButtonElement;\n /** 重置按钮。 */\n resetButtonElement: HTMLButtonElement;\n /** 关闭按钮。 */\n closeButtonElement: HTMLButtonElement;\n /** 滚动视口容器。 */\n viewportElement: HTMLElement;\n /** SVG 挂载画布。 */\n canvasElement: HTMLElement;\n /** SVG 的固有宽度(来自 viewBox / width 属性)。 */\n naturalWidth: number;\n /** SVG 的固有高度。 */\n naturalHeight: number;\n /** 当前缩放倍数。 */\n zoomValue: number;\n /** 当前是否处于鼠标拖拽平移状态。 */\n isDragging: boolean;\n /** 拖拽起始时鼠标的客户端 X 坐标。 */\n dragStartClientX: number;\n /** 拖拽起始时鼠标的客户端 Y 坐标。 */\n dragStartClientY: number;\n /** 拖拽起始时视口的横向滚动量。 */\n dragStartScrollLeft: number;\n /** 拖拽起始时视口的纵向滚动量。 */\n dragStartScrollTop: number;\n}\n\n/**\n * 打开 mermaid 全屏查看器。\n * @param ownerDocument 当前 figure 所在 document。\n * @param svgSource 缓存的 SVG HTML 源码。\n */\nfunction openMarkdownMermaidViewer(ownerDocument: Document, svgSource: string): void {\n // 当前 document 对应的查看器状态(单例)。\n const viewerState = getOrCreateMarkdownMermaidViewerState(ownerDocument);\n\n // 关键步骤:注入 SVG 并解析固有尺寸,做为 fit + 缩放计算基准。\n viewerState.canvasElement.innerHTML = svgSource;\n const svgDimensions = readSvgNaturalDimensions(viewerState.canvasElement);\n viewerState.naturalWidth = svgDimensions.width;\n viewerState.naturalHeight = svgDimensions.height;\n viewerState.viewportElement.scrollLeft = 0;\n viewerState.viewportElement.scrollTop = 0;\n viewerState.zoomValue = VIEWER_DEFAULT_ZOOM;\n\n showMarkdownMermaidViewerDialog(viewerState);\n requestMarkdownMermaidViewerLayout(viewerState);\n}\n\n/**\n * 解析画布内 SVG 的固有尺寸,优先读取 viewBox。\n * @param canvasElement 包含 SVG 的画布节点。\n * @returns 固有宽高,缺省回退为 720 × 480。\n */\nfunction readSvgNaturalDimensions(canvasElement: HTMLElement): {\n width: number;\n height: number;\n} {\n // 画布中第一个 SVG 节点。\n const svgElement = canvasElement.querySelector<SVGSVGElement>(\"svg\");\n\n if (!svgElement) {\n return { width: 720, height: 480 };\n }\n\n // 优先读取 viewBox.baseVal,未声明时回退到 width/height 属性。\n const viewBox = svgElement.viewBox?.baseVal;\n\n if (viewBox && viewBox.width > 0 && viewBox.height > 0) {\n return { width: viewBox.width, height: viewBox.height };\n }\n\n // SVG width/height 可能是百分比,无法直接当作固有尺寸时退化为兜底值。\n const widthAttribute = parseFloat(svgElement.getAttribute(\"width\") ?? \"\");\n const heightAttribute = parseFloat(svgElement.getAttribute(\"height\") ?? \"\");\n\n if (Number.isFinite(widthAttribute) && Number.isFinite(heightAttribute)) {\n return {\n width: widthAttribute > 0 ? widthAttribute : 720,\n height: heightAttribute > 0 ? heightAttribute : 480\n };\n }\n\n return { width: 720, height: 480 };\n}\n\n/**\n * 获取或创建当前 document 上的 mermaid 全屏查看器单例。\n * @param ownerDocument 当前 document。\n * @returns 查看器状态。\n */\nfunction getOrCreateMarkdownMermaidViewerState(\n ownerDocument: Document\n): MarkdownMermaidViewerState {\n const existingViewerState = mermaidViewerStateByDocument.get(ownerDocument);\n\n if (existingViewerState) {\n return existingViewerState;\n }\n\n const createdViewerState = createMarkdownMermaidViewerState(ownerDocument);\n\n mermaidViewerStateByDocument.set(ownerDocument, createdViewerState);\n\n return createdViewerState;\n}\n\n/**\n * 创建 mermaid 全屏查看器 DOM 与事件绑定。\n * @param ownerDocument 当前 document。\n * @returns 查看器状态。\n */\nfunction createMarkdownMermaidViewerState(ownerDocument: Document): MarkdownMermaidViewerState {\n /** 查看器外层 dialog。 */\n const dialogElement = ownerDocument.createElement(\"dialog\") as HTMLDialogElement;\n /** 顶部 chrome。 */\n const chromeElement = ownerDocument.createElement(\"div\");\n /** caption 节点。 */\n const captionElement = ownerDocument.createElement(\"p\");\n /** 控件容器。 */\n const controlsElement = ownerDocument.createElement(\"div\");\n /** 缩小按钮。 */\n const zoomOutButtonElement = createMarkdownMermaidViewerButton(ownerDocument, {\n ariaLabel: \"缩小图表\",\n text: VIEWER_ZOOM_OUT_TEXT\n });\n /** 缩放比例显示。 */\n const zoomValueElement = ownerDocument.createElement(\"span\");\n /** 放大按钮。 */\n const zoomInButtonElement = createMarkdownMermaidViewerButton(ownerDocument, {\n ariaLabel: \"放大图表\",\n text: VIEWER_ZOOM_IN_TEXT\n });\n /** 重置按钮。 */\n const resetButtonElement = createMarkdownMermaidViewerButton(ownerDocument, {\n ariaLabel: \"重置缩放\",\n text: VIEWER_RESET_TEXT\n });\n /** 关闭按钮。 */\n const closeButtonElement = createMarkdownMermaidViewerButton(ownerDocument, {\n ariaLabel: \"关闭全屏查看\",\n className: MERMAID_VIEWER_CLOSE_BUTTON_CLASS_NAME,\n text: VIEWER_CLOSE_TEXT\n });\n /** 滚动视口。 */\n const viewportElement = ownerDocument.createElement(\"div\");\n /** SVG 画布。 */\n const canvasElement = ownerDocument.createElement(\"div\");\n\n /** 查看器运行时状态。 */\n const viewerState: MarkdownMermaidViewerState = {\n dialogElement,\n captionElement,\n zoomValueElement,\n zoomOutButtonElement,\n zoomInButtonElement,\n resetButtonElement,\n closeButtonElement,\n viewportElement,\n canvasElement,\n naturalWidth: 720,\n naturalHeight: 480,\n zoomValue: VIEWER_DEFAULT_ZOOM,\n isDragging: false,\n dragStartClientX: 0,\n dragStartClientY: 0,\n dragStartScrollLeft: 0,\n dragStartScrollTop: 0\n };\n /** 当前 document 对应的 window。 */\n const ownerWindow = ownerDocument.defaultView;\n\n dialogElement.className = MERMAID_VIEWER_DIALOG_CLASS_NAME;\n dialogElement.setAttribute(\"aria-modal\", \"true\");\n dialogElement.setAttribute(\"aria-label\", \"全屏查看 Mermaid 图表\");\n dialogElement.setAttribute(\"tabindex\", \"-1\");\n chromeElement.className = MERMAID_VIEWER_CHROME_CLASS_NAME;\n captionElement.className = MERMAID_VIEWER_CAPTION_CLASS_NAME;\n captionElement.textContent = MERMAID_LABEL_TEXT;\n controlsElement.className = MERMAID_VIEWER_CONTROLS_CLASS_NAME;\n zoomValueElement.className = MERMAID_VIEWER_ZOOM_VALUE_CLASS_NAME;\n viewportElement.className = MERMAID_VIEWER_VIEWPORT_CLASS_NAME;\n canvasElement.className = MERMAID_VIEWER_CANVAS_CLASS_NAME;\n\n mermaidViewerStateByDialogElement.set(dialogElement, viewerState);\n controlsElement.append(\n zoomOutButtonElement,\n zoomValueElement,\n zoomInButtonElement,\n resetButtonElement,\n closeButtonElement\n );\n chromeElement.append(captionElement, controlsElement);\n viewportElement.append(canvasElement);\n dialogElement.append(chromeElement, viewportElement);\n ownerDocument.body.append(dialogElement);\n\n dialogElement.addEventListener(\"click\", handleMarkdownMermaidViewerBackdropClick);\n dialogElement.addEventListener(\"close\", handleMarkdownMermaidViewerClose);\n dialogElement.addEventListener(\"keydown\", handleMarkdownMermaidViewerKeyDown);\n viewportElement.addEventListener(\"wheel\", handleMarkdownMermaidViewerWheel, { passive: false });\n viewportElement.addEventListener(\"pointerdown\", handleMarkdownMermaidViewerPointerDown);\n viewportElement.addEventListener(\"pointermove\", handleMarkdownMermaidViewerPointerMove);\n viewportElement.addEventListener(\"pointerup\", handleMarkdownMermaidViewerPointerUp);\n viewportElement.addEventListener(\"pointercancel\", handleMarkdownMermaidViewerPointerUp);\n zoomOutButtonElement.addEventListener(\"click\", handleMarkdownMermaidViewerZoomOutClick);\n zoomInButtonElement.addEventListener(\"click\", handleMarkdownMermaidViewerZoomInClick);\n resetButtonElement.addEventListener(\"click\", handleMarkdownMermaidViewerResetClick);\n closeButtonElement.addEventListener(\"click\", handleMarkdownMermaidViewerCloseClick);\n\n if (ownerWindow) {\n ownerWindow.addEventListener(\"resize\", handleMarkdownMermaidViewerWindowResize);\n }\n\n updateMarkdownMermaidViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);\n\n return viewerState;\n}\n\n/**\n * 创建查看器按钮。\n * @param ownerDocument 当前 document。\n * @param options 按钮文案与可访问名称。\n * @returns 已配置的按钮元素。\n */\nfunction createMarkdownMermaidViewerButton(\n ownerDocument: Document,\n options: MarkdownViewerButtonOptions\n): HTMLButtonElement {\n const buttonElement = ownerDocument.createElement(\"button\");\n buttonElement.type = \"button\";\n buttonElement.className = options.className\n ? `${MERMAID_VIEWER_BUTTON_CLASS_NAME} ${options.className}`\n : MERMAID_VIEWER_BUTTON_CLASS_NAME;\n buttonElement.textContent = options.text;\n buttonElement.setAttribute(\"aria-label\", options.ariaLabel);\n return buttonElement;\n}\n\n/**\n * 显示 mermaid 全屏查看器 dialog。\n * @param viewerState 查看器状态。\n */\nfunction showMarkdownMermaidViewerDialog(viewerState: MarkdownMermaidViewerState): void {\n if (typeof viewerState.dialogElement.showModal === \"function\") {\n if (!viewerState.dialogElement.open) {\n viewerState.dialogElement.showModal();\n }\n } else {\n viewerState.dialogElement.setAttribute(\"open\", \"\");\n }\n viewerState.dialogElement.focus();\n}\n\n/**\n * 延后一次布局计算,确保 dialog 已经进入可测量状态。\n * @param viewerState 查看器状态。\n */\nfunction requestMarkdownMermaidViewerLayout(viewerState: MarkdownMermaidViewerState): void {\n const ownerWindow = viewerState.dialogElement.ownerDocument.defaultView;\n\n if (!ownerWindow) {\n updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue);\n return;\n }\n\n ownerWindow.setTimeout(handleMarkdownMermaidViewerDeferredLayout, 0, viewerState);\n}\n\n/**\n * 处理延后的布局计算。\n * @param viewerState 查看器状态。\n */\nfunction handleMarkdownMermaidViewerDeferredLayout(\n viewerState: MarkdownMermaidViewerState\n): void {\n updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue);\n}\n\n/**\n * 点击遮罩关闭查看器。\n * @param event dialog 点击事件。\n */\nfunction handleMarkdownMermaidViewerBackdropClick(event: MouseEvent): void {\n if (event.target !== event.currentTarget) {\n return;\n }\n const dialogElement = event.currentTarget as HTMLDialogElement;\n const viewerState = mermaidViewerStateByDialogElement.get(dialogElement);\n if (!viewerState) {\n return;\n }\n closeMarkdownMermaidViewer(viewerState);\n}\n\n/**\n * dialog close 后复位临时状态。\n * @param event dialog close 事件。\n */\nfunction handleMarkdownMermaidViewerClose(event: Event): void {\n const dialogElement = event.currentTarget as HTMLDialogElement;\n const viewerState = mermaidViewerStateByDialogElement.get(dialogElement);\n if (!viewerState) {\n return;\n }\n resetMarkdownMermaidViewerAfterClose(viewerState);\n}\n\n/**\n * 关闭后复位查看器内部临时状态(清空画布、重置缩放)。\n * @param viewerState 查看器状态。\n */\nfunction resetMarkdownMermaidViewerAfterClose(viewerState: MarkdownMermaidViewerState): void {\n viewerState.canvasElement.replaceChildren();\n viewerState.viewportElement.scrollLeft = 0;\n viewerState.viewportElement.scrollTop = 0;\n viewerState.zoomValue = VIEWER_DEFAULT_ZOOM;\n viewerState.isDragging = false;\n viewerState.dialogElement.classList.remove(MERMAID_VIEWER_DRAGGING_CLASS_NAME);\n viewerState.dialogElement.classList.remove(MERMAID_VIEWER_ZOOMED_CLASS_NAME);\n}\n\n/**\n * 处理键盘缩放和关闭。\n * @param event 键盘事件。\n */\nfunction handleMarkdownMermaidViewerKeyDown(event: KeyboardEvent): void {\n const viewerState = getMarkdownMermaidViewerStateFromEvent(event);\n if (!viewerState) {\n return;\n }\n\n if (event.key === \"+\" || event.key === \"=\") {\n event.preventDefault();\n updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue + VIEWER_ZOOM_STEP);\n return;\n }\n\n if (event.key === \"-\") {\n event.preventDefault();\n updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue - VIEWER_ZOOM_STEP);\n return;\n }\n\n if (event.key === \"0\") {\n event.preventDefault();\n updateMarkdownMermaidViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);\n return;\n }\n\n if (event.key === \"Escape\") {\n event.preventDefault();\n closeMarkdownMermaidViewer(viewerState);\n }\n}\n\n/**\n * 处理 ctrl/cmd + 滚轮缩放图表。\n * @param event 视口滚轮事件。\n */\nfunction handleMarkdownMermaidViewerWheel(event: WheelEvent): void {\n if (!event.ctrlKey && !event.metaKey) {\n return;\n }\n const viewerState = getMarkdownMermaidViewerStateFromEvent(event);\n if (!viewerState) {\n return;\n }\n // 与 deltaY 成比例并夹紧到上限,让触控板细滑动产生细粒度缩放、鼠标滚轮单格仍可见。\n const rawDelta = -event.deltaY * VIEWER_WHEEL_ZOOM_FACTOR;\n const zoomDelta = Math.max(\n -VIEWER_WHEEL_ZOOM_MAX_DELTA,\n Math.min(VIEWER_WHEEL_ZOOM_MAX_DELTA, rawDelta)\n );\n event.preventDefault();\n updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue + zoomDelta, {\n x: event.clientX,\n y: event.clientY\n });\n}\n\n/**\n * 处理鼠标按下开启拖拽平移。\n * @param event 视口指针事件。\n */\nfunction handleMarkdownMermaidViewerPointerDown(event: PointerEvent): void {\n if (event.button !== 0) {\n return;\n }\n const viewportElement = event.currentTarget as HTMLElement;\n const viewerState = getMarkdownMermaidViewerStateFromEvent(event);\n if (!viewerState) {\n return;\n }\n const canScrollHorizontally = viewportElement.scrollWidth > viewportElement.clientWidth;\n const canScrollVertically = viewportElement.scrollHeight > viewportElement.clientHeight;\n if (!canScrollHorizontally && !canScrollVertically) {\n return;\n }\n viewerState.isDragging = true;\n viewerState.dragStartClientX = event.clientX;\n viewerState.dragStartClientY = event.clientY;\n viewerState.dragStartScrollLeft = viewportElement.scrollLeft;\n viewerState.dragStartScrollTop = viewportElement.scrollTop;\n viewerState.dialogElement.classList.add(MERMAID_VIEWER_DRAGGING_CLASS_NAME);\n if (typeof viewportElement.setPointerCapture === \"function\") {\n viewportElement.setPointerCapture(event.pointerId);\n }\n event.preventDefault();\n}\n\n/**\n * 处理鼠标移动跟随滚动。\n * @param event 视口指针事件。\n */\nfunction handleMarkdownMermaidViewerPointerMove(event: PointerEvent): void {\n const viewportElement = event.currentTarget as HTMLElement;\n const viewerState = getMarkdownMermaidViewerStateFromEvent(event);\n if (!viewerState || !viewerState.isDragging) {\n return;\n }\n const deltaX = event.clientX - viewerState.dragStartClientX;\n const deltaY = event.clientY - viewerState.dragStartClientY;\n viewportElement.scrollLeft = viewerState.dragStartScrollLeft - deltaX;\n viewportElement.scrollTop = viewerState.dragStartScrollTop - deltaY;\n}\n\n/**\n * 处理鼠标抬起/取消,结束拖拽。\n * @param event 视口指针事件。\n */\nfunction handleMarkdownMermaidViewerPointerUp(event: PointerEvent): void {\n const viewportElement = event.currentTarget as HTMLElement;\n const viewerState = getMarkdownMermaidViewerStateFromEvent(event);\n if (!viewerState || !viewerState.isDragging) {\n return;\n }\n viewerState.isDragging = false;\n viewerState.dialogElement.classList.remove(MERMAID_VIEWER_DRAGGING_CLASS_NAME);\n if (\n typeof viewportElement.releasePointerCapture === \"function\" &&\n viewportElement.hasPointerCapture(event.pointerId)\n ) {\n viewportElement.releasePointerCapture(event.pointerId);\n }\n}\n\n/**\n * 处理 window resize 后的画布尺寸适配。\n * @param event window resize 事件。\n */\nfunction handleMarkdownMermaidViewerWindowResize(event: Event): void {\n const ownerWindow = event.currentTarget as Window;\n const viewerState = mermaidViewerStateByDocument.get(ownerWindow.document);\n if (!viewerState || !isMarkdownMermaidViewerOpen(viewerState)) {\n return;\n }\n updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue);\n}\n\n/**\n * 处理缩小按钮点击。\n * @param event 按钮点击事件。\n */\nfunction handleMarkdownMermaidViewerZoomOutClick(event: MouseEvent): void {\n const viewerState = getMarkdownMermaidViewerStateFromEvent(event);\n if (!viewerState) {\n return;\n }\n updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue - VIEWER_ZOOM_STEP);\n}\n\n/**\n * 处理放大按钮点击。\n * @param event 按钮点击事件。\n */\nfunction handleMarkdownMermaidViewerZoomInClick(event: MouseEvent): void {\n const viewerState = getMarkdownMermaidViewerStateFromEvent(event);\n if (!viewerState) {\n return;\n }\n updateMarkdownMermaidViewerZoom(viewerState, viewerState.zoomValue + VIEWER_ZOOM_STEP);\n}\n\n/**\n * 处理重置按钮点击。\n * @param event 按钮点击事件。\n */\nfunction handleMarkdownMermaidViewerResetClick(event: MouseEvent): void {\n const viewerState = getMarkdownMermaidViewerStateFromEvent(event);\n if (!viewerState) {\n return;\n }\n updateMarkdownMermaidViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);\n}\n\n/**\n * 处理关闭按钮点击。\n * @param event 按钮点击事件。\n */\nfunction handleMarkdownMermaidViewerCloseClick(event: MouseEvent): void {\n const viewerState = getMarkdownMermaidViewerStateFromEvent(event);\n if (!viewerState) {\n return;\n }\n closeMarkdownMermaidViewer(viewerState);\n}\n\n/**\n * 从事件回溯查看器状态。\n * @param event DOM 事件。\n * @returns 当前事件所属的查看器状态。\n */\nfunction getMarkdownMermaidViewerStateFromEvent(\n event: Event\n): MarkdownMermaidViewerState | undefined {\n // 优先沿祖先链找到所属 dialog。\n const targetElement = event.target as HTMLElement | null;\n const dialogElement =\n targetElement?.closest<HTMLDialogElement>(`.${MERMAID_VIEWER_DIALOG_CLASS_NAME}`) ?? null;\n\n if (!dialogElement) {\n return undefined;\n }\n return mermaidViewerStateByDialogElement.get(dialogElement);\n}\n\n/**\n * 关闭 mermaid 全屏查看器。\n * @param viewerState 查看器状态。\n */\nfunction closeMarkdownMermaidViewer(viewerState: MarkdownMermaidViewerState): void {\n if (typeof viewerState.dialogElement.close === \"function\" && viewerState.dialogElement.open) {\n viewerState.dialogElement.close();\n return;\n }\n viewerState.dialogElement.removeAttribute(\"open\");\n resetMarkdownMermaidViewerAfterClose(viewerState);\n}\n\n/**\n * 当前查看器是否处于打开态。\n * @param viewerState 查看器状态。\n * @returns dialog 是否打开。\n */\nfunction isMarkdownMermaidViewerOpen(viewerState: MarkdownMermaidViewerState): boolean {\n return viewerState.dialogElement.open === true;\n}\n\n/**\n * 更新查看器缩放比例与画布尺寸。\n * @param viewerState 查看器状态。\n * @param nextZoom 目标缩放倍数。\n * @param zoomAnchor 可选的缩放焦点(客户端坐标)。\n */\nfunction updateMarkdownMermaidViewerZoom(\n viewerState: MarkdownMermaidViewerState,\n nextZoom: number,\n zoomAnchor?: MarkdownViewerZoomAnchor\n): void {\n /** 归一化的缩放倍数。 */\n const normalizedZoom = clampMarkdownViewerZoom(nextZoom);\n /** 缩放前的焦点。 */\n const focalPoint = isMarkdownMermaidViewerOpen(viewerState)\n ? captureMarkdownMermaidViewerFocalPoint(viewerState, zoomAnchor)\n : undefined;\n\n viewerState.zoomValue = normalizedZoom;\n viewerState.dialogElement.classList.toggle(\n MERMAID_VIEWER_ZOOMED_CLASS_NAME,\n normalizedZoom > VIEWER_DEFAULT_ZOOM\n );\n viewerState.zoomValueElement.textContent = `${Math.round(normalizedZoom * 100)}%`;\n viewerState.zoomOutButtonElement.disabled = normalizedZoom <= VIEWER_MIN_ZOOM;\n viewerState.zoomInButtonElement.disabled = normalizedZoom >= VIEWER_MAX_ZOOM;\n viewerState.resetButtonElement.disabled = normalizedZoom === VIEWER_DEFAULT_ZOOM;\n updateMarkdownMermaidViewerCanvasSize(viewerState);\n\n if (focalPoint) {\n applyMarkdownMermaidViewerFocalPoint(viewerState, focalPoint);\n }\n}\n\n/**\n * 记录缩放前的焦点:把客户端坐标转成画布内的归一化位置。\n * @param viewerState 查看器状态。\n * @param zoomAnchor 客户端坐标锚点。\n * @returns 焦点信息,画布未布局时返回 undefined。\n */\nfunction captureMarkdownMermaidViewerFocalPoint(\n viewerState: MarkdownMermaidViewerState,\n zoomAnchor?: MarkdownViewerZoomAnchor\n): MarkdownViewerFocalPoint | undefined {\n /** 缩放前画布矩形。 */\n const preCanvasRect = viewerState.canvasElement.getBoundingClientRect();\n if (preCanvasRect.width <= 0 || preCanvasRect.height <= 0) {\n return undefined;\n }\n const viewportRect = viewerState.viewportElement.getBoundingClientRect();\n const anchorClientX = zoomAnchor?.x ?? viewportRect.left + viewportRect.width / 2;\n const anchorClientY = zoomAnchor?.y ?? viewportRect.top + viewportRect.height / 2;\n const normalizedX = (anchorClientX - preCanvasRect.left) / preCanvasRect.width;\n const normalizedY = (anchorClientY - preCanvasRect.top) / preCanvasRect.height;\n return { anchorClientX, anchorClientY, normalizedX, normalizedY };\n}\n\n/**\n * 缩放后调整滚动量,使焦点保持在原客户端位置。\n * @param viewerState 查看器状态。\n * @param focalPoint 缩放前记录的焦点。\n */\nfunction applyMarkdownMermaidViewerFocalPoint(\n viewerState: MarkdownMermaidViewerState,\n focalPoint: MarkdownViewerFocalPoint\n): void {\n viewerState.viewportElement.scrollLeft = 0;\n viewerState.viewportElement.scrollTop = 0;\n const postCanvasRect = viewerState.canvasElement.getBoundingClientRect();\n if (postCanvasRect.width <= 0 || postCanvasRect.height <= 0) {\n return;\n }\n const targetCanvasClientLeft =\n focalPoint.anchorClientX - focalPoint.normalizedX * postCanvasRect.width;\n const targetCanvasClientTop =\n focalPoint.anchorClientY - focalPoint.normalizedY * postCanvasRect.height;\n viewerState.viewportElement.scrollLeft = postCanvasRect.left - targetCanvasClientLeft;\n viewerState.viewportElement.scrollTop = postCanvasRect.top - targetCanvasClientTop;\n}\n\n/**\n * 根据视口与缩放比例更新画布尺寸。\n * @param viewerState 查看器状态。\n */\nfunction updateMarkdownMermaidViewerCanvasSize(viewerState: MarkdownMermaidViewerState): void {\n const viewportWidth = Math.max(\n viewerState.viewportElement.clientWidth * VIEWER_FIT_RATIO,\n 1\n );\n const viewportHeight = Math.max(\n viewerState.viewportElement.clientHeight * VIEWER_FIT_RATIO,\n 1\n );\n const naturalWidth = Math.max(viewerState.naturalWidth, 1);\n const naturalHeight = Math.max(viewerState.naturalHeight, 1);\n // fit 缩放:在不放大原图的前提下让整张图适配视口。\n const fitScale = Math.min(1, viewportWidth / naturalWidth, viewportHeight / naturalHeight);\n const displayWidth = Math.max(1, Math.round(naturalWidth * fitScale * viewerState.zoomValue));\n const displayHeight = Math.max(1, Math.round(naturalHeight * fitScale * viewerState.zoomValue));\n\n viewerState.canvasElement.style.width = `${displayWidth}px`;\n viewerState.canvasElement.style.height = `${displayHeight}px`;\n\n // 关键步骤:让内部 SVG 撑满 canvas,保持等比缩放。\n const innerSvgElement = viewerState.canvasElement.querySelector<SVGSVGElement>(\"svg\");\n if (innerSvgElement) {\n innerSvgElement.removeAttribute(\"width\");\n innerSvgElement.removeAttribute(\"height\");\n innerSvgElement.style.width = \"100%\";\n innerSvgElement.style.height = \"100%\";\n innerSvgElement.style.maxWidth = \"none\";\n innerSvgElement.style.display = \"block\";\n }\n}\n\nexport { openMarkdownMermaidViewer, MERMAID_LABEL_TEXT };\n","/**\n * 图片 / Mermaid 全屏查看器共用的缩放常量、焦点类型与缩放限制工具。\n */\n\n/**\n * 缩放前记录的视口焦点信息,用于保持光标/视口中心在缩放后位置不变。\n */\ninterface MarkdownViewerFocalPoint {\n /** 焦点在客户端坐标系的 X。 */\n anchorClientX: number;\n /** 焦点在客户端坐标系的 Y。 */\n anchorClientY: number;\n /** 焦点在内容(图片 / 画布)内的归一化 X (0..1)。 */\n normalizedX: number;\n /** 焦点在内容(图片 / 画布)内的归一化 Y (0..1)。 */\n normalizedY: number;\n}\n\n/**\n * 触发缩放时的可选锚点(鼠标客户端坐标)。\n */\ninterface MarkdownViewerZoomAnchor {\n x: number;\n y: number;\n}\n\n/**\n * 全屏查看器按钮配置。\n */\ninterface MarkdownViewerButtonOptions {\n ariaLabel: string;\n className?: string;\n text: string;\n}\n\n// 查看器默认缩放倍数。\nconst VIEWER_DEFAULT_ZOOM = 1;\n\n// 查看器最小缩放倍数。\nconst VIEWER_MIN_ZOOM = 0.25;\n\n// 查看器最大缩放倍数。\nconst VIEWER_MAX_ZOOM = 4;\n\n// 查看器每次缩放步进。\nconst VIEWER_ZOOM_STEP = 0.25;\n\n// 滚轮 deltaY 转换为缩放增量的比例系数:触控板单次事件 deltaY 通常较小(个位数),\n// 选 0.01 让一次轻微滑动只产生 ~1%-9% 的缩放变化,避免触控板上跳变过大。\nconst VIEWER_WHEEL_ZOOM_FACTOR = 0.01;\n\n// 单次滚轮事件允许的最大缩放变化量,防止鼠标滚轮一格 deltaY=100 时跳得过远。\nconst VIEWER_WHEEL_ZOOM_MAX_DELTA = 0.1;\n\n// 查看器适配视口时预留的安全比例。\nconst VIEWER_FIT_RATIO = 0.92;\n\n// 查看器关闭按钮文本。\nconst VIEWER_CLOSE_TEXT = \"x\";\n\n// 查看器放大按钮文本。\nconst VIEWER_ZOOM_IN_TEXT = \"+\";\n\n// 查看器缩小按钮文本。\nconst VIEWER_ZOOM_OUT_TEXT = \"-\";\n\n// 查看器重置按钮文本。\nconst VIEWER_RESET_TEXT = \"1:1\";\n\n/**\n * 限制查看器缩放倍数。\n * @param zoomValue 待限制的缩放倍数。\n * @returns 限制后的缩放倍数。\n */\nfunction clampMarkdownViewerZoom(zoomValue: number): number {\n return Math.min(VIEWER_MAX_ZOOM, Math.max(VIEWER_MIN_ZOOM, zoomValue));\n}\n\nexport type { MarkdownViewerFocalPoint, MarkdownViewerZoomAnchor, MarkdownViewerButtonOptions };\nexport {\n VIEWER_DEFAULT_ZOOM,\n VIEWER_MIN_ZOOM,\n VIEWER_MAX_ZOOM,\n VIEWER_ZOOM_STEP,\n VIEWER_WHEEL_ZOOM_FACTOR,\n VIEWER_WHEEL_ZOOM_MAX_DELTA,\n VIEWER_FIT_RATIO,\n VIEWER_CLOSE_TEXT,\n VIEWER_ZOOM_IN_TEXT,\n VIEWER_ZOOM_OUT_TEXT,\n VIEWER_RESET_TEXT,\n clampMarkdownViewerZoom\n};\n","/**\n * remark 插件:为块级节点标注 data-source-line 源码行锚点,\n * 供编辑器与预览的双向滚动对齐使用。\n */\n\nimport { SOURCE_LINE_DATA_ATTRIBUTE } from \"@scribdown/shared\";\n\nimport type { Blockquote, Data, List, ListItem, Node, Root, RootContent } from \"mdast\";\n// 关键步骤:加载 mdast-util-to-hast 的类型副作用,使 Data 拥有 hProperties 增强。\nimport type { Properties } from \"hast\";\nimport type {} from \"mdast-util-to-hast\";\n\n/**\n * 源码行号在 hast 节点上的属性名(camelCase)。\n * rehype-raw 会把 DOM 属性 data-source-line 回解析为该 camelCase 形式,\n * sanitize 白名单与 hProperties 注入均需以此形式匹配,序列化后仍输出 data-source-line。\n */\nconst SOURCE_LINE_HAST_PROPERTY = SOURCE_LINE_DATA_ATTRIBUTE.replace(\n /-([a-z])/gu,\n (_match: string, letter: string) => letter.toUpperCase()\n);\n\n/**\n * 判断是否为块级容器节点:这些节点的子节点同样是块级节点,需递归下钻标注源码行锚点,\n * 以覆盖列表中的代码块、嵌套引用等场景。\n * @param node 待判断节点。\n * @returns 是否为块级容器节点。\n */\nfunction isBlockContainerNode(node: RootContent): node is Blockquote | List | ListItem {\n return node.type === \"blockquote\" || node.type === \"list\" || node.type === \"listItem\";\n}\n\n/**\n * remark 插件:为块级节点标注源码起始行号,递归覆盖嵌套块级结构。\n * 通过 hProperties 注入 data-source-line 属性,供编辑器与预览的双向滚动对齐使用。\n * @returns Markdown AST 转换器。\n */\nfunction remarkSourceLine(): (tree: Root) => void {\n return (tree: Root) => {\n // 顶层块级节点列表。\n const blockNodes = tree.children;\n\n blockNodes.forEach((blockNode) => {\n annotateSourceLine(blockNode);\n\n // 关键步骤:列表项、引用等不在顶层,递归下钻为内部块级节点标注源码行锚点。\n annotateNestedBlockSourceLines(blockNode);\n });\n };\n}\n\n/**\n * 为单个节点注入 data-source-line 源码行锚点。\n * @param node 目标 Markdown 节点。\n */\nfunction annotateSourceLine(node: Node): void {\n // 当前节点的源码起始行号(1-based)。\n const startLine = node.position?.start.line;\n\n // 仅标注带源码位置的节点;TOC、定义列表、图片 figure 等转换节点已显式保留原段落位置,\n // 真正无源码位置的纯生成节点自动跳过。\n if (typeof startLine !== \"number\") {\n return;\n }\n\n // 节点 HTML 转换元数据容器。\n const nodeData: Data = node.data ?? {};\n // 节点 hast 属性容器。\n const hProperties: Properties = nodeData.hProperties ?? {};\n\n hProperties[SOURCE_LINE_HAST_PROPERTY] = startLine;\n nodeData.hProperties = hProperties;\n node.data = nodeData;\n}\n\n/**\n * 递归为块级容器节点内部的子节点标注源码行锚点。\n * 覆盖列表项、嵌套子列表、引用内的代码块与嵌套引用等场景。\n * @param node 当前块级节点。\n */\nfunction annotateNestedBlockSourceLines(node: RootContent): void {\n // 仅块级容器节点的子节点为块级节点,非容器节点无需下钻。\n if (!isBlockContainerNode(node)) {\n return;\n }\n\n // 容器节点的直接子节点。\n const childNodes = node.children;\n\n childNodes.forEach((childNode) => {\n annotateSourceLine(childNode);\n annotateNestedBlockSourceLines(childNode);\n });\n}\n\nexport { SOURCE_LINE_HAST_PROPERTY, remarkSourceLine };\n","/**\n * 图片渲染:remark 插件把独占段落的图片转换为 figure 结构(含失败态占位),\n * hydrate 阶段为图片绑定加载/失败状态类与全图查看入口。\n */\n\nimport {\n IMAGE_CAPTION_CLASS_NAME,\n IMAGE_ELEMENT_CLASS_NAME,\n IMAGE_FALLBACK_CLASS_NAME,\n IMAGE_FALLBACK_ICON_CLASS_NAME,\n IMAGE_FALLBACK_SOURCE_CLASS_NAME,\n IMAGE_FALLBACK_TEXT_CLASS_NAME,\n IMAGE_FIGURE_CLASS_NAME,\n IMAGE_FRAME_CLASS_NAME,\n IMAGE_FRAME_FAILED_CLASS_NAME,\n IMAGE_FRAME_LOADED_CLASS_NAME\n} from \"@scribdown/shared\";\n\nimport type { Image, ImageReference, Paragraph, Root } from \"mdast\";\nimport { SKIP, visit } from \"unist-util-visit\";\n\nimport {\n isImageNode,\n type ImageFallback,\n type ImageFigure,\n type ImageFrame,\n type ImageCaption\n} from \"../core/ast\";\nimport { bindMarkdownImageViewer } from \"./image-viewer\";\n\n// 图片运行时已绑定标记的 dataset 键。\nconst IMAGE_HYDRATED_DATA_KEY = \"scribdownImageHydrated\";\n\n/**\n * remark 插件:把独占段落的图片转换为 figure 结构。\n * @returns Markdown AST 转换器。\n */\nfunction remarkImageFigures(): (tree: Root) => void {\n return (tree: Root) => {\n // 关键步骤:先把独占图片段落整体替换为 figure(跳过内部,避免重复装饰),\n // 再为剩余的行内图片补统一 class。\n visit(tree, \"paragraph\", (node, index, parent) => {\n if (parent === undefined || index === undefined) {\n return;\n }\n\n // 当前图片段落转换出的 figure 节点。\n const imageFigureNode = createImageFigureNode(node);\n\n if (imageFigureNode) {\n parent.children[index] = imageFigureNode;\n return SKIP;\n }\n });\n\n visit(tree, isImageNode, (node) => {\n decorateImageNode(node);\n });\n };\n}\n\n/**\n * 尝试把独占图片段落转换为 figure 节点。\n * @param node 待转换的段落节点。\n * @returns 转换后的 figure 节点,不匹配时返回 undefined。\n */\nfunction createImageFigureNode(node: Paragraph): ImageFigure | undefined {\n if (node.children.length !== 1) {\n return undefined;\n }\n\n // 段落内唯一的行内节点。\n const onlyChild = node.children[0];\n\n if (!isImageNode(onlyChild)) {\n return undefined;\n }\n\n decorateImageNode(onlyChild);\n\n return {\n type: \"imageFigure\",\n // 关键步骤:保留原段落源码位置,使 remarkSourceLine 能为 figure 标注 data-source-line。\n position: node.position,\n data: {\n hName: \"figure\",\n hProperties: {\n className: [IMAGE_FIGURE_CLASS_NAME]\n }\n },\n children: [createImageFrameNode(onlyChild), ...createImageCaptionNodes(onlyChild)]\n };\n}\n\n/**\n * 给图片节点写入统一 class。\n * @param imageNode 图片节点。\n */\nfunction decorateImageNode(imageNode: Image | ImageReference): void {\n imageNode.data = {\n ...imageNode.data,\n hProperties: {\n ...imageNode.data?.hProperties,\n className: [IMAGE_ELEMENT_CLASS_NAME]\n }\n };\n}\n\n/**\n * 创建图片边框容器节点。\n * @param imageNode 图片节点。\n * @returns 图片边框容器节点。\n */\nfunction createImageFrameNode(imageNode: Image | ImageReference): ImageFrame {\n return {\n type: \"imageFrame\",\n data: {\n hName: \"span\",\n hProperties: {\n className: [IMAGE_FRAME_CLASS_NAME]\n }\n },\n children: [imageNode, createImageFallbackNode(imageNode)]\n };\n}\n\n/**\n * 创建图片标题节点列表。\n * @param imageNode 图片节点。\n * @returns 图片标题节点列表。\n */\nfunction createImageCaptionNodes(imageNode: Image | ImageReference): ImageCaption[] {\n // 图片 title 属性文本,用于生成 figcaption;引用式图片的 title 在定义节点上,此处无 caption。\n const imageTitle =\n imageNode.type === \"image\" && typeof imageNode.title === \"string\"\n ? imageNode.title.trim()\n : \"\";\n\n if (imageTitle.length === 0) {\n return [];\n }\n\n return [\n {\n type: \"imageCaption\",\n data: {\n hName: \"figcaption\",\n hProperties: {\n className: [IMAGE_CAPTION_CLASS_NAME]\n }\n },\n children: [{ type: \"text\", value: imageTitle }]\n }\n ];\n}\n\n/**\n * 创建图片失败态占位节点。\n * @param imageNode 图片节点。\n * @returns 图片失败态占位节点。\n */\nfunction createImageFallbackNode(imageNode: Image | ImageReference): ImageFallback {\n // 图片失败态展示的 alt 文本。\n const fallbackText = imageNode.alt?.trim() || \"图片加载失败\";\n // 图片失败态展示的来源路径:直接图片取 url,引用式图片取引用标识。\n const fallbackSource = imageNode.type === \"image\" ? imageNode.url : imageNode.identifier;\n\n return {\n type: \"imageFallback\",\n data: {\n hName: \"span\",\n hProperties: {\n className: [IMAGE_FALLBACK_CLASS_NAME]\n }\n },\n children: [\n {\n type: \"imageFallbackIcon\",\n data: {\n hName: \"span\",\n hProperties: {\n className: [IMAGE_FALLBACK_ICON_CLASS_NAME]\n }\n }\n },\n {\n type: \"imageFallbackText\",\n data: {\n hName: \"span\",\n hProperties: {\n className: [IMAGE_FALLBACK_TEXT_CLASS_NAME]\n }\n },\n children: [{ type: \"text\", value: fallbackText }]\n },\n {\n type: \"imageFallbackSource\",\n data: {\n hName: \"span\",\n hProperties: {\n className: [IMAGE_FALLBACK_SOURCE_CLASS_NAME]\n }\n },\n children: [{ type: \"text\", value: fallbackSource }]\n }\n ]\n };\n}\n\n/**\n * 给渲染后的图片绑定加载状态类名。\n * @param rootElement 包含 Markdown 渲染结果的根节点。\n */\nfunction hydrateMarkdownImages(rootElement: ParentNode): void {\n // 当前根节点内的所有图片元素。\n const imageElements = rootElement.querySelectorAll<HTMLImageElement>(\n `img.${IMAGE_ELEMENT_CLASS_NAME}`\n );\n\n imageElements.forEach(bindMarkdownImageState);\n}\n\n/**\n * 给单个图片元素绑定加载、失败状态与全图查看行为。\n * @param imageElement 待绑定状态的图片元素。\n */\nfunction bindMarkdownImageState(imageElement: HTMLImageElement): void {\n updateMarkdownImageState(imageElement);\n\n if (imageElement.dataset[IMAGE_HYDRATED_DATA_KEY] === \"true\") {\n return;\n }\n\n imageElement.dataset[IMAGE_HYDRATED_DATA_KEY] = \"true\";\n imageElement.addEventListener(\"load\", handleMarkdownImageLoad);\n imageElement.addEventListener(\"error\", handleMarkdownImageError);\n bindMarkdownImageViewer(imageElement);\n}\n\n/**\n * 处理图片加载成功事件。\n * @param event 图片加载事件。\n */\nfunction handleMarkdownImageLoad(event: Event): void {\n // 触发加载事件的图片元素。\n const imageElement = event.currentTarget as HTMLImageElement;\n\n updateMarkdownImageState(imageElement);\n}\n\n/**\n * 处理图片加载失败事件。\n * @param event 图片加载失败事件。\n */\nfunction handleMarkdownImageError(event: Event): void {\n // 触发失败事件的图片元素。\n const imageElement = event.currentTarget as HTMLImageElement;\n\n updateMarkdownImageState(imageElement);\n}\n\n/**\n * 根据图片当前加载结果更新 frame 状态类。\n * @param imageElement 待更新状态的图片元素。\n */\nfunction updateMarkdownImageState(imageElement: HTMLImageElement): void {\n // 图片外层 frame 元素。\n const frameElement = imageElement.closest<HTMLElement>(`.${IMAGE_FRAME_CLASS_NAME}`);\n\n if (!frameElement) {\n return;\n }\n\n // 当前图片是否已确认加载失败。\n const isFailed = imageElement.complete && imageElement.naturalWidth === 0;\n // 当前图片是否已确认加载完成。\n const isLoaded = imageElement.complete && imageElement.naturalWidth > 0;\n\n frameElement.classList.toggle(IMAGE_FRAME_FAILED_CLASS_NAME, isFailed);\n frameElement.classList.toggle(IMAGE_FRAME_LOADED_CLASS_NAME, isLoaded);\n}\n\nexport { remarkImageFigures, hydrateMarkdownImages };\n","/**\n * mdast 官方类型的项目级扩展:用 declaration merging(@types/mdast 的官方扩展机制)\n * 注册各渲染插件生成的自定义节点,并提供跨插件共享的少量节点工具。\n * 遍历统一用 unist-util-visit,文本提取统一用 mdast-util-to-string。\n */\n\nimport type {\n BlockContent,\n DefinitionContent,\n Image,\n ImageReference,\n Link,\n Node,\n Parent,\n PhrasingContent,\n Text\n} from \"mdast\";\n// 关键步骤:加载 mdast-util-to-hast 的类型副作用,为 mdast Data 合入 hName / hProperties 官方增强。\nimport type {} from \"mdast-util-to-hast\";\nimport { toString as mdastToString } from \"mdast-util-to-string\";\n\n// ── 标题(toc.ts)──────────────────────────────────────────────\n\n/** 标题行内内容的包裹节点,供多行高亮背景样式使用(hName: span)。 */\ninterface HeadingMark extends Parent {\n type: \"headingMark\";\n children: PhrasingContent[];\n}\n\n/** 目录根节点(hName: details)。 */\ninterface Toc extends Parent {\n type: \"toc\";\n children: (TocSummary | TocNav)[];\n}\n\n/** 目录摘要节点(hName: summary)。 */\ninterface TocSummary extends Parent {\n type: \"tocSummary\";\n children: PhrasingContent[];\n}\n\n/** 目录导航容器节点(hName: nav)。 */\ninterface TocNav extends Parent {\n type: \"tocNav\";\n children: TocList[];\n}\n\n/** 目录列表节点(hName: ol)。 */\ninterface TocList extends Parent {\n type: \"tocList\";\n children: TocItem[];\n}\n\n/** 目录列表项节点(hName: li)。 */\ninterface TocItem extends Parent {\n type: \"tocItem\";\n children: (TocToggle | Link | TocList)[];\n}\n\n/** 目录分支折叠按钮节点(hName: button)。 */\ninterface TocToggle extends Parent {\n type: \"tocToggle\";\n children: never[];\n}\n\n// ── 行内高亮(highlight-mark.ts)───────────────────────────────\n\n/** ==text== 行内高亮节点(hName: mark)。 */\ninterface HighlightMark extends Parent {\n type: \"highlightMark\";\n children: PhrasingContent[];\n}\n\n// ── 定义列表(definition-lists.ts)─────────────────────────────\n\n/** 定义列表节点(hName: dl)。 */\ninterface DefinitionList extends Parent {\n type: \"definitionList\";\n children: (DefinitionTerm | DefinitionDescription)[];\n}\n\n/** 定义项名称节点(hName: dt)。 */\ninterface DefinitionTerm extends Parent {\n type: \"definitionTerm\";\n children: PhrasingContent[];\n}\n\n/** 定义项说明节点(hName: dd)。 */\ninterface DefinitionDescription extends Parent {\n type: \"definitionDescription\";\n children: PhrasingContent[];\n}\n\n// ── Frontmatter 元数据卡片(frontmatter.ts)────────────────────\n\n/** frontmatter 元数据卡片根节点(hName: div)。 */\ninterface FrontmatterMetadata extends Parent {\n type: \"frontmatterMetadata\";\n children: (FrontmatterChrome | FrontmatterList)[];\n}\n\n/** 卡片头部装饰条节点(hName: div)。 */\ninterface FrontmatterChrome extends Parent {\n type: \"frontmatterChrome\";\n children: FrontmatterLabel[];\n}\n\n/** 卡片标签节点(hName: span)。 */\ninterface FrontmatterLabel extends Parent {\n type: \"frontmatterLabel\";\n children: PhrasingContent[];\n}\n\n/** 键值列表节点(hName: dl)。 */\ninterface FrontmatterList extends Parent {\n type: \"frontmatterList\";\n children: (FrontmatterTerm | FrontmatterDescription)[];\n}\n\n/** 键名节点(hName: dt)。 */\ninterface FrontmatterTerm extends Parent {\n type: \"frontmatterTerm\";\n children: PhrasingContent[];\n}\n\n/** 键值节点(hName: dd),对象值递归嵌套键值列表。 */\ninterface FrontmatterDescription extends Parent {\n type: \"frontmatterDescription\";\n children: (Text | FrontmatterList)[];\n}\n\n// ── 图片 figure(media/images.ts)──────────────────────────────\n\n/** 独占段落图片的 figure 包装节点(hName: figure)。 */\ninterface ImageFigure extends Parent {\n type: \"imageFigure\";\n children: (ImageFrame | ImageCaption)[];\n}\n\n/** 图片边框容器节点(hName: span)。 */\ninterface ImageFrame extends Parent {\n type: \"imageFrame\";\n children: (Image | ImageReference | ImageFallback)[];\n}\n\n/** 图片标题节点(hName: figcaption)。 */\ninterface ImageCaption extends Parent {\n type: \"imageCaption\";\n children: PhrasingContent[];\n}\n\n/** 图片失败态占位节点(hName: span)。 */\ninterface ImageFallback extends Parent {\n type: \"imageFallback\";\n children: (ImageFallbackIcon | ImageFallbackText | ImageFallbackSource)[];\n}\n\n/** 失败态图标节点(hName: span,无子节点)。 */\ninterface ImageFallbackIcon extends Node {\n type: \"imageFallbackIcon\";\n}\n\n/** 失败态文案节点(hName: span)。 */\ninterface ImageFallbackText extends Parent {\n type: \"imageFallbackText\";\n children: PhrasingContent[];\n}\n\n/** 失败态来源路径节点(hName: span)。 */\ninterface ImageFallbackSource extends Parent {\n type: \"imageFallbackSource\";\n children: PhrasingContent[];\n}\n\n// ── declaration merging:把自定义节点注册进 mdast 内容分类 ──────\n\ndeclare module \"mdast\" {\n /** 所有自定义节点都可出现在文档树中,注册进 RootContentMap 使其成为 Nodes 联合的一部分。 */\n interface RootContentMap {\n scribdownHeadingMark: HeadingMark;\n scribdownToc: Toc;\n scribdownTocSummary: TocSummary;\n scribdownTocNav: TocNav;\n scribdownTocList: TocList;\n scribdownTocItem: TocItem;\n scribdownTocToggle: TocToggle;\n scribdownHighlightMark: HighlightMark;\n scribdownDefinitionList: DefinitionList;\n scribdownDefinitionTerm: DefinitionTerm;\n scribdownDefinitionDescription: DefinitionDescription;\n scribdownFrontmatterMetadata: FrontmatterMetadata;\n scribdownFrontmatterChrome: FrontmatterChrome;\n scribdownFrontmatterLabel: FrontmatterLabel;\n scribdownFrontmatterList: FrontmatterList;\n scribdownFrontmatterTerm: FrontmatterTerm;\n scribdownFrontmatterDescription: FrontmatterDescription;\n scribdownImageFigure: ImageFigure;\n scribdownImageFrame: ImageFrame;\n scribdownImageCaption: ImageCaption;\n scribdownImageFallback: ImageFallback;\n scribdownImageFallbackIcon: ImageFallbackIcon;\n scribdownImageFallbackText: ImageFallbackText;\n scribdownImageFallbackSource: ImageFallbackSource;\n }\n\n /** 可替换段落等块级位置的自定义节点,注册进 BlockContentMap。 */\n interface BlockContentMap {\n scribdownToc: Toc;\n scribdownDefinitionList: DefinitionList;\n scribdownFrontmatterMetadata: FrontmatterMetadata;\n scribdownImageFigure: ImageFigure;\n }\n\n /** 可出现在行内位置的自定义节点,注册进 PhrasingContentMap。 */\n interface PhrasingContentMap {\n scribdownHeadingMark: HeadingMark;\n scribdownHighlightMark: HighlightMark;\n }\n}\n\n/**\n * 从节点及其子节点中提取可读文本。\n * 委托给 remark 官方的 mdast-util-to-string;关闭 includeHtml 以维持\n * 「标题内的行内 HTML 不参与锚点与目录文本」的既有行为。\n * @param node 当前节点。\n * @returns 当前节点的纯文本内容。\n */\nfunction extractNodeText(node: Node): string {\n return mdastToString(node, { includeHtml: false });\n}\n\n/**\n * 判断节点是否为图片或引用式图片。\n * @param node 待判断的 Markdown 节点。\n * @returns 当前节点是否为图片节点。\n */\nfunction isImageNode(node: Node): node is Image | ImageReference {\n return node.type === \"image\" || node.type === \"imageReference\";\n}\n\nexport type {\n BlockContent,\n DefinitionContent,\n DefinitionDescription,\n DefinitionList,\n DefinitionTerm,\n FrontmatterChrome,\n FrontmatterDescription,\n FrontmatterLabel,\n FrontmatterList,\n FrontmatterMetadata,\n FrontmatterTerm,\n HeadingMark,\n HighlightMark,\n ImageCaption,\n ImageFallback,\n ImageFallbackIcon,\n ImageFallbackSource,\n ImageFallbackText,\n ImageFigure,\n ImageFrame,\n Toc,\n TocItem,\n TocList,\n TocNav,\n TocSummary,\n TocToggle\n};\nexport { extractNodeText, isImageNode };\n","/**\n * 图片全图查看器:每个 document 复用一个 dialog 单例,\n * 支持缩放、拖拽平移、键盘操作与焦点保持。\n */\n\nimport {\n IMAGE_VIEWER_BUTTON_CLASS_NAME,\n IMAGE_VIEWER_CAPTION_CLASS_NAME,\n IMAGE_VIEWER_CAPTION_GROUP_CLASS_NAME,\n IMAGE_VIEWER_CHROME_CLASS_NAME,\n IMAGE_VIEWER_CLOSE_BUTTON_CLASS_NAME,\n IMAGE_VIEWER_CONTROLS_CLASS_NAME,\n IMAGE_VIEWER_DIALOG_CLASS_NAME,\n IMAGE_VIEWER_DRAGGING_CLASS_NAME,\n IMAGE_VIEWER_HINT_CLASS_NAME,\n IMAGE_VIEWER_IMAGE_CLASS_NAME,\n IMAGE_VIEWER_VIEWPORT_CLASS_NAME,\n IMAGE_VIEWER_ZOOMED_CLASS_NAME,\n IMAGE_VIEWER_ZOOM_VALUE_CLASS_NAME\n} from \"@scribdown/shared\";\n\nimport {\n VIEWER_CLOSE_TEXT,\n VIEWER_DEFAULT_ZOOM,\n VIEWER_FIT_RATIO,\n VIEWER_MAX_ZOOM,\n VIEWER_MIN_ZOOM,\n VIEWER_RESET_TEXT,\n VIEWER_WHEEL_ZOOM_FACTOR,\n VIEWER_WHEEL_ZOOM_MAX_DELTA,\n VIEWER_ZOOM_IN_TEXT,\n VIEWER_ZOOM_OUT_TEXT,\n VIEWER_ZOOM_STEP,\n clampMarkdownViewerZoom,\n type MarkdownViewerButtonOptions,\n type MarkdownViewerFocalPoint,\n type MarkdownViewerZoomAnchor\n} from \"../core/viewer-shared\";\n\n/**\n * 图片全图查看器的运行时状态。\n */\ninterface MarkdownImageViewerState {\n captionElement: HTMLElement;\n closeButtonElement: HTMLButtonElement;\n dialogElement: HTMLDialogElement;\n /** 拖拽起始时鼠标的客户端 X 坐标。 */\n dragStartClientX: number;\n /** 拖拽起始时鼠标的客户端 Y 坐标。 */\n dragStartClientY: number;\n /** 拖拽起始时视口的横向滚动量。 */\n dragStartScrollLeft: number;\n /** 拖拽起始时视口的纵向滚动量。 */\n dragStartScrollTop: number;\n hintElement: HTMLElement;\n imageElement: HTMLImageElement;\n /** 当前是否处于鼠标拖拽平移状态。 */\n isDragging: boolean;\n naturalHeight: number;\n naturalWidth: number;\n resetButtonElement: HTMLButtonElement;\n sourceImageElement?: HTMLImageElement;\n viewportElement: HTMLElement;\n zoomInButtonElement: HTMLButtonElement;\n zoomOutButtonElement: HTMLButtonElement;\n zoomValue: number;\n zoomValueElement: HTMLElement;\n}\n\n// 图片查看器快捷键提示文本。\nconst IMAGE_VIEWER_HINT_TEXT =\n \"快捷键:+/= 放大 · - 缩小 · 0 重置 · Esc 关闭 · 鼠标拖拽可平移 · Ctrl/⌘ + 滚轮缩放\";\n\n// 图片查看器兜底可访问名称。\nconst IMAGE_VIEWER_FALLBACK_LABEL = \"查看全图\";\n\n// 每个 document 复用一个图片查看器。\nconst imageViewerStateByDocument = new WeakMap<Document, MarkdownImageViewerState>();\n\n// 从 dialog 反查图片查看器状态。\nconst imageViewerStateByDialogElement = new WeakMap<HTMLDialogElement, MarkdownImageViewerState>();\n\n/**\n * 给图片绑定全图查看入口。\n * @param imageElement 待绑定的图片元素。\n */\nfunction bindMarkdownImageViewer(imageElement: HTMLImageElement): void {\n // 图片外层链接元素,存在时保留原始链接行为。\n const linkElement = imageElement.closest(\"a[href]\");\n\n if (linkElement) {\n return;\n }\n\n imageElement.setAttribute(\"role\", \"button\");\n imageElement.setAttribute(\"tabindex\", \"0\");\n imageElement.setAttribute(\"aria-label\", createMarkdownImageViewerAriaLabel(imageElement));\n imageElement.addEventListener(\"click\", handleMarkdownImageViewerOpenClick);\n imageElement.addEventListener(\"keydown\", handleMarkdownImageViewerOpenKeyDown);\n}\n\n/**\n * 处理图片点击打开全图查看器。\n * @param event 图片点击事件。\n */\nfunction handleMarkdownImageViewerOpenClick(event: MouseEvent): void {\n // 被点击的图片元素。\n const imageElement = event.currentTarget as HTMLImageElement;\n\n if (!canOpenMarkdownImageViewer(imageElement)) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n openMarkdownImageViewer(imageElement);\n}\n\n/**\n * 处理图片键盘打开全图查看器。\n * @param event 图片键盘事件。\n */\nfunction handleMarkdownImageViewerOpenKeyDown(event: KeyboardEvent): void {\n // 当前按键是否是打开操作。\n const isOpenKey = event.key === \"Enter\" || event.key === \" \";\n // 当前聚焦的图片元素。\n const imageElement = event.currentTarget as HTMLImageElement;\n\n if (!isOpenKey || !canOpenMarkdownImageViewer(imageElement)) {\n return;\n }\n\n event.preventDefault();\n openMarkdownImageViewer(imageElement);\n}\n\n/**\n * 判断图片是否可以进入全图查看。\n * @param imageElement 待判断的图片元素。\n * @returns 图片是否已经加载成功且存在有效来源。\n */\nfunction canOpenMarkdownImageViewer(imageElement: HTMLImageElement): boolean {\n // 图片可展示来源。\n const imageSource = getMarkdownImageSource(imageElement);\n\n return (\n imageSource.length > 0 &&\n imageElement.complete &&\n imageElement.naturalWidth > 0 &&\n imageElement.naturalHeight > 0\n );\n}\n\n/**\n * 打开图片全图查看器。\n * @param sourceImageElement 触发查看的原始图片元素。\n */\nfunction openMarkdownImageViewer(sourceImageElement: HTMLImageElement): void {\n // 图片可展示来源。\n const imageSource = getMarkdownImageSource(sourceImageElement);\n\n if (imageSource.length === 0) {\n return;\n }\n\n // 原始图片所在 document。\n const ownerDocument = sourceImageElement.ownerDocument;\n // 当前 document 对应的查看器状态。\n const viewerState = getOrCreateMarkdownImageViewerState(ownerDocument);\n // 原始图片可访问名称。\n const imageLabel = createMarkdownImageViewerCaption(sourceImageElement, imageSource);\n\n viewerState.sourceImageElement = sourceImageElement;\n viewerState.naturalWidth = sourceImageElement.naturalWidth;\n viewerState.naturalHeight = sourceImageElement.naturalHeight;\n viewerState.imageElement.src = imageSource;\n viewerState.imageElement.alt = sourceImageElement.alt;\n viewerState.captionElement.textContent = imageLabel;\n viewerState.dialogElement.setAttribute(\n \"aria-label\",\n createMarkdownImageViewerAriaLabel(sourceImageElement)\n );\n viewerState.viewportElement.scrollLeft = 0;\n viewerState.viewportElement.scrollTop = 0;\n viewerState.zoomValue = VIEWER_DEFAULT_ZOOM;\n\n showMarkdownImageViewerDialog(viewerState);\n requestMarkdownImageViewerLayout(viewerState);\n}\n\n/**\n * 获取或创建当前 document 的图片查看器。\n * @param ownerDocument 图片所在 document。\n * @returns 当前 document 可复用的查看器状态。\n */\nfunction getOrCreateMarkdownImageViewerState(ownerDocument: Document): MarkdownImageViewerState {\n // 已创建的查看器状态。\n const existingViewerState = imageViewerStateByDocument.get(ownerDocument);\n\n if (existingViewerState) {\n return existingViewerState;\n }\n\n // 新创建的查看器状态。\n const createdViewerState = createMarkdownImageViewerState(ownerDocument);\n\n imageViewerStateByDocument.set(ownerDocument, createdViewerState);\n\n return createdViewerState;\n}\n\n/**\n * 创建图片查看器 DOM 与事件绑定。\n * @param ownerDocument 图片所在 document。\n * @returns 图片查看器状态。\n */\nfunction createMarkdownImageViewerState(ownerDocument: Document): MarkdownImageViewerState {\n // 图片查看器 dialog。\n const dialogElement = ownerDocument.createElement(\"dialog\") as HTMLDialogElement;\n // 图片查看器顶部区域。\n const chromeElement = ownerDocument.createElement(\"div\");\n // 图片查看器标题分组(caption + hint)。\n const captionGroupElement = ownerDocument.createElement(\"div\");\n // 图片查看器说明文字。\n const captionElement = ownerDocument.createElement(\"p\");\n // 图片查看器快捷键提示。\n const hintElement = ownerDocument.createElement(\"p\");\n // 图片查看器按钮区域。\n const controlsElement = ownerDocument.createElement(\"div\");\n // 缩小按钮。\n const zoomOutButtonElement = createMarkdownImageViewerButton(ownerDocument, {\n ariaLabel: \"缩小图片\",\n text: VIEWER_ZOOM_OUT_TEXT\n });\n // 当前缩放比例文本。\n const zoomValueElement = ownerDocument.createElement(\"span\");\n // 放大按钮。\n const zoomInButtonElement = createMarkdownImageViewerButton(ownerDocument, {\n ariaLabel: \"放大图片\",\n text: VIEWER_ZOOM_IN_TEXT\n });\n // 重置缩放按钮。\n const resetButtonElement = createMarkdownImageViewerButton(ownerDocument, {\n ariaLabel: \"重置缩放\",\n text: VIEWER_RESET_TEXT\n });\n // 关闭按钮。\n const closeButtonElement = createMarkdownImageViewerButton(ownerDocument, {\n ariaLabel: \"关闭全图查看\",\n className: IMAGE_VIEWER_CLOSE_BUTTON_CLASS_NAME,\n text: VIEWER_CLOSE_TEXT\n });\n // 图片滚动视口。\n const viewportElement = ownerDocument.createElement(\"div\");\n // 查看器内展示的图片。\n const imageElement = ownerDocument.createElement(\"img\");\n // 图片查看器运行时状态。\n const viewerState: MarkdownImageViewerState = {\n captionElement,\n closeButtonElement,\n dialogElement,\n dragStartClientX: 0,\n dragStartClientY: 0,\n dragStartScrollLeft: 0,\n dragStartScrollTop: 0,\n hintElement,\n imageElement,\n isDragging: false,\n naturalHeight: 1,\n naturalWidth: 1,\n resetButtonElement,\n viewportElement,\n zoomInButtonElement,\n zoomOutButtonElement,\n zoomValue: VIEWER_DEFAULT_ZOOM,\n zoomValueElement\n };\n // 当前 document 对应的 window。\n const ownerWindow = ownerDocument.defaultView;\n\n dialogElement.className = IMAGE_VIEWER_DIALOG_CLASS_NAME;\n dialogElement.setAttribute(\"aria-modal\", \"true\");\n dialogElement.setAttribute(\"tabindex\", \"-1\");\n chromeElement.className = IMAGE_VIEWER_CHROME_CLASS_NAME;\n captionGroupElement.className = IMAGE_VIEWER_CAPTION_GROUP_CLASS_NAME;\n captionElement.className = IMAGE_VIEWER_CAPTION_CLASS_NAME;\n hintElement.className = IMAGE_VIEWER_HINT_CLASS_NAME;\n hintElement.textContent = IMAGE_VIEWER_HINT_TEXT;\n controlsElement.className = IMAGE_VIEWER_CONTROLS_CLASS_NAME;\n zoomValueElement.className = IMAGE_VIEWER_ZOOM_VALUE_CLASS_NAME;\n viewportElement.className = IMAGE_VIEWER_VIEWPORT_CLASS_NAME;\n imageElement.className = IMAGE_VIEWER_IMAGE_CLASS_NAME;\n imageElement.decoding = \"async\";\n\n imageViewerStateByDialogElement.set(dialogElement, viewerState);\n controlsElement.append(\n zoomOutButtonElement,\n zoomValueElement,\n zoomInButtonElement,\n resetButtonElement,\n closeButtonElement\n );\n captionGroupElement.append(captionElement, hintElement);\n chromeElement.append(captionGroupElement, controlsElement);\n viewportElement.append(imageElement);\n dialogElement.append(chromeElement, viewportElement);\n ownerDocument.body.append(dialogElement);\n\n dialogElement.addEventListener(\"click\", handleMarkdownImageViewerBackdropClick);\n dialogElement.addEventListener(\"close\", handleMarkdownImageViewerClose);\n dialogElement.addEventListener(\"keydown\", handleMarkdownImageViewerKeyDown);\n viewportElement.addEventListener(\"wheel\", handleMarkdownImageViewerWheel, { passive: false });\n viewportElement.addEventListener(\"pointerdown\", handleMarkdownImageViewerPointerDown);\n viewportElement.addEventListener(\"pointermove\", handleMarkdownImageViewerPointerMove);\n viewportElement.addEventListener(\"pointerup\", handleMarkdownImageViewerPointerUp);\n viewportElement.addEventListener(\"pointercancel\", handleMarkdownImageViewerPointerUp);\n imageElement.addEventListener(\"load\", handleMarkdownImageViewerImageLoad);\n imageElement.addEventListener(\"dragstart\", handleMarkdownImageViewerImageDragStart);\n zoomOutButtonElement.addEventListener(\"click\", handleMarkdownImageViewerZoomOutClick);\n zoomInButtonElement.addEventListener(\"click\", handleMarkdownImageViewerZoomInClick);\n resetButtonElement.addEventListener(\"click\", handleMarkdownImageViewerResetClick);\n closeButtonElement.addEventListener(\"click\", handleMarkdownImageViewerCloseClick);\n\n if (ownerWindow) {\n ownerWindow.addEventListener(\"resize\", handleMarkdownImageViewerWindowResize);\n }\n\n updateMarkdownImageViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);\n\n return viewerState;\n}\n\n/**\n * 创建图片查看器按钮。\n * @param ownerDocument 按钮所在 document。\n * @param options 按钮文本、可访问名称和修饰类。\n * @returns 图片查看器按钮元素。\n */\nfunction createMarkdownImageViewerButton(\n ownerDocument: Document,\n options: MarkdownViewerButtonOptions\n): HTMLButtonElement {\n // 图片查看器按钮元素。\n const buttonElement = ownerDocument.createElement(\"button\");\n\n buttonElement.type = \"button\";\n buttonElement.className = options.className\n ? `${IMAGE_VIEWER_BUTTON_CLASS_NAME} ${options.className}`\n : IMAGE_VIEWER_BUTTON_CLASS_NAME;\n buttonElement.textContent = options.text;\n buttonElement.setAttribute(\"aria-label\", options.ariaLabel);\n\n return buttonElement;\n}\n\n/**\n * 显示图片查看器 dialog。\n * @param viewerState 图片查看器状态。\n */\nfunction showMarkdownImageViewerDialog(viewerState: MarkdownImageViewerState): void {\n if (typeof viewerState.dialogElement.showModal === \"function\") {\n if (!viewerState.dialogElement.open) {\n viewerState.dialogElement.showModal();\n }\n } else {\n viewerState.dialogElement.setAttribute(\"open\", \"\");\n }\n\n viewerState.dialogElement.focus();\n}\n\n/**\n * 延后一次布局计算,确保 dialog 已经进入可测量状态。\n * @param viewerState 图片查看器状态。\n */\nfunction requestMarkdownImageViewerLayout(viewerState: MarkdownImageViewerState): void {\n // 图片查看器所在 window。\n const ownerWindow = viewerState.dialogElement.ownerDocument.defaultView;\n\n if (!ownerWindow) {\n updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);\n return;\n }\n\n ownerWindow.setTimeout(handleMarkdownImageViewerDeferredLayout, 0, viewerState);\n}\n\n/**\n * 处理延后的图片查看器布局计算。\n * @param viewerState 图片查看器状态。\n */\nfunction handleMarkdownImageViewerDeferredLayout(viewerState: MarkdownImageViewerState): void {\n updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);\n}\n\n/**\n * 处理点击遮罩关闭图片查看器。\n * @param event 查看器点击事件。\n */\nfunction handleMarkdownImageViewerBackdropClick(event: MouseEvent): void {\n if (event.target !== event.currentTarget) {\n return;\n }\n\n // 被点击的 dialog。\n const dialogElement = event.currentTarget as HTMLDialogElement;\n // 图片查看器状态。\n const viewerState = imageViewerStateByDialogElement.get(dialogElement);\n\n if (!viewerState) {\n return;\n }\n\n closeMarkdownImageViewer(viewerState);\n}\n\n/**\n * 处理图片查看器关闭后的状态复位。\n * @param event 查看器关闭事件。\n */\nfunction handleMarkdownImageViewerClose(event: Event): void {\n // 被关闭的 dialog。\n const dialogElement = event.currentTarget as HTMLDialogElement;\n // 图片查看器状态。\n const viewerState = imageViewerStateByDialogElement.get(dialogElement);\n\n if (!viewerState) {\n return;\n }\n\n resetMarkdownImageViewerAfterClose(viewerState);\n}\n\n/**\n * 处理查看器内键盘缩放和关闭。\n * @param event 查看器键盘事件。\n */\nfunction handleMarkdownImageViewerKeyDown(event: KeyboardEvent): void {\n // 图片查看器状态。\n const viewerState = getMarkdownImageViewerStateFromEvent(event);\n\n if (!viewerState) {\n return;\n }\n\n if (event.key === \"+\" || event.key === \"=\") {\n event.preventDefault();\n updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + VIEWER_ZOOM_STEP);\n return;\n }\n\n if (event.key === \"-\") {\n event.preventDefault();\n updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue - VIEWER_ZOOM_STEP);\n return;\n }\n\n if (event.key === \"0\") {\n event.preventDefault();\n updateMarkdownImageViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);\n return;\n }\n\n if (event.key === \"Escape\") {\n event.preventDefault();\n closeMarkdownImageViewer(viewerState);\n }\n}\n\n/**\n * 处理 ctrl/cmd + 滚轮缩放图片。\n * @param event 图片查看器滚轮事件。\n */\nfunction handleMarkdownImageViewerWheel(event: WheelEvent): void {\n if (!event.ctrlKey && !event.metaKey) {\n return;\n }\n\n // 图片查看器状态。\n const viewerState = getMarkdownImageViewerStateFromEvent(event);\n\n if (!viewerState) {\n return;\n }\n\n // 与 deltaY 成比例并夹紧到上限,让触控板细滑动产生细粒度缩放、鼠标滚轮单格仍可见。\n const rawDelta = -event.deltaY * VIEWER_WHEEL_ZOOM_FACTOR;\n const zoomDelta = Math.max(\n -VIEWER_WHEEL_ZOOM_MAX_DELTA,\n Math.min(VIEWER_WHEEL_ZOOM_MAX_DELTA, rawDelta)\n );\n\n event.preventDefault();\n updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + zoomDelta, {\n x: event.clientX,\n y: event.clientY\n });\n}\n\n/**\n * 处理图片视口鼠标按下,开启拖拽平移。\n * @param event 视口指针事件。\n */\nfunction handleMarkdownImageViewerPointerDown(event: PointerEvent): void {\n // 仅响应鼠标主键 / 触摸 / 笔。\n if (event.button !== 0) {\n return;\n }\n\n // 当前事件所在视口。\n const viewportElement = event.currentTarget as HTMLElement;\n // 图片查看器状态。\n const viewerState = getMarkdownImageViewerStateFromEvent(event);\n\n if (!viewerState) {\n return;\n }\n\n // 视口是否存在可滚动空间。\n const canScrollHorizontally = viewportElement.scrollWidth > viewportElement.clientWidth;\n const canScrollVertically = viewportElement.scrollHeight > viewportElement.clientHeight;\n\n if (!canScrollHorizontally && !canScrollVertically) {\n return;\n }\n\n viewerState.isDragging = true;\n viewerState.dragStartClientX = event.clientX;\n viewerState.dragStartClientY = event.clientY;\n viewerState.dragStartScrollLeft = viewportElement.scrollLeft;\n viewerState.dragStartScrollTop = viewportElement.scrollTop;\n viewerState.dialogElement.classList.add(IMAGE_VIEWER_DRAGGING_CLASS_NAME);\n\n if (typeof viewportElement.setPointerCapture === \"function\") {\n viewportElement.setPointerCapture(event.pointerId);\n }\n\n event.preventDefault();\n}\n\n/**\n * 处理图片视口指针移动,跟随鼠标更新滚动位置。\n * @param event 视口指针事件。\n */\nfunction handleMarkdownImageViewerPointerMove(event: PointerEvent): void {\n // 当前事件所在视口。\n const viewportElement = event.currentTarget as HTMLElement;\n // 图片查看器状态。\n const viewerState = getMarkdownImageViewerStateFromEvent(event);\n\n if (!viewerState || !viewerState.isDragging) {\n return;\n }\n\n // 当前位移量:与起点的差值。\n const deltaX = event.clientX - viewerState.dragStartClientX;\n const deltaY = event.clientY - viewerState.dragStartClientY;\n\n viewportElement.scrollLeft = viewerState.dragStartScrollLeft - deltaX;\n viewportElement.scrollTop = viewerState.dragStartScrollTop - deltaY;\n}\n\n/**\n * 处理图片视口指针抬起/取消,结束拖拽。\n * @param event 视口指针事件。\n */\nfunction handleMarkdownImageViewerPointerUp(event: PointerEvent): void {\n // 当前事件所在视口。\n const viewportElement = event.currentTarget as HTMLElement;\n // 图片查看器状态。\n const viewerState = getMarkdownImageViewerStateFromEvent(event);\n\n if (!viewerState || !viewerState.isDragging) {\n return;\n }\n\n viewerState.isDragging = false;\n viewerState.dialogElement.classList.remove(IMAGE_VIEWER_DRAGGING_CLASS_NAME);\n\n if (\n typeof viewportElement.releasePointerCapture === \"function\" &&\n viewportElement.hasPointerCapture(event.pointerId)\n ) {\n viewportElement.releasePointerCapture(event.pointerId);\n }\n}\n\n/**\n * 阻止图片原生拖拽,避免与平移手势冲突。\n * @param event 图片拖拽事件。\n */\nfunction handleMarkdownImageViewerImageDragStart(event: DragEvent): void {\n event.preventDefault();\n}\n\n/**\n * 处理查看器图片加载后尺寸同步。\n * @param event 图片加载事件。\n */\nfunction handleMarkdownImageViewerImageLoad(event: Event): void {\n // 查看器图片元素。\n const imageElement = event.currentTarget as HTMLImageElement;\n // 图片所在 dialog。\n const dialogElement = imageElement.closest<HTMLDialogElement>(\n `.${IMAGE_VIEWER_DIALOG_CLASS_NAME}`\n );\n\n if (!dialogElement) {\n return;\n }\n\n // 图片查看器状态。\n const viewerState = imageViewerStateByDialogElement.get(dialogElement);\n\n if (!viewerState) {\n return;\n }\n\n viewerState.naturalWidth = imageElement.naturalWidth || viewerState.naturalWidth;\n viewerState.naturalHeight = imageElement.naturalHeight || viewerState.naturalHeight;\n updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);\n}\n\n/**\n * 处理窗口尺寸变化后的图片适配。\n * @param event window resize 事件。\n */\nfunction handleMarkdownImageViewerWindowResize(event: Event): void {\n // 触发 resize 的 window。\n const ownerWindow = event.currentTarget as Window;\n // 当前 window 对应的查看器状态。\n const viewerState = imageViewerStateByDocument.get(ownerWindow.document);\n\n if (!viewerState || !isMarkdownImageViewerOpen(viewerState)) {\n return;\n }\n\n updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue);\n}\n\n/**\n * 处理缩小按钮点击。\n * @param event 缩小按钮点击事件。\n */\nfunction handleMarkdownImageViewerZoomOutClick(event: MouseEvent): void {\n // 图片查看器状态。\n const viewerState = getMarkdownImageViewerStateFromEvent(event);\n\n if (!viewerState) {\n return;\n }\n\n updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue - VIEWER_ZOOM_STEP);\n}\n\n/**\n * 处理放大按钮点击。\n * @param event 放大按钮点击事件。\n */\nfunction handleMarkdownImageViewerZoomInClick(event: MouseEvent): void {\n // 图片查看器状态。\n const viewerState = getMarkdownImageViewerStateFromEvent(event);\n\n if (!viewerState) {\n return;\n }\n\n updateMarkdownImageViewerZoom(viewerState, viewerState.zoomValue + VIEWER_ZOOM_STEP);\n}\n\n/**\n * 处理重置缩放按钮点击。\n * @param event 重置按钮点击事件。\n */\nfunction handleMarkdownImageViewerResetClick(event: MouseEvent): void {\n // 图片查看器状态。\n const viewerState = getMarkdownImageViewerStateFromEvent(event);\n\n if (!viewerState) {\n return;\n }\n\n updateMarkdownImageViewerZoom(viewerState, VIEWER_DEFAULT_ZOOM);\n}\n\n/**\n * 处理关闭按钮点击。\n * @param event 关闭按钮点击事件。\n */\nfunction handleMarkdownImageViewerCloseClick(event: MouseEvent): void {\n // 图片查看器状态。\n const viewerState = getMarkdownImageViewerStateFromEvent(event);\n\n if (!viewerState) {\n return;\n }\n\n closeMarkdownImageViewer(viewerState);\n}\n\n/**\n * 根据事件目标找到图片查看器状态。\n * @param event 来自图片查看器内部的事件。\n * @returns 图片查看器状态,不存在时返回 undefined。\n */\nfunction getMarkdownImageViewerStateFromEvent(event: Event): MarkdownImageViewerState | undefined {\n // 当前事件绑定元素。\n const currentTargetElement = event.currentTarget as HTMLElement | null;\n\n if (!currentTargetElement) {\n return undefined;\n }\n\n // 当前元素所属 dialog。\n const dialogElement = currentTargetElement.closest<HTMLDialogElement>(\n `.${IMAGE_VIEWER_DIALOG_CLASS_NAME}`\n );\n\n if (!dialogElement) {\n return undefined;\n }\n\n return imageViewerStateByDialogElement.get(dialogElement);\n}\n\n/**\n * 更新图片查看器缩放比例和展示尺寸。\n * @param viewerState 图片查看器状态。\n * @param nextZoom 目标缩放倍数。\n * @param zoomAnchor 可选的缩放锚点(客户端坐标),不传则以视口中心为锚点。\n */\nfunction updateMarkdownImageViewerZoom(\n viewerState: MarkdownImageViewerState,\n nextZoom: number,\n zoomAnchor?: MarkdownViewerZoomAnchor\n): void {\n // 归一化后的缩放倍数。\n const normalizedZoom = clampMarkdownViewerZoom(nextZoom);\n // 缩放前的焦点信息,用于恢复光标/视口中心在新尺寸下的相对位置。\n const focalPoint = isMarkdownImageViewerOpen(viewerState)\n ? captureMarkdownImageViewerFocalPoint(viewerState, zoomAnchor)\n : undefined;\n\n viewerState.zoomValue = normalizedZoom;\n viewerState.dialogElement.classList.toggle(\n IMAGE_VIEWER_ZOOMED_CLASS_NAME,\n normalizedZoom > VIEWER_DEFAULT_ZOOM\n );\n viewerState.zoomValueElement.textContent = `${Math.round(normalizedZoom * 100)}%`;\n viewerState.zoomOutButtonElement.disabled = normalizedZoom <= VIEWER_MIN_ZOOM;\n viewerState.zoomInButtonElement.disabled = normalizedZoom >= VIEWER_MAX_ZOOM;\n viewerState.resetButtonElement.disabled = normalizedZoom === VIEWER_DEFAULT_ZOOM;\n updateMarkdownImageViewerImageSize(viewerState);\n\n if (focalPoint) {\n applyMarkdownImageViewerFocalPoint(viewerState, focalPoint);\n }\n}\n\n/**\n * 记录缩放前的焦点:把客户端坐标转成图片内的归一化位置。\n * @param viewerState 图片查看器状态。\n * @param zoomAnchor 客户端坐标锚点,不传则以视口中心。\n * @returns 焦点信息,图片尚未布局完成时返回 undefined。\n */\nfunction captureMarkdownImageViewerFocalPoint(\n viewerState: MarkdownImageViewerState,\n zoomAnchor?: MarkdownViewerZoomAnchor\n): MarkdownViewerFocalPoint | undefined {\n // 当前图片矩形。\n const preImageRect = viewerState.imageElement.getBoundingClientRect();\n\n if (preImageRect.width <= 0 || preImageRect.height <= 0) {\n return undefined;\n }\n\n // 当前视口矩形。\n const viewportRect = viewerState.viewportElement.getBoundingClientRect();\n // 锚点客户端坐标:未传时取视口中心。\n const anchorClientX = zoomAnchor?.x ?? viewportRect.left + viewportRect.width / 2;\n const anchorClientY = zoomAnchor?.y ?? viewportRect.top + viewportRect.height / 2;\n // 锚点在图片内部的归一化坐标。\n const normalizedX = (anchorClientX - preImageRect.left) / preImageRect.width;\n const normalizedY = (anchorClientY - preImageRect.top) / preImageRect.height;\n\n return {\n anchorClientX,\n anchorClientY,\n normalizedX,\n normalizedY\n };\n}\n\n/**\n * 缩放后调整滚动量,使焦点保持在原客户端位置。\n * @param viewerState 图片查看器状态。\n * @param focalPoint 缩放前记录的焦点信息。\n */\nfunction applyMarkdownImageViewerFocalPoint(\n viewerState: MarkdownImageViewerState,\n focalPoint: MarkdownViewerFocalPoint\n): void {\n // 先回到无滚动状态,便于读取布局的自然位置。\n viewerState.viewportElement.scrollLeft = 0;\n viewerState.viewportElement.scrollTop = 0;\n\n // 缩放后图片在客户端坐标系的位置和尺寸。\n const postImageRect = viewerState.imageElement.getBoundingClientRect();\n\n if (postImageRect.width <= 0 || postImageRect.height <= 0) {\n return;\n }\n\n // 期望的图片左上角客户端坐标:让归一化锚点落在原客户端位置。\n const targetImageClientLeft =\n focalPoint.anchorClientX - focalPoint.normalizedX * postImageRect.width;\n const targetImageClientTop =\n focalPoint.anchorClientY - focalPoint.normalizedY * postImageRect.height;\n\n // 利用滚动量补齐自然位置与期望位置的差值,浏览器会自动裁剪到合法范围。\n viewerState.viewportElement.scrollLeft = postImageRect.left - targetImageClientLeft;\n viewerState.viewportElement.scrollTop = postImageRect.top - targetImageClientTop;\n}\n\n/**\n * 根据视口和缩放比例更新查看器图片尺寸。\n * @param viewerState 图片查看器状态。\n */\nfunction updateMarkdownImageViewerImageSize(viewerState: MarkdownImageViewerState): void {\n // 视口可用宽度。\n const viewportWidth = Math.max(\n viewerState.viewportElement.clientWidth * VIEWER_FIT_RATIO,\n 1\n );\n // 视口可用高度。\n const viewportHeight = Math.max(\n viewerState.viewportElement.clientHeight * VIEWER_FIT_RATIO,\n 1\n );\n // 图片原始宽度。\n const naturalWidth = Math.max(viewerState.naturalWidth, 1);\n // 图片原始高度。\n const naturalHeight = Math.max(viewerState.naturalHeight, 1);\n // 默认全图适配比例。\n const fitScale = Math.min(1, viewportWidth / naturalWidth, viewportHeight / naturalHeight);\n // 缩放后的展示宽度。\n const displayWidth = Math.max(1, Math.round(naturalWidth * fitScale * viewerState.zoomValue));\n // 缩放后的展示高度。\n const displayHeight = Math.max(1, Math.round(naturalHeight * fitScale * viewerState.zoomValue));\n\n viewerState.imageElement.style.width = `${displayWidth}px`;\n viewerState.imageElement.style.height = `${displayHeight}px`;\n}\n\n/**\n * 关闭图片查看器。\n * @param viewerState 图片查看器状态。\n */\nfunction closeMarkdownImageViewer(viewerState: MarkdownImageViewerState): void {\n if (typeof viewerState.dialogElement.close === \"function\" && viewerState.dialogElement.open) {\n viewerState.dialogElement.close();\n return;\n }\n\n viewerState.dialogElement.removeAttribute(\"open\");\n resetMarkdownImageViewerAfterClose(viewerState);\n}\n\n/**\n * 复位图片查看器关闭后的临时状态。\n * @param viewerState 图片查看器状态。\n */\nfunction resetMarkdownImageViewerAfterClose(viewerState: MarkdownImageViewerState): void {\n viewerState.dialogElement.classList.remove(IMAGE_VIEWER_ZOOMED_CLASS_NAME);\n viewerState.dialogElement.classList.remove(IMAGE_VIEWER_DRAGGING_CLASS_NAME);\n viewerState.isDragging = false;\n viewerState.imageElement.removeAttribute(\"src\");\n viewerState.imageElement.removeAttribute(\"alt\");\n viewerState.imageElement.removeAttribute(\"style\");\n viewerState.captionElement.textContent = \"\";\n viewerState.viewportElement.scrollLeft = 0;\n viewerState.viewportElement.scrollTop = 0;\n viewerState.zoomValue = VIEWER_DEFAULT_ZOOM;\n\n if (viewerState.sourceImageElement?.isConnected) {\n viewerState.sourceImageElement.focus();\n }\n}\n\n/**\n * 判断图片查看器是否处于打开状态。\n * @param viewerState 图片查看器状态。\n * @returns 查看器是否打开。\n */\nfunction isMarkdownImageViewerOpen(viewerState: MarkdownImageViewerState): boolean {\n return viewerState.dialogElement.open || viewerState.dialogElement.hasAttribute(\"open\");\n}\n\n/**\n * 读取图片可展示来源。\n * @param imageElement 图片元素。\n * @returns 图片来源 URL。\n */\nfunction getMarkdownImageSource(imageElement: HTMLImageElement): string {\n return imageElement.currentSrc || imageElement.getAttribute(\"src\") || imageElement.src || \"\";\n}\n\n/**\n * 创建图片查看器可访问名称。\n * @param imageElement 图片元素。\n * @returns 图片查看器可访问名称。\n */\nfunction createMarkdownImageViewerAriaLabel(imageElement: HTMLImageElement): string {\n // 图片 alt 文本。\n const imageAltText = imageElement.alt.trim();\n\n if (imageAltText.length === 0) {\n return IMAGE_VIEWER_FALLBACK_LABEL;\n }\n\n return `${IMAGE_VIEWER_FALLBACK_LABEL}:${imageAltText}`;\n}\n\n/**\n * 创建图片查看器说明文本。\n * @param imageElement 图片元素。\n * @param imageSource 图片来源 URL。\n * @returns 图片查看器说明文本。\n */\nfunction createMarkdownImageViewerCaption(\n imageElement: HTMLImageElement,\n imageSource: string\n): string {\n // 图片 alt 文本。\n const imageAltText = imageElement.alt.trim();\n // 图片 title 文本。\n const imageTitleText = imageElement.title.trim();\n\n return imageAltText || imageTitleText || imageSource;\n}\n\nexport { bindMarkdownImageViewer };\n","/**\n * 视频渲染:rehype 插件把 <video> 包装为与图片一致的 figure 结构(含失败态占位),\n * hydrate 阶段绑定加载完成与失败回退状态机。\n */\n\nimport {\n VIDEO_ELEMENT_CLASS_NAME,\n VIDEO_FALLBACK_CLASS_NAME,\n VIDEO_FALLBACK_ICON_CLASS_NAME,\n VIDEO_FALLBACK_SOURCE_CLASS_NAME,\n VIDEO_FALLBACK_TEXT_CLASS_NAME,\n VIDEO_FIGURE_CLASS_NAME,\n VIDEO_FRAME_CLASS_NAME,\n VIDEO_FRAME_FAILED_CLASS_NAME,\n VIDEO_FRAME_LOADED_CLASS_NAME\n} from \"@scribdown/shared\";\n\nimport type { Element, ElementContent, Root } from \"hast\";\nimport { classnames } from \"hast-util-classnames\";\nimport { SKIP, visit } from \"unist-util-visit\";\n\n// 视频运行时已绑定标记的 dataset 键。\nconst VIDEO_HYDRATED_DATA_KEY = \"scribdownVideoHydrated\";\n\n// 视频失败态展示文本。\nconst VIDEO_FALLBACK_DEFAULT_TEXT = \"视频加载失败\";\n\n/**\n * rehype 插件:把渲染后的 <video> 元素包装为 figure 结构,\n * 与图片 figure 保持视觉与失败回退状态机一致。\n * 该插件运行在 rehype-raw 之后、rehype-sanitize 之前,结构清洗在后兜底。\n * @returns hast 转换器。\n */\nfunction rehypeVideoFigures(): (tree: Root) => void {\n return (tree: Root) => {\n visit(tree, \"element\", (node, index, parent) => {\n if (parent === undefined || index === undefined) {\n return;\n }\n\n if (isHastVideoElement(node)) {\n parent.children[index] = createVideoFigureHast(node);\n // 关键步骤:figure 内部无需再遍历,跳过并从替换节点之后继续。\n return SKIP;\n }\n\n // <p><video/></p> 形式(行内 HTML 解析的常见结果):把整段替换为 figure。\n /** 仅包含单个 video 的段落剥离结果。 */\n const standaloneVideo = extractStandaloneParagraphVideo(node);\n if (standaloneVideo) {\n parent.children[index] = createVideoFigureHast(standaloneVideo);\n return SKIP;\n }\n });\n };\n}\n\n/**\n * 判断 hast 节点是否为 <video> 元素。\n * @param node 待判断的 hast 节点。\n * @returns 是否为 video 元素。\n */\nfunction isHastVideoElement(node: ElementContent): node is Element {\n return node.type === \"element\" && node.tagName === \"video\";\n}\n\n/**\n * 尝试从只包含单个 <video> 的 <p> 段落中剥离出该 video 节点。\n * @param node 待检查的 hast 元素。\n * @returns 剥离出的 video 节点;不匹配则返回 undefined。\n */\nfunction extractStandaloneParagraphVideo(node: Element): Element | undefined {\n if (node.tagName !== \"p\") {\n return undefined;\n }\n\n /** 段落内忽略纯空白文本节点后的有效子节点列表。 */\n const significantChildren = node.children.filter((childNode) => {\n if (childNode.type === \"text\") {\n return childNode.value.trim().length > 0;\n }\n return true;\n });\n\n if (\n significantChildren.length === 1 &&\n isHastVideoElement(significantChildren[0])\n ) {\n return significantChildren[0];\n }\n\n return undefined;\n}\n\n/**\n * 把 video 节点装饰类名后包装为 figure + frame + fallback 的 hast 结构。\n * @param videoNode 原始 video 节点。\n * @returns 新的 figure hast 节点。\n */\nfunction createVideoFigureHast(videoNode: Element): Element {\n decorateHastVideoElement(videoNode);\n\n /** 用于失败态展示的源 URL。 */\n const sourceUrl = readHastVideoSourceUrl(videoNode);\n\n return {\n type: \"element\",\n tagName: \"figure\",\n properties: { className: [VIDEO_FIGURE_CLASS_NAME] },\n children: [\n {\n type: \"element\",\n tagName: \"span\",\n properties: { className: [VIDEO_FRAME_CLASS_NAME] },\n children: [videoNode, createVideoFallbackHast(sourceUrl)]\n }\n ]\n };\n}\n\n/**\n * 给 video 节点追加统一类名(hast-util-classnames 保留原有 class 并去重)。\n * @param videoNode 待装饰的 video 节点。\n */\nfunction decorateHastVideoElement(videoNode: Element): void {\n classnames(videoNode, VIDEO_ELEMENT_CLASS_NAME);\n}\n\n/**\n * 读取 video 节点的源 URL,优先使用 src 属性,其次回退到首个 <source> 子节点。\n * @param videoNode video 节点。\n * @returns 源 URL,未找到时返回空串。\n */\nfunction readHastVideoSourceUrl(videoNode: Element): string {\n if (typeof videoNode.properties.src === \"string\") {\n return videoNode.properties.src;\n }\n\n for (const childNode of videoNode.children) {\n if (\n childNode.type === \"element\" &&\n childNode.tagName === \"source\" &&\n typeof childNode.properties.src === \"string\"\n ) {\n return childNode.properties.src;\n }\n }\n\n return \"\";\n}\n\n/**\n * 构造视频失败态占位 hast 节点。\n * @param sourceUrl 视频源 URL,用于失败态尾部展示。\n * @returns 失败态占位 hast 节点。\n */\nfunction createVideoFallbackHast(sourceUrl: string): Element {\n return {\n type: \"element\",\n tagName: \"span\",\n properties: { className: [VIDEO_FALLBACK_CLASS_NAME] },\n children: [\n {\n type: \"element\",\n tagName: \"span\",\n properties: { className: [VIDEO_FALLBACK_ICON_CLASS_NAME] },\n children: []\n },\n {\n type: \"element\",\n tagName: \"span\",\n properties: { className: [VIDEO_FALLBACK_TEXT_CLASS_NAME] },\n children: [{ type: \"text\", value: VIDEO_FALLBACK_DEFAULT_TEXT }]\n },\n {\n type: \"element\",\n tagName: \"span\",\n properties: { className: [VIDEO_FALLBACK_SOURCE_CLASS_NAME] },\n children: [{ type: \"text\", value: sourceUrl }]\n }\n ]\n };\n}\n\n/**\n * 给渲染后的视频绑定加载/失败状态类名。\n * @param rootElement 包含 Markdown 渲染结果的根节点。\n */\nfunction hydrateMarkdownVideos(rootElement: ParentNode): void {\n // 当前根节点内的所有视频元素。\n const videoElements = rootElement.querySelectorAll<HTMLVideoElement>(\n `video.${VIDEO_ELEMENT_CLASS_NAME}`\n );\n\n videoElements.forEach(bindMarkdownVideoState);\n}\n\n/**\n * 给单个视频元素绑定加载完成与失败回退状态机。\n * @param videoElement 待绑定状态的视频元素。\n */\nfunction bindMarkdownVideoState(videoElement: HTMLVideoElement): void {\n updateMarkdownVideoState(videoElement);\n\n if (videoElement.dataset[VIDEO_HYDRATED_DATA_KEY] === \"true\") {\n return;\n }\n\n videoElement.dataset[VIDEO_HYDRATED_DATA_KEY] = \"true\";\n videoElement.addEventListener(\"loadeddata\", handleMarkdownVideoLoaded);\n videoElement.addEventListener(\"error\", handleMarkdownVideoError);\n\n // 关键步骤:同时监听内部 <source> 的 error,覆盖多源视频在最后一个源失败后才能确认失败的场景。\n videoElement\n .querySelectorAll<HTMLSourceElement>(\"source\")\n .forEach((sourceElement) => {\n sourceElement.addEventListener(\"error\", handleMarkdownVideoSourceError);\n });\n}\n\n/**\n * 处理视频可播放数据就绪事件,刷新 frame 状态类。\n * @param event 视频加载事件。\n */\nfunction handleMarkdownVideoLoaded(event: Event): void {\n // 触发就绪事件的视频元素。\n const videoElement = event.currentTarget as HTMLVideoElement;\n\n updateMarkdownVideoState(videoElement);\n}\n\n/**\n * 处理视频本体加载失败事件。\n * @param event 视频失败事件。\n */\nfunction handleMarkdownVideoError(event: Event): void {\n // 触发失败事件的视频元素。\n const videoElement = event.currentTarget as HTMLVideoElement;\n\n markMarkdownVideoFailed(videoElement);\n}\n\n/**\n * 处理视频内部 <source> 的失败事件。\n * 仅当 networkState 进入 NETWORK_NO_SOURCE,意味着所有候选源均不可用时,才标记失败。\n * @param event source 失败事件。\n */\nfunction handleMarkdownVideoSourceError(event: Event): void {\n // 触发失败事件的 source 元素。\n const sourceElement = event.currentTarget as HTMLElement;\n // 关联的视频宿主元素。\n const videoElement = sourceElement.closest<HTMLVideoElement>(\n `video.${VIDEO_ELEMENT_CLASS_NAME}`\n );\n\n if (!videoElement) {\n return;\n }\n if (videoElement.networkState !== videoElement.NETWORK_NO_SOURCE) {\n return;\n }\n\n markMarkdownVideoFailed(videoElement);\n}\n\n/**\n * 根据视频当前播放状态刷新 frame 状态类。\n * @param videoElement 待更新状态的视频元素。\n */\nfunction updateMarkdownVideoState(videoElement: HTMLVideoElement): void {\n // 视频外层 frame 元素。\n const frameElement = videoElement.closest<HTMLElement>(\n `.${VIDEO_FRAME_CLASS_NAME}`\n );\n\n if (!frameElement) {\n return;\n }\n\n // 当前视频是否已确认加载失败:本体 error 非空,或没有可用源。\n const isFailed =\n videoElement.error !== null ||\n videoElement.networkState === videoElement.NETWORK_NO_SOURCE;\n // 当前视频是否已确认拿到首帧。\n const isLoaded = videoElement.readyState >= videoElement.HAVE_CURRENT_DATA;\n\n frameElement.classList.toggle(VIDEO_FRAME_FAILED_CLASS_NAME, isFailed);\n frameElement.classList.toggle(\n VIDEO_FRAME_LOADED_CLASS_NAME,\n isLoaded && !isFailed\n );\n}\n\n/**\n * 把视频 frame 强制切到失败态,并清除可能已存在的 loaded 标记。\n * @param videoElement 触发失败的视频元素。\n */\nfunction markMarkdownVideoFailed(videoElement: HTMLVideoElement): void {\n // 视频外层 frame 元素。\n const frameElement = videoElement.closest<HTMLElement>(\n `.${VIDEO_FRAME_CLASS_NAME}`\n );\n\n if (!frameElement) {\n return;\n }\n\n frameElement.classList.add(VIDEO_FRAME_FAILED_CLASS_NAME);\n frameElement.classList.remove(VIDEO_FRAME_LOADED_CLASS_NAME);\n}\n\nexport { rehypeVideoFigures, hydrateMarkdownVideos };\n","/**\n * HTML 安全清洗:rehype-sanitize 的 Scribdown 白名单扩展\n * 与 DOMPurify 字符串级二次清洗。\n */\n\nimport {\n DETAILS_CLASS_NAME,\n FRAME_CLASS_NAME,\n FRONTMATTER_CHROME_CLASS_NAME,\n FRONTMATTER_CLASS_NAME,\n FRONTMATTER_LABEL_CLASS_NAME,\n FRONTMATTER_LIST_CLASS_NAME,\n FRONTMATTER_LIST_NESTED_CLASS_NAME,\n HEADING_MARK_CLASS_NAME,\n IMAGE_CAPTION_CLASS_NAME,\n IMAGE_ELEMENT_CLASS_NAME,\n IMAGE_FALLBACK_CLASS_NAME,\n IMAGE_FALLBACK_ICON_CLASS_NAME,\n IMAGE_FALLBACK_SOURCE_CLASS_NAME,\n IMAGE_FALLBACK_TEXT_CLASS_NAME,\n IMAGE_FIGURE_CLASS_NAME,\n IMAGE_FRAME_CLASS_NAME,\n TOC_CLASS_NAME,\n TOC_ITEM_CLASS_PREFIX,\n TOC_LINK_CLASS_NAME,\n TOC_LIST_CLASS_NAME,\n TOC_LIST_NESTED_CLASS_NAME,\n TOC_NAV_CLASS_NAME,\n TOC_SUMMARY_CLASS_NAME,\n TOC_TOGGLE_CLASS_NAME,\n VIDEO_ELEMENT_CLASS_NAME,\n VIDEO_FALLBACK_CLASS_NAME,\n VIDEO_FALLBACK_ICON_CLASS_NAME,\n VIDEO_FALLBACK_SOURCE_CLASS_NAME,\n VIDEO_FALLBACK_TEXT_CLASS_NAME,\n VIDEO_FIGURE_CLASS_NAME,\n VIDEO_FRAME_CLASS_NAME\n} from \"@scribdown/shared\";\n\nimport DOMPurify from \"dompurify\";\nimport { defaultSchema } from \"rehype-sanitize\";\n\nimport { SOURCE_LINE_HAST_PROPERTY } from \"./core/source-line\";\n\n/**\n * 使用 DOMPurify 清洗 HTML。\n * @param unsafeHtml 未清洗的 HTML。\n * @returns 清洗后的 HTML。\n */\nfunction sanitizeHtmlWithDomPurify(unsafeHtml: string): string {\n // DOMPurify 在 Node 环境下没有绑定 window 时会以工厂形态存在。\n const domPurify = DOMPurify as { sanitize?: (unsafeHtml: string) => string };\n\n if (typeof domPurify.sanitize !== \"function\") {\n return unsafeHtml;\n }\n\n return domPurify.sanitize(unsafeHtml);\n}\n\n/**\n * 为 Scribdown 扩展默认 HTML 清洗规则。\n * @returns 支持目录节点 class / nav 的清洗规则。\n */\nfunction createScribdownSanitizeSchema(): typeof defaultSchema {\n // 默认标签白名单。\n const defaultTagNames = defaultSchema.tagNames ?? [];\n // 默认属性白名单。\n const defaultAttributes = defaultSchema.attributes ?? {};\n // details 元素的属性白名单(目录根 + 用户手写折叠块;分支已改用 button,不再用 details)。\n const detailsAttributes = [\n \"open\",\n [\"className\", DETAILS_CLASS_NAME, FRAME_CLASS_NAME, TOC_CLASS_NAME] as [\n string,\n string,\n string,\n string\n ]\n ];\n // summary 元素的属性白名单(仅目录根 summary)。\n const summaryAttributes = [\n ...(defaultAttributes.summary ?? []),\n [\"className\", TOC_SUMMARY_CLASS_NAME] as [string, string]\n ];\n // nav 元素的属性白名单。\n const navAttributes = [\"ariaLabel\", [\"className\", TOC_NAV_CLASS_NAME] as [string, string]];\n // a 元素的属性白名单。\n const linkAttributes = [\n \"ariaDescribedBy\",\n \"ariaLabel\",\n \"ariaLabelledBy\",\n \"dataFootnoteBackref\",\n \"dataFootnoteRef\",\n [\"className\", \"data-footnote-backref\", TOC_LINK_CLASS_NAME] as [string, string, string],\n \"href\"\n ];\n // button 元素的属性白名单:仅放行目录分支折叠按钮所需属性,禁止任何事件处理属性。\n const buttonAttributes = [\n \"type\",\n \"ariaExpanded\",\n \"ariaLabel\",\n [\"className\", TOC_TOGGLE_CLASS_NAME] as [string, string]\n ];\n // ol 元素的属性白名单。\n const orderedListAttributes = [\n \"ariaDescribedBy\",\n \"ariaLabel\",\n \"ariaLabelledBy\",\n [\"className\", \"contains-task-list\", TOC_LIST_CLASS_NAME, TOC_LIST_NESTED_CLASS_NAME] as [\n string,\n ...string[]\n ]\n ];\n // div 元素的属性白名单:放行 frontmatter 元数据卡片容器与 chrome 的 class。\n const divAttributes = [\n ...(defaultAttributes.div ?? []),\n [\"className\", FRONTMATTER_CLASS_NAME, FRAME_CLASS_NAME, FRONTMATTER_CHROME_CLASS_NAME] as [\n string,\n ...string[]\n ]\n ];\n // dl 元素的属性白名单:放行 frontmatter 键值列表(含嵌套修饰类)的 class。\n const definitionListAttributes = [\n [\"className\", FRONTMATTER_LIST_CLASS_NAME, FRONTMATTER_LIST_NESTED_CLASS_NAME] as [\n string,\n string,\n string\n ]\n ];\n // span 元素的属性白名单:放行标题行内包裹层与图片 / 视频 frame / fallback 的 class。\n const spanAttributes = [\n ...(defaultAttributes.span ?? []),\n [\n \"className\",\n HEADING_MARK_CLASS_NAME,\n FRONTMATTER_LABEL_CLASS_NAME,\n IMAGE_FRAME_CLASS_NAME,\n IMAGE_FALLBACK_CLASS_NAME,\n IMAGE_FALLBACK_ICON_CLASS_NAME,\n IMAGE_FALLBACK_TEXT_CLASS_NAME,\n IMAGE_FALLBACK_SOURCE_CLASS_NAME,\n VIDEO_FRAME_CLASS_NAME,\n VIDEO_FALLBACK_CLASS_NAME,\n VIDEO_FALLBACK_ICON_CLASS_NAME,\n VIDEO_FALLBACK_TEXT_CLASS_NAME,\n VIDEO_FALLBACK_SOURCE_CLASS_NAME\n ] as [string, ...string[]]\n ];\n // figure 元素的属性白名单:放行图片 figure 与视频 figure 的 class。\n const figureAttributes = [\n [\"className\", IMAGE_FIGURE_CLASS_NAME, VIDEO_FIGURE_CLASS_NAME] as [\n string,\n string,\n string\n ]\n ];\n // figcaption 元素的属性白名单。\n const figcaptionAttributes = [[\"className\", IMAGE_CAPTION_CLASS_NAME] as [string, string]];\n // img 元素的属性白名单。\n const imageAttributes = [\n ...(defaultAttributes.img ?? []),\n [\"className\", IMAGE_ELEMENT_CLASS_NAME] as [string, string]\n ];\n // video 元素的属性白名单:放行常用播放控制属性与统一类名。\n // 禁止任何事件处理属性(默认 schema 不在白名单的属性会被剥除)。\n const videoAttributes = [\n \"src\",\n \"controls\",\n \"controlsList\",\n \"width\",\n \"height\",\n \"poster\",\n \"preload\",\n \"playsInline\",\n \"muted\",\n \"loop\",\n \"autoPlay\",\n \"crossOrigin\",\n [\"className\", VIDEO_ELEMENT_CLASS_NAME] as [string, string]\n ];\n // source 元素的属性白名单(<video> 多源回退用)。\n const sourceAttributes = [\"src\", \"type\", \"media\"];\n // abbr 元素的属性白名单:保留 title 以展示完整释义。\n const abbrAttributes = [\"title\"];\n // li 元素的属性白名单。目录条目 class 由统一前缀拼出正则,避免与 shared 常量漂移。\n const listItemAttributes = [\n \"dataTocIndex\",\n [\n \"className\",\n \"task-list-item\",\n new RegExp(`^${TOC_ITEM_CLASS_PREFIX}(?:--(?:branch|depth-[1-6]))?$`, \"u\")\n ] as [string, string, RegExp]\n ];\n // 通配元素属性白名单:额外放行滚动对齐用的 data-source-line。\n const wildcardAttributes = [...(defaultAttributes[\"*\"] ?? []), SOURCE_LINE_HAST_PROPERTY];\n\n return {\n ...defaultSchema,\n clobberPrefix: \"\",\n tagNames: Array.from(\n new Set([\n ...defaultTagNames,\n \"button\",\n \"details\",\n \"nav\",\n \"summary\",\n \"u\",\n \"mark\",\n \"sub\",\n \"sup\",\n \"kbd\",\n \"abbr\",\n \"small\",\n \"dl\",\n \"dt\",\n \"dd\",\n \"figure\",\n \"figcaption\",\n \"video\",\n \"source\"\n ])\n ),\n attributes: {\n ...defaultAttributes,\n \"*\": wildcardAttributes,\n button: buttonAttributes,\n details: detailsAttributes,\n summary: summaryAttributes,\n nav: navAttributes,\n a: linkAttributes,\n ol: orderedListAttributes,\n li: listItemAttributes,\n div: divAttributes,\n dl: definitionListAttributes,\n span: spanAttributes,\n abbr: abbrAttributes,\n figure: figureAttributes,\n figcaption: figcaptionAttributes,\n img: imageAttributes,\n video: videoAttributes,\n source: sourceAttributes\n }\n };\n}\n\nexport { sanitizeHtmlWithDomPurify, createScribdownSanitizeSchema };\n","/**\n * remark 插件:把 term + 下一行冒号定义的段落转换为 dl/dt/dd 定义列表。\n */\n\nimport type { Paragraph, Root } from \"mdast\";\nimport { visit } from \"unist-util-visit\";\n\nimport { extractNodeText, type DefinitionList } from \"../core/ast\";\n\n// 定义列表段落匹配规则:支持 CommonMark 未内建的 term + 下一行冒号定义写法。\nconst DEFINITION_LIST_PARAGRAPH_PATTERN = /^\\s*([^\\n:][^\\n]*)\\n\\s*:\\s+([^\\n]+)\\s*$/u;\n\n/**\n * remark 插件:把 term + 下一行冒号定义的段落转换为定义列表。\n * @returns Markdown AST 转换器。\n */\nfunction remarkDefinitionLists(): (tree: Root) => void {\n return (tree: Root) => {\n visit(tree, \"paragraph\", (node, index, parent) => {\n if (parent === undefined || index === undefined) {\n return;\n }\n\n // 当前段落转换后的定义列表节点。\n const definitionListNode = createDefinitionListNode(node);\n\n if (definitionListNode) {\n parent.children[index] = definitionListNode;\n }\n });\n };\n}\n\n/**\n * 尝试把段落转换为定义列表节点。\n * @param node 待转换的段落节点。\n * @returns 转换后的定义列表节点,不匹配时返回 undefined。\n */\nfunction createDefinitionListNode(node: Paragraph): DefinitionList | undefined {\n // 段落纯文本内容,用于识别定义列表语法。\n const paragraphText = extractNodeText(node);\n // 定义列表匹配结果。\n const definitionMatch = DEFINITION_LIST_PARAGRAPH_PATTERN.exec(paragraphText);\n\n if (!definitionMatch) {\n return undefined;\n }\n\n // 定义项名称。\n const definitionTerm = definitionMatch[1].trim();\n // 定义项说明。\n const definitionDescription = definitionMatch[2].trim();\n\n if (definitionTerm.length === 0 || definitionDescription.length === 0) {\n return undefined;\n }\n\n return {\n type: \"definitionList\",\n // 关键步骤:保留原段落源码位置,使 remarkSourceLine 能为 dl 标注 data-source-line。\n position: node.position,\n data: {\n hName: \"dl\"\n },\n children: [\n {\n type: \"definitionTerm\",\n data: {\n hName: \"dt\"\n },\n children: [{ type: \"text\", value: definitionTerm }]\n },\n {\n type: \"definitionDescription\",\n data: {\n hName: \"dd\"\n },\n children: [{ type: \"text\", value: definitionDescription }]\n }\n ]\n };\n}\n\nexport { remarkDefinitionLists };\n","/**\n * rehype 插件:为用户手写的内容折叠块 <details>\n * 打上 scribdown-details + scribdown-frame 手绘边框 class。\n */\n\nimport { DETAILS_CLASS_NAME, FRAME_CLASS_NAME, TOC_CLASS_NAME } from \"@scribdown/shared\";\n\nimport type { Root } from \"hast\";\nimport { classnames } from \"hast-util-classnames\";\nimport { visit } from \"unist-util-visit\";\n\n/**\n * rehype 插件:为内容折叠块注入手绘边框 class,实现样式层的 opt-in。\n * 用户手写的原生 <details>(非目录用)打上 scribdown-details + scribdown-frame;\n * 目录根的 scribdown-frame 已在 mdast 阶段注入,目录根在此跳过(分支已改用 button,无 details)。\n * 该插件运行在 rehype-raw 之后、rehype-sanitize 之前,注入的 class 由 sanitize 白名单放行。\n * @returns hast 转换器。\n */\nfunction rehypeFrameClass(): (tree: Root) => void {\n return (tree: Root) => {\n visit(tree, \"element\", (node) => {\n if (node.tagName !== \"details\") {\n return;\n }\n\n // 当前 details 已有的 class 列表(可能是数组、字符串或缺失)。\n const classNames = normalizeClassNames(node.properties.className);\n\n // 目录根使用自己的命名空间,不当作内容折叠块处理。\n if (!classNames.includes(TOC_CLASS_NAME)) {\n // 关键步骤:hast-util-classnames 追加 class 并去重,保留已有 class。\n classnames(node, DETAILS_CLASS_NAME, FRAME_CLASS_NAME);\n }\n });\n };\n}\n\n/**\n * 将 hast 节点的 className 属性标准化为字符串数组。\n * @param className hast 节点上的 className 属性原值。\n * @returns 标准化后的 class 名数组。\n */\nfunction normalizeClassNames(className: unknown): string[] {\n if (Array.isArray(className)) {\n return className.filter((item): item is string => typeof item === \"string\");\n }\n if (typeof className === \"string\") {\n return className.split(/\\s+/u).filter(Boolean);\n }\n return [];\n}\n\nexport { rehypeFrameClass };\n","/**\n * remark 插件:把文档头部的 YAML frontmatter 渲染为元数据卡片。\n */\n\nimport {\n FRAME_CLASS_NAME,\n FRONTMATTER_CHROME_CLASS_NAME,\n FRONTMATTER_CLASS_NAME,\n FRONTMATTER_LABEL_CLASS_NAME,\n FRONTMATTER_LIST_CLASS_NAME,\n FRONTMATTER_LIST_NESTED_CLASS_NAME\n} from \"@scribdown/shared\";\n\nimport type { Code, Root, Yaml } from \"mdast\";\nimport { parse as parseYamlSource } from \"yaml\";\n\nimport type {\n FrontmatterDescription,\n FrontmatterList,\n FrontmatterMetadata,\n FrontmatterTerm\n} from \"../core/ast\";\n\n// Frontmatter 标签展示文本。\nconst FRONTMATTER_LABEL_TEXT = \"元数据\";\n\n// Frontmatter 数组值中各标量项的拼接分隔符。\nconst FRONTMATTER_ARRAY_SEPARATOR = \", \";\n\n// Frontmatter 解析失败时回退代码块使用的语言标识。\nconst FRONTMATTER_FALLBACK_LANGUAGE_ID = \"yaml\";\n\n/**\n * remark 插件:把文档头部的 YAML frontmatter 渲染为元数据卡片。\n * remark-frontmatter 仅负责把 `---` 包裹的头部解析为 yaml 节点,\n * 本插件再把 yaml 节点转换为键值列表结构;解析失败时回退为 yaml 代码块展示原文。\n * @returns Markdown AST 转换器。\n */\nfunction remarkFrontmatterMetadata(): (tree: Root) => void {\n return (tree: Root) => {\n // 顶层块级节点列表。\n const childNodes = tree.children;\n // frontmatter 只可能出现在文档首个节点(remark-frontmatter 仅识别文档起始处的 ---)。\n const yamlNode = childNodes[0];\n\n if (!yamlNode || yamlNode.type !== \"yaml\") {\n return;\n }\n\n childNodes[0] = createFrontmatterNode(yamlNode);\n };\n}\n\n/**\n * 把 yaml frontmatter 节点转换为元数据卡片节点。\n * @param yamlNode remark-frontmatter 解析出的 yaml 节点。\n * @returns 元数据卡片节点;yaml 解析失败或非键值对象时返回 yaml 代码块节点。\n */\nfunction createFrontmatterNode(yamlNode: Yaml): FrontmatterMetadata | Code {\n // yaml 原文文本。\n const yamlSource = yamlNode.value;\n // yaml 解析结果,解析失败时保持 undefined。\n let parsedValue: unknown;\n\n try {\n parsedValue = parseYamlSource(yamlSource);\n } catch {\n parsedValue = undefined;\n }\n\n // 关键步骤:仅键值对象按卡片渲染;解析失败或非对象时回退为 yaml 代码块展示原文。\n if (!isPlainRecord(parsedValue)) {\n return {\n type: \"code\",\n lang: FRONTMATTER_FALLBACK_LANGUAGE_ID,\n position: yamlNode.position,\n value: yamlSource\n };\n }\n\n return {\n type: \"frontmatterMetadata\",\n // 关键步骤:保留原节点源码位置,使 remarkSourceLine 能标注 data-source-line。\n position: yamlNode.position,\n data: {\n hName: \"div\",\n hProperties: { className: [FRONTMATTER_CLASS_NAME, FRAME_CLASS_NAME] }\n },\n children: [\n {\n type: \"frontmatterChrome\",\n data: {\n hName: \"div\",\n hProperties: { className: [FRONTMATTER_CHROME_CLASS_NAME] }\n },\n children: [\n {\n type: \"frontmatterLabel\",\n data: {\n hName: \"span\",\n hProperties: { className: [FRONTMATTER_LABEL_CLASS_NAME] }\n },\n children: [{ type: \"text\", value: FRONTMATTER_LABEL_TEXT }]\n }\n ]\n },\n createFrontmatterListNode(parsedValue, false)\n ]\n };\n}\n\n/**\n * 判断值是否为纯键值对象(排除 null 与数组)。\n * @param value 待判断的值。\n * @returns 是否为纯键值对象。\n */\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * 把键值对象转换为 dl 键值列表节点,对象类型的值递归展开为嵌套列表。\n * @param record frontmatter 中的键值对象。\n * @param nested 是否为嵌套层(嵌套层追加修饰类)。\n * @returns dl 键值列表节点。\n */\nfunction createFrontmatterListNode(record: Record<string, unknown>, nested: boolean): FrontmatterList {\n // dl 的 className 列表:嵌套层追加修饰类。\n const listClassNames = nested\n ? [FRONTMATTER_LIST_CLASS_NAME, FRONTMATTER_LIST_NESTED_CLASS_NAME]\n : [FRONTMATTER_LIST_CLASS_NAME];\n // dt / dd 交替排列的子节点列表。\n const listChildren: (FrontmatterTerm | FrontmatterDescription)[] = [];\n\n Object.entries(record).forEach(([entryKey, entryValue]) => {\n listChildren.push({\n type: \"frontmatterTerm\",\n data: { hName: \"dt\" },\n children: [{ type: \"text\", value: entryKey }]\n });\n listChildren.push({\n type: \"frontmatterDescription\",\n data: { hName: \"dd\" },\n children: createFrontmatterValueNodes(entryValue)\n });\n });\n\n return {\n type: \"frontmatterList\",\n data: {\n hName: \"dl\",\n hProperties: { className: listClassNames }\n },\n children: listChildren\n };\n}\n\n/**\n * 把 frontmatter 中的单个值转换为 dd 内容节点列表。\n * @param value frontmatter 中的任意值。\n * @returns dd 的子节点列表。\n */\nfunction createFrontmatterValueNodes(value: unknown): FrontmatterDescription[\"children\"] {\n // 对象值向下展开为嵌套键值列表。\n if (isPlainRecord(value)) {\n return [createFrontmatterListNode(value, true)];\n }\n\n // 数组值逐项格式化后拼接为一行文本。\n if (Array.isArray(value)) {\n return [\n {\n type: \"text\",\n value: value.map(formatFrontmatterScalar).join(FRONTMATTER_ARRAY_SEPARATOR)\n }\n ];\n }\n\n return [{ type: \"text\", value: formatFrontmatterScalar(value) }];\n}\n\n/**\n * 把 frontmatter 标量值格式化为展示文本。\n * @param value frontmatter 中的标量值。\n * @returns 展示文本;null / undefined 展示为空串。\n */\nfunction formatFrontmatterScalar(value: unknown): string {\n if (value === null || value === undefined) {\n return \"\";\n }\n // 数组内的嵌套对象极少见,退化为 JSON 文本展示。\n if (typeof value === \"object\") {\n return JSON.stringify(value);\n }\n return String(value);\n}\n\nexport { remarkFrontmatterMetadata };\n","/**\n * remark 插件:把行内 text 节点中的 ==text== 转换为 <mark> 高亮节点。\n */\n\nimport type { PhrasingContent, Root } from \"mdast\";\nimport { SKIP, visit } from \"unist-util-visit\";\n\n// ==text== 高亮匹配:成对的 == 之间不能换行或再次出现 ==。\nconst HIGHLIGHT_MARKER_PATTERN = /==([^=\\n]+?)==/g;\n\n/**\n * remark 插件:把行内 text 节点中的 ==text== 转换为 <mark> 节点。\n * GFM 本身不支持该语法,因此独立实现以覆盖 fixture 中的高亮用法。\n * @returns Markdown AST 转换器。\n */\nfunction remarkHighlightMark(): (tree: Root) => void {\n return (tree: Root) => {\n visit(tree, \"text\", (node, index, parent) => {\n if (parent === undefined || index === undefined || !node.value.includes(\"==\")) {\n return;\n }\n\n // 拆分出的行内节点列表(普通文本与 mark 节点交替)。\n const segments = splitHighlightMarks(node.value);\n\n parent.children.splice(index, 1, ...segments);\n\n // 关键步骤:跳过刚插入的片段(其中的 text 不会再含成对 ==),从其后继续遍历。\n return [SKIP, index + segments.length];\n });\n };\n}\n\n/**\n * 拆分 text 字符串,把 ==content== 段落转换成 mark 节点。\n * @param textValue 原始 text 节点的内容。\n * @returns 拆分后的行内节点列表。\n */\nfunction splitHighlightMarks(textValue: string): PhrasingContent[] {\n // 拆分结果。\n const segments: PhrasingContent[] = [];\n // 当前游标位置。\n let cursor = 0;\n\n HIGHLIGHT_MARKER_PATTERN.lastIndex = 0;\n // 上一个匹配结果。\n let match: RegExpExecArray | null = HIGHLIGHT_MARKER_PATTERN.exec(textValue);\n\n while (match !== null) {\n // 当前匹配开始位置之前的普通文本。\n const leadingText = textValue.slice(cursor, match.index);\n\n if (leadingText.length > 0) {\n segments.push({ type: \"text\", value: leadingText });\n }\n\n segments.push({\n type: \"highlightMark\",\n data: {\n hName: \"mark\"\n },\n children: [{ type: \"text\", value: match[1] }]\n });\n\n cursor = match.index + match[0].length;\n match = HIGHLIGHT_MARKER_PATTERN.exec(textValue);\n }\n\n // 尾部剩余文本。\n const trailingText = textValue.slice(cursor);\n if (trailingText.length > 0) {\n segments.push({ type: \"text\", value: trailingText });\n }\n\n return segments;\n}\n\nexport { remarkHighlightMark };\n","/**\n * 目录(TOC):remark 插件收集标题、生成锚点并把独占段落的 [TOC] 替换为目录;\n * 同时提供从 DOM 收集标题、构建目录 nav 元素(toolbar 侧栏复用)与运行时折叠/跳转绑定。\n */\n\nimport {\n FRAME_CLASS_NAME,\n HEADING_MARK_CLASS_NAME,\n SCRIBDOWN_MARKDOWN_CLASS_NAME,\n TOC_CLASS_NAME,\n TOC_ITEM_BRANCH_CLASS_NAME,\n TOC_ITEM_CLASS_PREFIX,\n TOC_ITEM_COLLAPSED_CLASS_NAME,\n TOC_LINK_CLASS_NAME,\n TOC_LIST_CLASS_NAME,\n TOC_LIST_NESTED_CLASS_NAME,\n TOC_NAV_CLASS_NAME,\n TOC_SUMMARY_CLASS_NAME,\n TOC_TOGGLE_CLASS_NAME\n} from \"@scribdown/shared\";\n\nimport GithubSlugger from \"github-slugger\";\nimport type { Link, Paragraph, Root } from \"mdast\";\nimport { visit } from \"unist-util-visit\";\n\nimport {\n extractNodeText,\n type Toc,\n type TocItem,\n type TocList,\n type TocToggle\n} from \"../core/ast\";\n\n/**\n * 目录中的单个标题条目。\n */\ninterface TocHeading {\n depth: number;\n id: string;\n index: string;\n text: string;\n}\n\n/**\n * 目录树中的单个标题条目。\n */\ninterface TocTreeItem extends TocHeading {\n children: TocTreeItem[];\n}\n\n// [TOC] 占位符匹配规则:仅处理独占一段的目录标记。\nconst TOC_MARKER_PATTERN = /^\\s*\\[toc]\\s*$/i;\n\n// 目录分支折叠按钮可访问名称。\nconst TOC_TOGGLE_ARIA_LABEL = \"展开或折叠子目录\";\n\n// 目录折叠按钮已绑定交互的标记 dataset 键,保证 hydrate 幂等。\nconst TOC_TOGGLE_HYDRATED_DATA_KEY = \"scribdownTocToggleHydrated\";\n\n// 目录标题链接已绑定平滑滚动的标记 dataset 键,保证 hydrate 幂等。\nconst TOC_LINK_HYDRATED_DATA_KEY = \"scribdownTocLinkHydrated\";\n\n// 目录可访问名称。\nconst TOC_ARIA_LABEL = \"目录\";\n\n// 目录摘要显示文本。\nconst TOC_SUMMARY_TEXT = \"目录\";\n\n// 空标题生成锚点时使用的前缀。\nconst EMPTY_HEADING_SLUG_PREFIX = \"section\";\n\n/**\n * remark 插件:收集标题、生成标题锚点,并把独占一段的 [TOC] 替换为目录。\n * @returns Markdown AST 转换器。\n */\nfunction remarkTableOfContents(): (tree: Root) => void {\n return (tree: Root) => {\n // 标题条目,同时会在收集时写入 heading id。\n const tocHeadings = collectTocHeadings(tree);\n\n // 关键步骤:用目录节点替换所有独占一段的 [TOC]。\n replaceTocMarkers(tree, tocHeadings);\n };\n}\n\n/**\n * 收集文档标题并给标题节点写入稳定 id。\n * @param tree Markdown 根节点。\n * @returns 可用于渲染目录的标题列表。\n */\nfunction collectTocHeadings(tree: Root): TocHeading[] {\n // 标题锚点生成器:github-slugger 与 GitHub / VS Code 内置预览同源,\n // 实例内部自带重复标题计数(\"重复标题\" → \"重复标题-1\");每次渲染新建实例保证计数从零开始。\n const headingSlugger = new GithubSlugger();\n // 收集到的目录标题条目。\n const tocHeadings: TocHeading[] = [];\n // 各标题层级的计数器,用于生成 1 / 1.1 / 1.1.1 这类目录序号。\n const headingIndexCounts = Array.from({ length: 7 }, () => 0);\n // 文档内最浅标题层级,用作目录序号的根层级。\n let rootHeadingDepth: number | undefined;\n\n visit(tree, \"heading\", (node) => {\n // 当前标题层级。\n const headingDepth = node.depth;\n // 从标题行内节点提取纯文本。\n const headingText = extractNodeText(node).trim();\n // 为标题生成去重后的锚点。\n const headingId = createHeadingId(headingSlugger, headingText, tocHeadings.length + 1);\n // 根据标题层级生成目录编号。\n const headingIndex = createHeadingIndex(\n headingDepth,\n headingIndexCounts,\n rootHeadingDepth ?? headingDepth\n );\n\n rootHeadingDepth = rootHeadingDepth ?? headingDepth;\n\n node.data = {\n ...node.data,\n hProperties: {\n ...node.data?.hProperties,\n id: headingId\n }\n };\n\n // 关键步骤:把标题行内内容包进一个 inline 元素,\n // 让样式可以借助 box-decoration-break 在多行间复制高亮背景。\n node.children = [\n {\n type: \"headingMark\",\n data: {\n hName: \"span\",\n hProperties: {\n className: [HEADING_MARK_CLASS_NAME]\n }\n },\n children: node.children\n }\n ];\n\n tocHeadings.push({\n depth: headingDepth,\n id: headingId,\n index: headingIndex,\n text: headingText || headingId\n });\n });\n\n return tocHeadings;\n}\n\n/**\n * 将 [TOC] 段落替换为目录节点。\n * @param node 当前节点。\n * @param tocHeadings 目录标题条目。\n */\nfunction replaceTocMarkers(tree: Root, tocHeadings: TocHeading[]): void {\n visit(tree, \"paragraph\", (node, index, parent) => {\n if (parent === undefined || index === undefined || !isTocMarkerParagraph(node)) {\n return;\n }\n\n parent.children[index] = createTocNode(tocHeadings, node);\n });\n}\n\n/**\n * 判断段落是否是独占的 [TOC] 标记。\n * @param node 待判断段落节点。\n * @returns 当前节点是否为目录占位段落。\n */\nfunction isTocMarkerParagraph(node: Paragraph): boolean {\n if (node.children.length !== 1) {\n return false;\n }\n\n // 段落内唯一的行内节点。\n const onlyChild = node.children[0];\n\n return onlyChild.type === \"text\" && TOC_MARKER_PATTERN.test(onlyChild.value);\n}\n\n/**\n * 创建目录容器节点。\n * @param tocHeadings 目录标题条目。\n * @param markerNode [TOC] 标记段落节点,用于保留源码位置。\n * @returns 可被 remark-rehype 转换为 details 的 Markdown 节点。\n */\nfunction createTocNode(tocHeadings: TocHeading[], markerNode: Paragraph): Toc {\n // 层级化后的目录条目,用于生成可折叠分支。\n const tocTree = createTocTree(tocHeadings);\n\n return {\n type: \"toc\",\n // 关键步骤:保留 [TOC] 标记段落的源码位置,使 remarkSourceLine 能为目录标注 data-source-line。\n position: markerNode.position,\n data: {\n hName: \"details\",\n hProperties: {\n // 关键步骤:目录根 opt-in 手绘边框,再由 toc.css 叠加目录自身样式。\n className: [TOC_CLASS_NAME, FRAME_CLASS_NAME]\n }\n },\n children: [\n {\n type: \"tocSummary\",\n data: {\n hName: \"summary\",\n hProperties: {\n className: [TOC_SUMMARY_CLASS_NAME]\n }\n },\n children: [\n {\n type: \"text\",\n value: TOC_SUMMARY_TEXT\n }\n ]\n },\n {\n type: \"tocNav\",\n data: {\n hName: \"nav\",\n hProperties: {\n ariaLabel: TOC_ARIA_LABEL,\n className: [TOC_NAV_CLASS_NAME]\n }\n },\n children: [createTocListNode(tocTree, false)]\n }\n ]\n };\n}\n\n/**\n * 将扁平标题列表转换为目录树。\n * @param tocHeadings 扁平目录标题条目。\n * @returns 可生成嵌套目录的树形条目。\n */\nfunction createTocTree(tocHeadings: TocHeading[]): TocTreeItem[] {\n // 目录根节点集合。\n const rootItems: TocTreeItem[] = [];\n // 当前遍历路径上的分支栈。\n const itemStack: TocTreeItem[] = [];\n\n // 目录标题索引。\n for (let headingIndex = 0; headingIndex < tocHeadings.length; headingIndex += 1) {\n // 当前遍历到的标题条目。\n const tocHeading = tocHeadings[headingIndex];\n // 当前标题转换后的树节点。\n const tocTreeItem: TocTreeItem = {\n ...tocHeading,\n children: []\n };\n\n // 关键步骤:回退到比当前标题更浅的父级。\n while (itemStack.length > 0 && itemStack[itemStack.length - 1].depth >= tocTreeItem.depth) {\n itemStack.pop();\n }\n\n if (itemStack.length === 0) {\n rootItems.push(tocTreeItem);\n } else {\n itemStack[itemStack.length - 1].children.push(tocTreeItem);\n }\n\n itemStack.push(tocTreeItem);\n }\n\n return rootItems;\n}\n\n/**\n * 创建目录列表节点。\n * @param tocItems 当前层级的目录条目。\n * @param isNested 是否为嵌套列表。\n * @returns Markdown 列表节点。\n */\nfunction createTocListNode(tocItems: TocTreeItem[], isNested: boolean): TocList {\n // 当前目录列表需要输出的类名。\n const tocListClassNames = isNested\n ? [TOC_LIST_CLASS_NAME, TOC_LIST_NESTED_CLASS_NAME]\n : [TOC_LIST_CLASS_NAME];\n\n return {\n type: \"tocList\",\n data: {\n hName: \"ol\",\n hProperties: {\n className: tocListClassNames\n }\n },\n children: tocItems.map(createTocListItem)\n };\n}\n\n/**\n * 创建目录列表项。\n * @param tocItem 目录树条目。\n * @returns Markdown 列表项节点。\n */\nfunction createTocListItem(tocItem: TocTreeItem): TocItem {\n // 当前条目是否拥有可折叠的子层级。\n const hasChildren = tocItem.children.length > 0;\n // 当前目录条目的 class 列表。\n const tocItemClassNames = createTocItemClassNames(tocItem.depth, hasChildren);\n\n // 关键步骤:叶子与分支的标题都用同一个 <a href=\"#id\">(createTocLinkNode)、共用同一套跳转;\n // 分支只是额外在标题前放一个「独立的折叠按钮」、标题后放嵌套子列表。\n // 折叠按钮与标题链接分离,从结构上消除「点标题既跳转又折叠」的冲突。\n const itemChildren: TocItem[\"children\"] = hasChildren\n ? [createTocToggleNode(), createTocLinkNode(tocItem), createTocListNode(tocItem.children, true)]\n : [createTocLinkNode(tocItem)];\n\n return {\n type: \"tocItem\",\n data: {\n hName: \"li\",\n hProperties: {\n dataTocIndex: tocItem.index,\n className: tocItemClassNames\n }\n },\n children: itemChildren\n };\n}\n\n/**\n * 生成目录条目所需的 class 列表,inline TOC 与 toolbar 抽屉共用。\n * @param depth 标题原始层级。\n * @param hasChildren 是否拥有可折叠子层级。\n * @returns class 列表。\n */\nfunction createTocItemClassNames(depth: number, hasChildren: boolean): string[] {\n return [\n TOC_ITEM_CLASS_PREFIX,\n `${TOC_ITEM_CLASS_PREFIX}--depth-${depth}`,\n ...(hasChildren ? [TOC_ITEM_BRANCH_CLASS_NAME] : [])\n ];\n}\n\n/**\n * 创建目录分支的折叠按钮节点(与标题链接分离,专职展开/折叠)。\n * 默认 aria-expanded=\"true\"(展开),运行时由 {@link hydrateToc} 绑定点击切换。\n * @returns 可被 remark-rehype 转换为 button 的 Markdown 节点。\n */\nfunction createTocToggleNode(): TocToggle {\n return {\n type: \"tocToggle\",\n data: {\n hName: \"button\",\n hProperties: {\n type: \"button\",\n className: [TOC_TOGGLE_CLASS_NAME],\n ariaExpanded: \"true\",\n ariaLabel: TOC_TOGGLE_ARIA_LABEL\n }\n },\n children: []\n };\n}\n\n/**\n * 创建目录条目的标题跳转链接(叶子与分支共用)。\n * 仅产出原生 <a href=\"#id\">,由宿主锚点处理(webview 拦截器 / 浏览器原生 hash)统一滚动,\n * 渲染器不再附加点击监听,避免与宿主重复触发滚动。\n * @param tocItem 目录树条目。\n * @returns Markdown 链接节点。\n */\nfunction createTocLinkNode(tocItem: TocTreeItem): Link {\n return {\n type: \"link\",\n url: `#${tocItem.id}`,\n data: {\n hProperties: {\n className: [TOC_LINK_CLASS_NAME]\n }\n },\n children: [\n {\n type: \"text\",\n value: tocItem.text\n }\n ]\n };\n}\n\n/**\n * 生成去重后的标题锚点 id。\n * slug 规则完全交给 github-slugger(与 GitHub / VS Code 内置预览同源),\n * 仅补充空标题(或纯符号标题被删空)时的 \"section-N\" 回退,github-slugger 对空结果返回空串。\n * @param headingSlugger 当前文档的 slugger 实例(内部自带重复计数)。\n * @param headingText 标题文本。\n * @param fallbackIndex 空标题回退序号。\n * @returns 唯一标题锚点。\n */\nfunction createHeadingId(\n headingSlugger: GithubSlugger,\n headingText: string,\n fallbackIndex: number\n): string {\n // github-slugger 生成的基础 slug(重复标题自动追加 -1 / -2 后缀)。\n const baseSlug = headingSlugger.slug(headingText);\n\n if (baseSlug) {\n return baseSlug;\n }\n\n // 关键步骤:空 slug 走回退命名,也经 slugger 登记,与后续同名标题保持互斥。\n return headingSlugger.slug(`${EMPTY_HEADING_SLUG_PREFIX}-${fallbackIndex}`);\n}\n\n/**\n * 根据标题层级生成目录编号。\n * @param headingDepth 当前标题层级。\n * @param headingIndexCounts 各标题层级已出现次数。\n * @param rootHeadingDepth 目录根标题层级。\n * @returns 层级化目录编号。\n */\nfunction createHeadingIndex(\n headingDepth: number,\n headingIndexCounts: number[],\n rootHeadingDepth: number\n): string {\n // 起始层级不能超过当前标题层级。\n const startDepth = Math.min(rootHeadingDepth, headingDepth);\n\n headingIndexCounts[headingDepth] += 1;\n\n // 关键步骤:当前层级之后的子层级计数失效。\n for (let depthIndex = headingDepth + 1; depthIndex < headingIndexCounts.length; depthIndex += 1) {\n headingIndexCounts[depthIndex] = 0;\n }\n\n // 补齐被跳过的父级,避免出现 1.0.1 这种编号。\n for (let depthIndex = startDepth; depthIndex < headingDepth; depthIndex += 1) {\n if (headingIndexCounts[depthIndex] === 0) {\n headingIndexCounts[depthIndex] = 1;\n }\n }\n\n return headingIndexCounts.slice(startDepth, headingDepth + 1).join(\".\");\n}\n\n/**\n * 从已渲染的 DOM 中收集带 id 的标题,转换为目录条目。\n * 与 inline TOC 共用 {@link createHeadingIndex} 生成层级编号,使 toolbar 抽屉与文档内目录的层级逻辑一致。\n * @param rootElement 含 Markdown 渲染结果的根节点。\n * @returns 扁平目录标题条目。\n */\nfunction collectTocHeadingsFromDom(rootElement: ParentNode): TocHeading[] {\n /** Markdown 渲染容器;rootElement 自身或其后代命中 .scribdown-markdown 时即视为容器,否则视为未渲染。 */\n const markdownContainer =\n rootElement instanceof Element && rootElement.matches(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME}`)\n ? rootElement\n : rootElement.querySelector<HTMLElement>(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME}`);\n if (!markdownContainer) {\n return [];\n }\n\n /** 渲染区域内所有带 id 的标题元素。 */\n const headingElements = Array.from(\n markdownContainer.querySelectorAll<HTMLHeadingElement>(\n \"h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]\"\n )\n );\n\n /** 收集结果。 */\n const tocHeadings: TocHeading[] = [];\n /** 各标题层级的计数器。 */\n const headingIndexCounts = Array.from({ length: 7 }, () => 0);\n /** 文档内首个标题的层级,作为目录序号根层级。 */\n let rootHeadingDepth: number | undefined;\n\n for (const headingElement of headingElements) {\n // 从标签名解析当前标题层级(h1..h6)。\n const headingDepth = parseInt(headingElement.tagName.slice(1), 10);\n if (!Number.isFinite(headingDepth)) {\n continue;\n }\n\n // 标题可见文本。\n const headingText = headingElement.textContent?.trim() ?? \"\";\n // 标题锚点 id,由 inline TOC 渲染时写入。\n const headingId = headingElement.id;\n // 与 inline TOC 共用的层级编号生成逻辑。\n const headingIndex = createHeadingIndex(\n headingDepth,\n headingIndexCounts,\n rootHeadingDepth ?? headingDepth\n );\n rootHeadingDepth = rootHeadingDepth ?? headingDepth;\n\n tocHeadings.push({\n depth: headingDepth,\n id: headingId,\n index: headingIndex,\n text: headingText || headingId\n });\n }\n\n return tocHeadings;\n}\n\n/**\n * 用与 inline TOC 完全一致的结构构建目录 nav 节点(toolbar 侧栏复用)。\n * 标题统一为原生 <a href=\"#id\">(跳转交给宿主锚点处理),分支额外带独立折叠按钮 + 嵌套列表;\n * 折叠交互由挂载后调用的 {@link hydrateToc} 统一绑定。\n * @param ownerDocument 目标 document。\n * @param tocTreeItems 目录树根节点集合。\n * @returns 含完整目录列表的 nav 元素。\n */\nfunction createTocNavElement(ownerDocument: Document, tocTreeItems: TocTreeItem[]): HTMLElement {\n const navElement = ownerDocument.createElement(\"nav\");\n navElement.setAttribute(\"aria-label\", TOC_ARIA_LABEL);\n navElement.className = TOC_NAV_CLASS_NAME;\n navElement.appendChild(createTocListElement(ownerDocument, tocTreeItems, false));\n return navElement;\n}\n\n/**\n * 构造目录列表元素,与 inline TOC 的 ol 结构保持一致。\n * @param ownerDocument 目标 document。\n * @param tocItems 当前层级的目录条目。\n * @param isNested 是否为嵌套列表。\n * @param options 共享构造选项。\n * @returns ol 列表元素。\n */\nfunction createTocListElement(\n ownerDocument: Document,\n tocItems: TocTreeItem[],\n isNested: boolean\n): HTMLOListElement {\n const listElement = ownerDocument.createElement(\"ol\");\n listElement.className = isNested\n ? `${TOC_LIST_CLASS_NAME} ${TOC_LIST_NESTED_CLASS_NAME}`\n : TOC_LIST_CLASS_NAME;\n\n for (const tocItem of tocItems) {\n listElement.appendChild(createTocListItemElement(ownerDocument, tocItem));\n }\n\n return listElement;\n}\n\n/**\n * 构造目录列表项(li):叶子与分支共用同一个标题链接 <a href=\"#id\">。\n * 分支额外在标题前插入「独立折叠按钮」、标题后插入嵌套子列表;不再用 <details>/<summary>,\n * 从结构上消除「点标题既跳转又折叠」的冲突与 offsetX 像素 hack。\n * 跳转交给宿主锚点处理(不挂渲染器点击监听,避免与宿主重复触发滚动);\n * 折叠交互由 {@link hydrateToc} 在挂载后统一绑定。\n * @param ownerDocument 目标 document。\n * @param tocItem 当前目录条目。\n * @returns li 元素。\n */\nfunction createTocListItemElement(ownerDocument: Document, tocItem: TocTreeItem): HTMLLIElement {\n /** 当前条目是否拥有可折叠的子层级。 */\n const hasChildren = tocItem.children.length > 0;\n\n const itemElement = ownerDocument.createElement(\"li\");\n itemElement.className = createTocItemClassNames(tocItem.depth, hasChildren).join(\" \");\n itemElement.dataset.tocIndex = tocItem.index;\n\n // 分支:标题前插入独立折叠按钮(与 inline TOC 同构,hydrate 时绑定展开/折叠)。\n if (hasChildren) {\n const toggleElement = ownerDocument.createElement(\"button\");\n toggleElement.type = \"button\";\n toggleElement.className = TOC_TOGGLE_CLASS_NAME;\n toggleElement.setAttribute(\"aria-expanded\", \"true\");\n toggleElement.setAttribute(\"aria-label\", TOC_TOGGLE_ARIA_LABEL);\n itemElement.appendChild(toggleElement);\n }\n\n // 标题链接:叶子与分支完全相同,仅产出原生 <a href=\"#id\">,跳转交给宿主锚点处理。\n const linkElement = ownerDocument.createElement(\"a\");\n linkElement.className = TOC_LINK_CLASS_NAME;\n linkElement.href = `#${tocItem.id}`;\n linkElement.textContent = tocItem.text;\n itemElement.appendChild(linkElement);\n\n // 分支:标题后插入嵌套子列表。\n if (hasChildren) {\n itemElement.appendChild(createTocListElement(ownerDocument, tocItem.children, true));\n }\n\n return itemElement;\n}\n\n/**\n * 为目录绑定运行时交互(inline TOC 与 toolbar 侧栏共用):\n * 1. 分支折叠按钮的展开/折叠;\n * 2. 标题链接的跳转 —— 在冒泡阶段拦截原生 hash 跳转,改用注入的 scrollToHeading 滚动到标题\n * (webview 宿主拦截器对 .scribdown-toc-link 放行,跳转滚动完全交给这里)。\n * 滚动实现由宿主注入(缺省原生平滑),渲染器只负责调用——平台差异落在 apps/* 各宿主。\n * 幂等:已绑定过的元素(带 hydrate 标记)跳过,可安全在每次 hydrate / 挂载时重复调用。\n * @param rootElement 含目录结构的根节点。\n * @param scrollToHeading 跳转到目标标题的滚动实现(由宿主注入)。\n */\nfunction hydrateToc(\n rootElement: ParentNode,\n scrollToHeading: (targetElement: HTMLElement) => void\n): void {\n // 根节点内所有目录折叠按钮。\n const toggleElements = rootElement.querySelectorAll<HTMLButtonElement>(\n `button.${TOC_TOGGLE_CLASS_NAME}`\n );\n\n toggleElements.forEach((toggleElement) => {\n if (toggleElement.dataset[TOC_TOGGLE_HYDRATED_DATA_KEY] === \"true\") {\n return;\n }\n toggleElement.dataset[TOC_TOGGLE_HYDRATED_DATA_KEY] = \"true\";\n\n toggleElement.addEventListener(\"click\", () => {\n // 按钮所属的目录条目(承载折叠态 class,并包含待显隐的嵌套列表)。\n const itemElement = toggleElement.closest<HTMLLIElement>(`.${TOC_ITEM_CLASS_PREFIX}`);\n if (!itemElement) {\n return;\n }\n // 切换后是否处于折叠态。\n const nextCollapsed = !itemElement.classList.contains(TOC_ITEM_COLLAPSED_CLASS_NAME);\n itemElement.classList.toggle(TOC_ITEM_COLLAPSED_CLASS_NAME, nextCollapsed);\n // 关键步骤:aria-expanded 与折叠态保持同步,供无障碍读屏与 CSS 箭头朝向使用。\n toggleElement.setAttribute(\"aria-expanded\", String(!nextCollapsed));\n });\n });\n\n // 根节点内所有目录标题跳转链接。\n const linkElements = rootElement.querySelectorAll<HTMLAnchorElement>(\n `a.${TOC_LINK_CLASS_NAME}`\n );\n\n linkElements.forEach((linkElement) => {\n if (linkElement.dataset[TOC_LINK_HYDRATED_DATA_KEY] === \"true\") {\n return;\n }\n linkElement.dataset[TOC_LINK_HYDRATED_DATA_KEY] = \"true\";\n\n linkElement.addEventListener(\"click\", (event) => {\n // 关键步骤:拦截原生 hash 跳转(webview 下会触发被 CSP 拦截的 iframe 导航),\n // 改由宿主注入的 scrollToHeading 滚动定位。\n event.preventDefault();\n // 链接所属 document。\n const ownerDocument = linkElement.ownerDocument;\n // 目标标题 id(去掉前导 #)。\n const rawTargetId = (linkElement.getAttribute(\"href\") ?? \"\").slice(1);\n if (!rawTargetId) {\n return;\n }\n // 目标标题元素:优先按原文匹配,失败再尝试解码兜底(兼容被编码的中文 id)。\n let targetElement = ownerDocument.getElementById(rawTargetId);\n if (!targetElement) {\n try {\n targetElement = ownerDocument.getElementById(decodeURIComponent(rawTargetId));\n } catch {\n targetElement = null;\n }\n }\n if (targetElement) {\n scrollToHeading(targetElement);\n }\n });\n });\n}\n\nexport type { TocHeading, TocTreeItem };\nexport {\n remarkTableOfContents,\n hydrateToc,\n collectTocHeadingsFromDom,\n createTocTree,\n createTocNavElement\n};\n","/**\n * 浮动页面工具栏:目录侧栏、「更多」菜单(回到顶部 / 页面宽度 / 关于)\n * 与当前章节 scrollspy 指示器。\n */\n\nimport {\n CONTENT_WIDTH_STORAGE_KEY,\n PROJECT_HOMEPAGE_URL,\n SCRIBDOWN_CONTENT_AREA_CLASS_NAME,\n SCRIBDOWN_CONTENT_SCROLL_CLASS_NAME,\n SCRIBDOWN_MARKDOWN_CLASS_NAME,\n SCRIBDOWN_THIN_SCROLLBAR_CLASS_NAME,\n SCRIBDOWN_TOC_HOST_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_BTN_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_CURRENT_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_CURRENT_TEXT_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_MENU_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_MENU_GROUP_CHOICES_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_MENU_GROUP_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_MENU_ITEM_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_MENU_SELECT_CHEVRON_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_MENU_SELECT_LABEL_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_MENU_SELECT_VALUE_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_LABEL_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_MARK_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_TOC_PANEL_CLOSE_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_TOC_PANEL_EMPTY_CLASS_NAME,\n SCRIBDOWN_TOOLBAR_TOC_PANEL_TITLE_CLASS_NAME,\n TOC_LINK_ACTIVE_CLASS_NAME,\n TOC_LINK_CLASS_NAME\n} from \"@scribdown/shared\";\n\nimport {\n collectTocHeadingsFromDom,\n createTocNavElement,\n createTocTree,\n hydrateToc\n} from \"./syntax/toc\";\n\n// ─── Page Toolbar ────────────────────────────────────────────────────────────\n\n// 页面宽度预设列表(label 用于按钮徽章,value 为 CSS max-width 值)。\nconst TOOLBAR_WIDTH_PRESETS: Array<{ label: string; value: string }> = [\n { label: \"680\", value: \"680px\" },\n { label: \"840\", value: \"840px\" },\n { label: \"1080\", value: \"1080px\" },\n { label: \"100%\", value: \"100%\" }\n];\n\n// 默认内容宽度。\nconst TOOLBAR_DEFAULT_WIDTH = \"840px\";\n\n/**\n * 从 localStorage 读取已保存的内容宽度,不可用时返回默认值。\n * @returns 宽度 CSS 值字符串。\n */\nfunction loadContentWidth(): string {\n try {\n return localStorage.getItem(CONTENT_WIDTH_STORAGE_KEY) ?? TOOLBAR_DEFAULT_WIDTH;\n } catch {\n return TOOLBAR_DEFAULT_WIDTH;\n }\n}\n\n/**\n * 将内容宽度写入 localStorage。\n * @param value 宽度 CSS 值字符串。\n */\nfunction saveContentWidth(value: string): void {\n try {\n localStorage.setItem(CONTENT_WIDTH_STORAGE_KEY, value);\n } catch {\n // localStorage 不可用时静默跳过。\n }\n}\n\n/**\n * 把内容宽度应用到文档根节点的 CSS 自定义属性。\n * @param ownerDocument 目标 document。\n * @param value 宽度 CSS 值字符串。\n */\nfunction applyContentWidth(ownerDocument: Document, value: string): void {\n ownerDocument.documentElement.style.setProperty(\"--scribdown-content-width\", value);\n}\n\n/**\n * 创建一个工具栏按钮。\n * @param ownerDocument 目标 document。\n * @param ariaLabel 可访问名称(同时用作 tooltip)。\n * @param svgContent 按钮内嵌 SVG 字符串。\n * @returns 按钮元素。\n */\nfunction createPageToolbarBtn(\n ownerDocument: Document,\n ariaLabel: string,\n svgContent: string\n): HTMLButtonElement {\n const btn = ownerDocument.createElement(\"button\");\n btn.type = \"button\";\n btn.className = SCRIBDOWN_TOOLBAR_BTN_CLASS_NAME;\n btn.setAttribute(\"aria-label\", ariaLabel);\n btn.innerHTML = svgContent;\n return btn;\n}\n\n/**\n * 工具栏目录按钮的内嵌 SVG 字符串。\n */\nconst TOOLBAR_TOC_ICON_SVG =\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">' +\n '<line x1=\"3\" y1=\"5\" x2=\"15\" y2=\"5\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>' +\n '<line x1=\"3\" y1=\"9\" x2=\"11\" y2=\"9\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>' +\n '<line x1=\"3\" y1=\"13\" x2=\"13\" y2=\"13\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>' +\n \"</svg>\";\n\n/**\n * 工具栏「更多」按钮的内嵌 SVG 字符串(三个横向点)。\n */\nconst TOOLBAR_MORE_ICON_SVG =\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">' +\n '<circle cx=\"4\" cy=\"9\" r=\"1.4\" fill=\"currentColor\"/>' +\n '<circle cx=\"9\" cy=\"9\" r=\"1.4\" fill=\"currentColor\"/>' +\n '<circle cx=\"14\" cy=\"9\" r=\"1.4\" fill=\"currentColor\"/>' +\n \"</svg>\";\n\n/**\n * 「回到顶部」菜单项的内嵌 SVG 字符串。\n */\nconst TOOLBAR_BACK_TOP_ICON_SVG =\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">' +\n '<path d=\"M9 14V5M9 5L5 9M9 5l4 4\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>' +\n '<line x1=\"4\" y1=\"3.5\" x2=\"14\" y2=\"3.5\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>' +\n \"</svg>\";\n\n/**\n * 「切换页面宽度」菜单项的内嵌 SVG 字符串。\n */\nconst TOOLBAR_WIDTH_ICON_SVG =\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">' +\n '<path d=\"M3 6v6M15 6v6\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>' +\n '<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\"/>' +\n \"</svg>\";\n\n/**\n * 「关于」菜单项的内嵌 SVG 字符串(信息图标)。\n */\nconst TOOLBAR_ABOUT_ICON_SVG =\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 18 18\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">' +\n '<circle cx=\"9\" cy=\"9\" r=\"6.5\" stroke=\"currentColor\" stroke-width=\"1.6\"/>' +\n '<path d=\"M9 8v4\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>' +\n '<circle cx=\"9\" cy=\"5.6\" r=\"0.9\" fill=\"currentColor\"/>' +\n \"</svg>\";\n\n/**\n * 每个 container 上当前工具栏实例的清理函数。\n * 重复挂载前先调用,断开上一轮注册在 document / window 上的监听\n * (「更多」菜单的点外部收起、scrollspy 的 scroll/resize),避免监听堆积与对已卸载 DOM 的写入。\n */\nconst pageToolbarTeardowns = new WeakMap<Element, () => void>();\n\n/**\n * 在指定容器内构造并挂载浮动工具栏:横向「目录」+「更多」两个入口。\n * 「更多」下拉菜单承载回到顶部、切换页面宽度等次要功能。\n * 重复调用时先清理 container 内的旧实例,保证每次 hydrate 只有一个工具栏。\n * 浏览器环境检查由对外的 {@link mountMarkdownToolbar} 完成。\n * @param ownerDocument 目标 document。\n * @param container 工具栏与目录抽屉的物理挂载点,同时作为目录采集作用域。\n * @param scrollToHeading 目录跳转到目标标题的滚动实现(由宿主注入)。\n */\nfunction mountPageToolbar(\n ownerDocument: Document,\n container: Element,\n scrollToHeading: (targetElement: HTMLElement) => void\n): void {\n // 关键步骤:重挂载前先执行上一轮实例的清理,断开 document / window 监听,避免重复绑定。\n pageToolbarTeardowns.get(container)?.();\n pageToolbarTeardowns.delete(container);\n\n /** 本轮实例的清理回调集合,挂载过程中注册的全局监听都在此登记,重挂载前统一断开。 */\n const teardownCallbacks: Array<() => void> = [];\n\n // 移除 container 作用域内的旧实例,避免重渲染后重复挂载。\n container.querySelector(`:scope > .${SCRIBDOWN_TOOLBAR_CLASS_NAME}`)?.remove();\n container.querySelector(`:scope > .${SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME}`)?.remove();\n\n // 关键步骤:把 container 已有内容包进 .scribdown-content-area > .scribdown-content-scroll,\n // 形成统一布局:container 是 flex 容器(TOC + 正文横向并列),content-area flex:1 占满剩余宽度,\n // 其内部的 content-scroll 以 absolute+inset:0 铺满并承载横向 / 纵向滚动(外层不滚动)。\n // 幂等:复用已存在的 wrapper / 滚动层,避免重复包装。\n container.classList.add(SCRIBDOWN_TOC_HOST_CLASS_NAME);\n\n /** 正文列容器(flex item,占满 TOC 之外的剩余宽度,作为滚动层的定位上下文)。 */\n let contentArea = container.querySelector(\n `:scope > .${SCRIBDOWN_CONTENT_AREA_CLASS_NAME}`\n ) as HTMLElement | null;\n if (!contentArea) {\n contentArea = ownerDocument.createElement(\"div\");\n contentArea.className = SCRIBDOWN_CONTENT_AREA_CLASS_NAME;\n container.appendChild(contentArea);\n }\n\n /** 正文内部滚动层(absolute+inset:0 铺满 content-area,正文实际在此滚动)。 */\n let contentScroll = contentArea.querySelector(\n `:scope > .${SCRIBDOWN_CONTENT_SCROLL_CLASS_NAME}`\n ) as HTMLElement | null;\n if (!contentScroll) {\n contentScroll = ownerDocument.createElement(\"div\");\n // 关键步骤:正文滚动层与目录侧栏共用同一套细滚动条样式,显式 opt-in 引入。\n contentScroll.className = `${SCRIBDOWN_CONTENT_SCROLL_CLASS_NAME} ${SCRIBDOWN_THIN_SCROLLBAR_CLASS_NAME}`;\n contentArea.appendChild(contentScroll);\n }\n\n // 关键步骤:把散落在 container / content-area 下的「裸正文内容」(剔除工具栏 / 侧栏 / 两层 wrapper 自身)\n // 全部归集进滚动层;首次挂载时正文原本直接挂在 container 下,需移入,幂等情况下已在层内不会重复搬动。\n const contentScrollElement = contentScroll;\n const contentAreaElement = contentArea;\n [...Array.from(container.children), ...Array.from(contentArea.children)].forEach((child) => {\n if (\n child === contentAreaElement ||\n child === contentScrollElement ||\n child.classList.contains(SCRIBDOWN_TOOLBAR_CLASS_NAME) ||\n child.classList.contains(SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME)\n ) {\n return;\n }\n contentScrollElement.appendChild(child);\n });\n\n const ownerWindow = ownerDocument.defaultView;\n\n /** 工具栏容器(横向布局,仅承载「目录」「更多」两个入口)。 */\n const toolbar = ownerDocument.createElement(\"div\");\n toolbar.className = SCRIBDOWN_TOOLBAR_CLASS_NAME;\n\n // ── 目录抽屉(按钮回调需要引用,先建好)──\n /** 目录浮动面板。 */\n const tocPanel = ownerDocument.createElement(\"div\");\n // 关键步骤:目录侧栏与正文滚动层共用同一套细滚动条样式,显式 opt-in 引入。\n tocPanel.className = `${SCRIBDOWN_TOOLBAR_TOC_PANEL_CLASS_NAME} ${SCRIBDOWN_THIN_SCROLLBAR_CLASS_NAME}`;\n\n const tocTitle = ownerDocument.createElement(\"div\");\n tocTitle.className = SCRIBDOWN_TOOLBAR_TOC_PANEL_TITLE_CLASS_NAME;\n\n /** 抽屉顶部标题文本节点。 */\n const tocTitleText = ownerDocument.createElement(\"span\");\n tocTitleText.textContent = \"目录\";\n tocTitle.appendChild(tocTitleText);\n\n /** 抽屉关闭按钮(右上角 ×)。 */\n const tocCloseBtn = ownerDocument.createElement(\"button\");\n tocCloseBtn.type = \"button\";\n tocCloseBtn.className = SCRIBDOWN_TOOLBAR_TOC_PANEL_CLOSE_CLASS_NAME;\n tocCloseBtn.setAttribute(\"aria-label\", \"关闭目录\");\n tocCloseBtn.innerHTML =\n '<svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">' +\n '<path d=\"M4 4l8 8M12 4l-8 8\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\"/>' +\n \"</svg>\";\n /**\n * 切换目录侧栏的展开态:margin-left 由 CSS 配合 `.is-open` 在 flex 流中切换收起/展开。\n * @param open 是否打开侧栏。\n */\n const setTocOpen = (open: boolean): void => {\n tocPanel.classList.toggle(\"is-open\", open);\n };\n\n tocCloseBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n setTocOpen(false);\n });\n tocTitle.appendChild(tocCloseBtn);\n\n tocPanel.appendChild(tocTitle);\n\n // 关键步骤:与 inline [TOC] 共用层级数据 + DOM 结构,确保两处目录的层级渲染逻辑一致。\n const tocHeadings = collectTocHeadingsFromDom(container);\n if (tocHeadings.length === 0) {\n const emptyElement = ownerDocument.createElement(\"p\");\n emptyElement.className = SCRIBDOWN_TOOLBAR_TOC_PANEL_EMPTY_CLASS_NAME;\n emptyElement.textContent = \"暂无标题\";\n tocPanel.appendChild(emptyElement);\n } else {\n const tocTree = createTocTree(tocHeadings);\n // 关键步骤:与 inline [TOC] 共用同一个 createTocNavElement —— 叶子/分支同元素、同跳转。\n // 标题为原生 <a href=\"#id\">;折叠按钮与标题跳转随后由 hydrateToc 绑定,滚动用注入实现。\n const tocNavElement = createTocNavElement(ownerDocument, tocTree);\n tocPanel.appendChild(tocNavElement);\n hydrateToc(tocNavElement, scrollToHeading);\n }\n\n // ── 目录按钮(工具栏第 1 个入口) ──\n /** 目录切换按钮。 */\n const tocBtn = createPageToolbarBtn(ownerDocument, \"目录\", TOOLBAR_TOC_ICON_SVG);\n\n // ── 更多按钮(工具栏第 2 个入口) ──\n /** 「更多」按钮,点击展开下拉菜单。 */\n const moreBtn = createPageToolbarBtn(ownerDocument, \"更多\", TOOLBAR_MORE_ICON_SVG);\n moreBtn.classList.add(`${SCRIBDOWN_TOOLBAR_BTN_CLASS_NAME}--more`);\n moreBtn.setAttribute(\"aria-haspopup\", \"menu\");\n moreBtn.setAttribute(\"aria-expanded\", \"false\");\n\n // ── 更多下拉菜单 ──\n /** 「更多」下拉菜单容器。 */\n const menu = ownerDocument.createElement(\"div\");\n menu.className = SCRIBDOWN_TOOLBAR_MENU_CLASS_NAME;\n menu.setAttribute(\"role\", \"menu\");\n\n // 菜单项:切换页面宽度(下拉选择)\n // 关键步骤:宽度初始 index 取自 localStorage,兜底到默认宽度档位。\n let widthIndex = TOOLBAR_WIDTH_PRESETS.findIndex((p) => p.value === loadContentWidth());\n if (widthIndex === -1) {\n widthIndex = TOOLBAR_WIDTH_PRESETS.findIndex((p) => p.value === TOOLBAR_DEFAULT_WIDTH);\n }\n\n /** 宽度切换分组容器(包裹触发行与可折叠的档位列表)。 */\n const widthGroup = ownerDocument.createElement(\"div\");\n widthGroup.className = SCRIBDOWN_TOOLBAR_MENU_GROUP_CLASS_NAME;\n\n /** 宽度下拉触发行:左侧 icon + 文字、右侧当前档位值 + chevron。 */\n const widthTrigger = ownerDocument.createElement(\"button\");\n widthTrigger.type = \"button\";\n widthTrigger.className = `${SCRIBDOWN_TOOLBAR_MENU_ITEM_CLASS_NAME} ${SCRIBDOWN_TOOLBAR_MENU_ITEM_CLASS_NAME}--select`;\n widthTrigger.setAttribute(\"role\", \"menuitem\");\n widthTrigger.setAttribute(\"aria-haspopup\", \"listbox\");\n widthTrigger.setAttribute(\"aria-expanded\", \"false\");\n\n /** 触发行内当前档位值展示节点,切换档位时同步更新文本。 */\n const widthValueElement = ownerDocument.createElement(\"span\");\n widthValueElement.className = SCRIBDOWN_TOOLBAR_MENU_SELECT_VALUE_CLASS_NAME;\n widthValueElement.textContent = TOOLBAR_WIDTH_PRESETS[widthIndex].label;\n\n widthTrigger.innerHTML = `${TOOLBAR_WIDTH_ICON_SVG}<span class=\"${SCRIBDOWN_TOOLBAR_MENU_SELECT_LABEL_CLASS_NAME}\">页面宽度</span>`;\n widthTrigger.appendChild(widthValueElement);\n widthTrigger.insertAdjacentHTML(\n \"beforeend\",\n `<svg class=\"${SCRIBDOWN_TOOLBAR_MENU_SELECT_CHEVRON_CLASS_NAME}\" width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\" aria-hidden=\"true\">` +\n '<path d=\"M3 4.5l3 3 3-3\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>' +\n \"</svg>\"\n );\n\n widthGroup.appendChild(widthTrigger);\n\n /** 宽度档位列表(垂直 listbox,默认折叠,is-open 时展开)。 */\n const widthChoices = ownerDocument.createElement(\"div\");\n widthChoices.className = SCRIBDOWN_TOOLBAR_MENU_GROUP_CHOICES_CLASS_NAME;\n widthChoices.setAttribute(\"role\", \"listbox\");\n\n /** 已渲染的宽度子项 DOM 列表,用于切换 aria-selected。 */\n const widthSubItems: HTMLButtonElement[] = [];\n TOOLBAR_WIDTH_PRESETS.forEach((preset, idx) => {\n /** 单个宽度档位选项按钮。 */\n const subItem = ownerDocument.createElement(\"button\");\n subItem.type = \"button\";\n subItem.className = SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_CLASS_NAME;\n subItem.setAttribute(\"role\", \"option\");\n subItem.setAttribute(\"aria-selected\", String(idx === widthIndex));\n // 关键步骤:左侧勾选位预留固定宽度的占位,未选中时不可见,避免标签左右抖动。\n subItem.innerHTML =\n `<span class=\"${SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_MARK_CLASS_NAME}\" aria-hidden=\"true\">` +\n '<svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">' +\n '<path d=\"M2.5 6.5l2.5 2.5 4.5-5\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>' +\n \"</svg>\" +\n `</span><span class=\"${SCRIBDOWN_TOOLBAR_MENU_SUB_ITEM_LABEL_CLASS_NAME}\">${preset.label}</span>`;\n subItem.addEventListener(\"click\", () => {\n widthIndex = idx;\n applyContentWidth(ownerDocument, preset.value);\n saveContentWidth(preset.value);\n widthValueElement.textContent = preset.label;\n widthSubItems.forEach((item, i) => {\n item.setAttribute(\"aria-selected\", String(i === idx));\n });\n // 选完后收起下拉,更多菜单整体保持开启便于继续操作其他项。\n closeWidthSelect();\n });\n widthChoices.appendChild(subItem);\n widthSubItems.push(subItem);\n });\n widthGroup.appendChild(widthChoices);\n\n /** 关闭宽度下拉并同步 aria 状态。 */\n const closeWidthSelect = (): void => {\n widthGroup.classList.remove(\"is-open\");\n widthTrigger.setAttribute(\"aria-expanded\", \"false\");\n };\n\n widthTrigger.addEventListener(\"click\", () => {\n const opened = widthGroup.classList.toggle(\"is-open\");\n widthTrigger.setAttribute(\"aria-expanded\", String(opened));\n });\n\n /** 关闭「更多」菜单并同步 aria 状态,同时重置嵌套的宽度下拉。 */\n const closeMoreMenu = (): void => {\n menu.classList.remove(\"is-open\");\n moreBtn.setAttribute(\"aria-expanded\", \"false\");\n closeWidthSelect();\n };\n\n // 菜单项:回到顶部(声明顺序晚于 closeMoreMenu,便于在点击回调里关闭整个菜单)。\n /** 「回到顶部」菜单项。 */\n const backTopItem = ownerDocument.createElement(\"button\");\n backTopItem.type = \"button\";\n backTopItem.className = SCRIBDOWN_TOOLBAR_MENU_ITEM_CLASS_NAME;\n backTopItem.setAttribute(\"role\", \"menuitem\");\n backTopItem.innerHTML = `${TOOLBAR_BACK_TOP_ICON_SVG}<span>回到顶部</span>`;\n backTopItem.addEventListener(\"click\", () => {\n // 关键步骤:正文滚动容器是 .scribdown-content-scroll,优先滚动它回到顶部;\n // 同时兜底滚动 window,兼容仍以整页滚动的宿主(无害的空操作)。\n contentScroll?.scrollTo({ top: 0, behavior: \"smooth\" });\n ownerWindow?.scrollTo({ top: 0, behavior: \"smooth\" });\n closeMoreMenu();\n });\n\n // 菜单项:关于(点击在新标签页打开项目 GitHub Pages 主页)。\n // 关键步骤:用原生 <a> 而非 button + window.open —— VS Code Webview 沙箱禁用 window.open,\n // 但 <a> 点击会被 Webview 侧的链接拦截器捕获并转交扩展进程 openExternal;\n // 浏览器宿主则直接走原生新标签页行为,两端共用同一实现。\n /** 「关于」菜单项。 */\n const aboutItem = ownerDocument.createElement(\"a\");\n aboutItem.href = PROJECT_HOMEPAGE_URL;\n aboutItem.target = \"_blank\";\n // noopener/noreferrer 阻断被打开页对本页的引用。\n aboutItem.rel = \"noopener noreferrer\";\n aboutItem.className = SCRIBDOWN_TOOLBAR_MENU_ITEM_CLASS_NAME;\n aboutItem.setAttribute(\"role\", \"menuitem\");\n aboutItem.innerHTML = `${TOOLBAR_ABOUT_ICON_SVG}<span>关于</span>`;\n aboutItem.addEventListener(\"click\", () => {\n closeMoreMenu();\n });\n\n // 关键步骤:菜单项按可见顺序追加 —— 回到顶部 → 切换页面宽度(下拉)→ 关于。\n menu.appendChild(backTopItem);\n menu.appendChild(widthGroup);\n menu.appendChild(aboutItem);\n\n tocBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n /** 切换前 tocPanel 是否打开。 */\n const wasOpen = tocPanel.classList.contains(\"is-open\");\n setTocOpen(!wasOpen);\n // 打开目录侧栏时同时收起更多菜单,避免视觉重叠。\n if (!wasOpen) {\n closeMoreMenu();\n }\n });\n\n moreBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const opened = menu.classList.toggle(\"is-open\");\n moreBtn.setAttribute(\"aria-expanded\", String(opened));\n });\n\n // 点击「更多」菜单外部时收起菜单;目录侧栏不参与外部点击关闭,\n // 仅由目录按钮再次点击或侧栏顶部 × 主动关闭,避免阅读时误触收起。\n /** document 级点外部收起监听,随实例清理移除,避免重挂载后监听堆积。 */\n const handleDocumentClick = (e: MouseEvent): void => {\n const target = e.target as Node;\n if (!menu.contains(target) && target !== moreBtn) {\n closeMoreMenu();\n }\n };\n ownerDocument.addEventListener(\"click\", handleDocumentClick);\n teardownCallbacks.push(() => {\n ownerDocument.removeEventListener(\"click\", handleDocumentClick);\n });\n\n // ── 当前章节指示器(工具栏最左侧)──\n // 关键步骤:收集正文标题做 scrollspy,随滚动展示「当前可视区」所属标题;\n // 窄条最宽 30px、超出省略号,hover 时由 ::after 读取 aria-label 显示全文。\n /** 正文 Markdown 容器,作为标题采集作用域。 */\n const markdownContainer = container.matches(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME}`)\n ? (container as HTMLElement)\n : container.querySelector<HTMLElement>(`.${SCRIBDOWN_MARKDOWN_CLASS_NAME}`);\n /** 正文内全部带 id 的标题元素,按文档顺序排列,用于判定当前章节。 */\n const headingElements = markdownContainer\n ? Array.from(\n markdownContainer.querySelectorAll<HTMLHeadingElement>(\n \"h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]\"\n )\n )\n : [];\n\n if (headingElements.length > 0) {\n /** 判定线:标题 top 越过工具栏下沿(距视口顶 48px)即视为已进入当前章节。 */\n const ACTIVE_HEADING_OFFSET = 48;\n\n /** 指示器容器(position:relative 承载 hover tip),点击跳转到当前章节标题。 */\n const currentHeading = ownerDocument.createElement(\"div\");\n currentHeading.className = SCRIBDOWN_TOOLBAR_CURRENT_CLASS_NAME;\n // 关键步骤:作为可点击元素暴露给辅助技术,并支持键盘聚焦 / 触发。\n currentHeading.setAttribute(\"role\", \"button\");\n currentHeading.tabIndex = 0;\n\n /** 指示器可见文本节点(max-width 30px,超出省略号)。 */\n const currentHeadingText = ownerDocument.createElement(\"span\");\n currentHeadingText.className = SCRIBDOWN_TOOLBAR_CURRENT_TEXT_CLASS_NAME;\n currentHeading.appendChild(currentHeadingText);\n\n /** 上一次写入的标题文本,相同则跳过 DOM 写入。 */\n let lastHeadingText = \"\";\n /** 当前命中的标题元素,供点击跳转使用。 */\n let activeHeadingElement: HTMLHeadingElement = headingElements[0];\n\n // ── 目录侧栏「当前章节」高亮(复用同一份 scrollspy 结果)──\n // 关键步骤:建立「正文标题元素 → 目录链接」映射,使 scrollspy 命中标题后可反查并高亮对应目录项。\n // 链接目标 id 解析与 hydrateToc 一致:优先原文匹配,失败再解码兜底(兼容被编码的中文 id)。\n /** 正文标题元素 → 目录侧栏对应跳转链接的映射。 */\n const tocLinkByHeading = new Map<HTMLHeadingElement, HTMLAnchorElement>();\n tocPanel\n .querySelectorAll<HTMLAnchorElement>(`a.${TOC_LINK_CLASS_NAME}`)\n .forEach((linkElement) => {\n /** 链接目标标题 id(去掉前导 #)。 */\n const rawTargetId = (linkElement.getAttribute(\"href\") ?? \"\").slice(1);\n if (!rawTargetId) {\n return;\n }\n /** 链接指向的标题元素,按原文匹配失败时解码兜底。 */\n let targetHeading = ownerDocument.getElementById(rawTargetId);\n if (!targetHeading) {\n try {\n targetHeading = ownerDocument.getElementById(decodeURIComponent(rawTargetId));\n } catch {\n targetHeading = null;\n }\n }\n if (targetHeading instanceof HTMLHeadingElement) {\n tocLinkByHeading.set(targetHeading, linkElement);\n }\n });\n\n /** 当前高亮的目录链接;undefined 表示尚未高亮任何项。 */\n let activeTocLink: HTMLAnchorElement | undefined;\n\n /**\n * 把目录侧栏「当前章节」高亮切换到指定标题对应的链接上。\n * 按链接元素去重,未变化时直接返回,避免每帧重复读写 DOM。\n * @param headingElement 当前命中的正文标题元素。\n */\n const setActiveTocLink = (headingElement: HTMLHeadingElement): void => {\n /** 命中标题对应的目录链接,无对应项时为 undefined。 */\n const nextLink = tocLinkByHeading.get(headingElement);\n if (nextLink === activeTocLink) {\n return;\n }\n if (activeTocLink) {\n activeTocLink.classList.remove(TOC_LINK_ACTIVE_CLASS_NAME);\n activeTocLink.removeAttribute(\"aria-current\");\n }\n if (nextLink) {\n nextLink.classList.add(TOC_LINK_ACTIVE_CLASS_NAME);\n // 关键步骤:aria-current 把「当前所在位置」语义同步给读屏,与高亮类保持一致。\n nextLink.setAttribute(\"aria-current\", \"location\");\n }\n activeTocLink = nextLink;\n };\n\n /**\n * 计算当前可视区所属标题并刷新指示器文本 + tip。\n * 取文档顺序中最后一个 top ≤ 判定线的标题;都未越线时回退到首个标题。\n */\n const updateCurrentHeading = (): void => {\n /** 命中的当前标题元素,默认首个标题。 */\n let activeElement: HTMLHeadingElement = headingElements[0];\n for (const headingElement of headingElements) {\n if (headingElement.getBoundingClientRect().top <= ACTIVE_HEADING_OFFSET) {\n activeElement = headingElement;\n } else {\n break;\n }\n }\n // 关键步骤:始终同步当前标题引用(即便文本未变),保证点击跳转目标准确。\n activeHeadingElement = activeElement;\n // 关键步骤:同步目录侧栏高亮到当前章节;按元素去重,独立于下方的文本去重短路。\n setActiveTocLink(activeElement);\n /** 当前标题可见文本。 */\n const headingText = activeElement.textContent?.trim() ?? \"\";\n if (headingText === lastHeadingText) {\n return;\n }\n lastHeadingText = headingText;\n currentHeadingText.textContent = headingText;\n // 关键步骤:全文写入 aria-label,hover 时由 ::after 读取展示完整标题。\n currentHeading.setAttribute(\"aria-label\", headingText);\n };\n\n /** 点击 / 回车:平滑滚动到当前章节标题,与「回到顶部」一致采用 smooth 行为。 */\n const jumpToActiveHeading = (): void => {\n activeHeadingElement.scrollIntoView({ behavior: \"smooth\", block: \"start\" });\n };\n currentHeading.addEventListener(\"click\", jumpToActiveHeading);\n currentHeading.addEventListener(\"keydown\", (event) => {\n // 关键步骤:role=button 需自行处理 Enter / Space 触发。\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n jumpToActiveHeading();\n }\n });\n\n /** rAF 节流句柄,避免滚动时每个事件都同步读取 rect。 */\n let scrollRafId = 0;\n /** 滚动/缩放回调:在一帧内合并一次计算。 */\n const onScrollOrResize = (): void => {\n if (scrollRafId) {\n return;\n }\n scrollRafId =\n ownerWindow?.requestAnimationFrame(() => {\n scrollRafId = 0;\n updateCurrentHeading();\n }) ?? 0;\n };\n\n // 关键步骤:正文已改为在 .scribdown-content-area 内部滚动(整页 window 不再滚动),\n // scroll 事件不冒泡,故用「捕获阶段」在 window 上监听,可同时捕获到内部滚动容器派发的 scroll。\n ownerWindow?.addEventListener(\"scroll\", onScrollOrResize, {\n passive: true,\n capture: true\n });\n ownerWindow?.addEventListener(\"resize\", onScrollOrResize, { passive: true });\n // 首次同步一次当前章节。\n updateCurrentHeading();\n\n // 关键步骤:登记清理回调,下次重挂载工具栏前断开窗口监听(capture 标志需与注册时一致)。\n teardownCallbacks.push(() => {\n if (scrollRafId) {\n ownerWindow?.cancelAnimationFrame(scrollRafId);\n }\n ownerWindow?.removeEventListener(\"scroll\", onScrollOrResize, { capture: true });\n ownerWindow?.removeEventListener(\"resize\", onScrollOrResize);\n });\n\n // 指示器置于工具栏最左侧(目录按钮之前)。\n toolbar.appendChild(currentHeading);\n }\n\n toolbar.appendChild(tocBtn);\n toolbar.appendChild(moreBtn);\n // 关键步骤:菜单挂在工具栏内部,便于以工具栏为锚点做绝对定位。\n toolbar.appendChild(menu);\n\n // 关键步骤:toolbar 仍为 fixed 浮动元素,append 到 container 末尾;\n // tocPanel 是 flex item,必须插在 contentArea 之前才能视觉上位于正文左侧。\n container.appendChild(toolbar);\n container.insertBefore(tocPanel, contentArea);\n\n // 关键步骤:登记本轮实例的统一清理函数,供下次重挂载前断开全部全局监听。\n pageToolbarTeardowns.set(container, () => {\n teardownCallbacks.forEach((teardownCallback) => {\n teardownCallback();\n });\n });\n}\n\nexport { mountPageToolbar, loadContentWidth, applyContentWidth };\n"],"mappings":";AAMA,OAAO,eAAe;AACtB,OAAO,oBAAoB;AAC3B,OAAO,qBAAqB;AAC5B,OAAO,uBAAuB;AAC9B,OAAO,eAAe;AACtB,OAAO,iBAAiB;AACxB,OAAO,kBAAkB;AACzB,SAAS,eAAe;;;ACRxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,IAAM,oCAAoC;AAG1C,IAAM,+BAA+B;AAGrC,IAAM,8BAAsD;AAAA,EAC1D,MAAM;AAAA,EACN,GAAG;AAAA,EACH,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,MAAM;AAAA,EACN,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AAAA,EACV,IAAI;AAAA,EACJ,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,KAAK;AAAA,EACL,OAAO;AAAA,EACP,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,KAAK;AAAA,EACL,YAAY;AAAA,EACZ,KAAK;AAAA,EACL,KAAK;AAAA,EACL,MAAM;AAAA,EACN,KAAK;AAAA,EACL,KAAK;AACP;AAGA,IAAM,8BAA8B;AAGpC,IAAM,mCAAmC;AAGzC,IAAM,6BAA6B;AAGnC,IAAM,oCAAoC;AAM1C,SAAS,kBAAkB,aAA+B;AAExD,QAAM,cAAc,YAAY,iBAAiC,KAAK;AAEtE,cAAY,QAAQ,iBAAiB;AACvC;AAMA,SAAS,kBAAkB,YAAkC;AAE3D,MAAI,WAAW,eAAe,UAAU,SAAS,qBAAqB,GAAG;AACvE;AAAA,EACF;AAEA,MAAI,WAAW,QAAQ,4BAA4B,MAAM,QAAQ;AAC/D;AAAA,EACF;AAGA,QAAM,cAAc,WAAW,cAA2B,eAAe;AAEzE,MAAI,CAAC,aAAa;AAChB;AAAA,EACF;AAEA,aAAW,QAAQ,4BAA4B,IAAI;AAGnD,QAAM,gBAAgB,WAAW;AAEjC,QAAM,aAAa,2BAA2B,WAAW;AAEzD,QAAM,gBAAgB,8BAA8B,UAAU;AAG9D,QAAM,mBAAmB,YAAY,eAAe;AAGpD,QAAM,gBAAgB,sBAAsB,aAAa,aAAa;AAGtE,QAAM,gBAAgB,cAAc,cAAc,KAAK;AACvD,gBAAc,YAAY;AAG1B,QAAM,cAAc,cAAc,cAAc,MAAM;AACtD,cAAY,YAAY;AACxB,cAAY,cAAc;AAC1B,gBAAc,OAAO,WAAW;AAGhC,QAAM,oBAAoB,0BAA0B,aAAa;AACjE,gBAAc,OAAO,iBAAiB;AAGtC,QAAM,gBAAgB,cAAc,cAAc,QAAQ;AAC1D,gBAAc,YAAY;AAC1B,gBAAc,QAAQ,oBAAoB;AAG1C,QAAM,cAAc,cAAc,cAAc,KAAK;AACrD,cAAY,YAAY;AAGxB,QAAM,gBAAgB,gCAAgC,eAAe,aAAa;AAGlF,aAAW,YAAY,aAAa;AACpC,cAAY,OAAO,eAAe,UAAU;AAC5C,gBAAc,OAAO,eAAe,WAAW;AAK/C,QAAM,sBAAsB,YAAY,aAAa,0BAA0B;AAE/E,MAAI,wBAAwB,MAAM;AAChC,kBAAc,aAAa,4BAA4B,mBAAmB;AAC1E,gBAAY,gBAAgB,0BAA0B;AAAA,EACxD;AAGA,oBAAkB,QAAQ,sBAAsB;AAChD,oBAAkB,iBAAiB,SAAS,wBAAwB;AACtE;AAOA,SAAS,2BAA2B,aAAkC;AAEpE,QAAM,oBAAoB,MAAM,KAAK,YAAY,SAAS,EAAE;AAAA,IAAK,CAAC,cAChE,UAAU,WAAW,WAAW;AAAA,EAClC;AAEA,MAAI,CAAC,mBAAmB;AACtB,WAAO;AAAA,EACT;AAEA,SAAO,kBAAkB,MAAM,YAAY,MAAM,EAAE,YAAY;AACjE;AAOA,SAAS,8BAA8B,YAA4B;AACjE,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AAGA,QAAM,cAAc,4BAA4B,UAAU;AAE1D,MAAI,aAAa;AACf,WAAO;AAAA,EACT;AAEA,SAAO,WAAW,OAAO,CAAC,EAAE,YAAY,IAAI,WAAW,MAAM,CAAC;AAChE;AASA,SAAS,sBAAsB,aAA0B,eAAiC;AAExF,QAAM,oBAAoB,YAAY,iBAA8B,oBAAoB;AAExF,MAAI,kBAAkB,SAAS,GAAG;AAEhC,UAAM,iBAAiB,MAAM,KAAK,iBAAiB;AAEnD,sBAAkB,QAAQ,CAAC,gBAAgB;AACzC,kBAAY,UAAU,IAAI,0BAA0B;AAEpD,UAAI,YAAY,gBAAgB,IAAI;AAClC,oBAAY,cAAc;AAAA,MAC5B;AAAA,IACF,CAAC;AAGD,gBAAY,gBAAgB,GAAG,cAAc;AAC7C,WAAO,eAAe;AAAA,EACxB;AAGA,QAAM,WAAW,YAAY,eAAe;AAE5C,QAAM,kBAAkB,SAAS,QAAQ,SAAS,EAAE;AAEpD,QAAM,YAAY,gBAAgB,MAAM,IAAI;AAG5C,SAAO,YAAY,YAAY;AAC7B,gBAAY,YAAY,YAAY,UAAU;AAAA,EAChD;AAEA,YAAU,QAAQ,CAAC,aAAa;AAE9B,UAAM,cAAc,cAAc,cAAc,MAAM;AACtD,gBAAY,YAAY;AAExB,QAAI,SAAS,WAAW,GAAG;AACzB,kBAAY,cAAc;AAAA,IAC5B,OAAO;AACL,kBAAY,cAAc;AAAA,IAC5B;AACA,gBAAY,OAAO,WAAW;AAAA,EAChC,CAAC;AAED,SAAO,UAAU;AACnB;AAQA,SAAS,gCACP,eACA,WACgB;AAEhB,QAAM,gBAAgB,cAAc,cAAc,KAAK;AACvD,gBAAc,YAAY;AAG1B,WAAS,YAAY,GAAG,YAAY,WAAW,aAAa,GAAG;AAE7D,UAAM,iBAAiB,OAAO,YAAY,CAAC;AAE3C,UAAM,oBAAoB,cAAc,cAAc,MAAM;AAC5D,sBAAkB,YAAY;AAC9B,sBAAkB,cAAc;AAChC,kBAAc,OAAO,iBAAiB;AAAA,EACxC;AAEA,SAAO;AACT;AAMA,IAAM,kCACJ;AAQF,IAAM,mCACJ;AAQF,SAAS,0BAA0B,eAA4C;AAE7E,QAAM,oBAAoB,cAAc,cAAc,QAAQ;AAC9D,oBAAkB,OAAO;AACzB,oBAAkB,YAAY;AAC9B,oBAAkB,aAAa,cAAc,0BAA0B;AAGvE,QAAM,kBAAkB,cAAc,gBAAgB,8BAA8B,KAAK;AACzF,kBAAgB;AAAA,IACd;AAAA,IACA,GAAG,+BAA+B,IAAI,oCAAoC;AAAA,EAC5E;AACA,kBAAgB,aAAa,WAAW,WAAW;AACnD,kBAAgB,aAAa,eAAe,MAAM;AAClD,kBAAgB,aAAa,aAAa,OAAO;AACjD,kBAAgB,YAAY;AAC5B,oBAAkB,OAAO,eAAe;AAGxC,QAAM,mBAAmB,cAAc,gBAAgB,8BAA8B,KAAK;AAC1F,mBAAiB;AAAA,IACf;AAAA,IACA,GAAG,+BAA+B,IAAI,qCAAqC;AAAA,EAC7E;AACA,mBAAiB,aAAa,WAAW,WAAW;AACpD,mBAAiB,aAAa,eAAe,MAAM;AACnD,mBAAiB,aAAa,aAAa,OAAO;AAClD,mBAAiB,YAAY;AAC7B,oBAAkB,OAAO,gBAAgB;AAEzC,SAAO;AACT;AAMA,SAAS,yBAAyB,OAAyB;AAEzD,QAAM,oBAAoB,MAAM;AAEhC,QAAM,WAAW,kBAAkB,QAAQ,uBAAuB;AAElE,OAAK,8BAA8B,QAAQ,EAAE,KAAK,CAAC,gBAAgB;AACjE,QAAI,aAAa;AACf,+BAAyB,iBAAiB;AAAA,IAC5C;AAAA,EACF,CAAC;AACH;AAOA,eAAe,8BAA8B,UAAoC;AAE/E,MAAI,OAAO,cAAc,eAAe,UAAU,WAAW,WAAW;AACtE,QAAI;AACF,YAAM,UAAU,UAAU,UAAU,QAAQ;AAC5C,aAAO;AAAA,IACT,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI,OAAO,aAAa,aAAa;AACnC,WAAO;AAAA,EACT;AAGA,QAAM,mBAAmB,SAAS,cAAc,UAAU;AAC1D,mBAAiB,QAAQ;AACzB,mBAAiB,aAAa,YAAY,EAAE;AAC5C,mBAAiB,MAAM,WAAW;AAClC,mBAAiB,MAAM,UAAU;AACjC,mBAAiB,MAAM,gBAAgB;AACvC,WAAS,KAAK,OAAO,gBAAgB;AACrC,mBAAiB,OAAO;AAGxB,QAAM,cAAc,SAAS,YAAY,MAAM;AAE/C,mBAAiB,OAAO;AACxB,SAAO;AACT;AAMA,SAAS,yBAAyB,mBAA4C;AAE5E,oBAAkB,QAAQ,sBAAsB;AAChD,oBAAkB,aAAa,cAAc,iCAAiC;AAG9E,SAAO,WAAW,MAAM;AACtB,WAAO,kBAAkB,QAAQ;AACjC,sBAAkB,aAAa,cAAc,0BAA0B;AAAA,EACzE,GAAG,gCAAgC;AACrC;;;AC/ZA,OAAO,sBAAsB;AAC7B,SAA+B,6BAA6B;AAC5D,SAAS,6BAA6B;;;ACW/B,IAAM,iCAAkD,CAAC;AAQzD,IAAM,8BAA6D;AAAA;AAAA,EAExE,MAAM,MAAM,OAAO,qBAAqB;AAAA,EACxC,IAAI,MAAM,OAAO,qBAAqB;AAAA,EACtC,OAAO,MAAM,OAAO,qBAAqB;AAAA,EACzC,KAAK,MAAM,OAAO,oBAAoB;AAAA,EACtC,MAAM,MAAM,OAAO,qBAAqB;AAAA,EACxC,MAAM,MAAM,OAAO,qBAAqB;AAAA,EACxC,IAAI,MAAM,OAAO,2BAA2B;AAAA,EAC5C,YAAY,MAAM,OAAO,2BAA2B;AAAA,EACpD,MAAM,MAAM,OAAO,qBAAqB;AAAA,EACxC,UAAU,MAAM,OAAO,yBAAyB;AAAA,EAChD,IAAI,MAAM,OAAO,yBAAyB;AAAA,EAC1C,IAAI,MAAM,OAAO,2BAA2B;AAAA,EAC5C,YAAY,MAAM,OAAO,2BAA2B;AAAA,EACpD,MAAM,MAAM,OAAO,qBAAqB;AAAA,EACxC,KAAK,MAAM,OAAO,qBAAqB;AAAA;AAAA,EAEvC,KAAK,MAAM,OAAO,oBAAoB;AAAA,EACtC,KAAK,MAAM,OAAO,oBAAoB;AAAA,EACtC,KAAK,MAAM,OAAO,oBAAoB;AAAA,EACtC,QAAQ,MAAM,OAAO,uBAAuB;AAAA,EAC5C,OAAO,MAAM,OAAO,sBAAsB;AAAA,EAC1C,KAAK,MAAM,OAAO,oBAAoB;AAAA;AAAA,EAEtC,MAAM,MAAM,OAAO,qBAAqB;AAAA,EACxC,MAAM,MAAM,OAAO,qBAAqB;AAAA,EACxC,MAAM,MAAM,OAAO,qBAAqB;AAAA,EACxC,SAAS,MAAM,OAAO,wBAAwB;AAAA,EAC9C,QAAQ,MAAM,OAAO,uBAAuB;AAAA;AAAA,EAE5C,SAAS,MAAM,OAAO,wBAAwB;AAAA,EAC9C,KAAK,MAAM,OAAO,wBAAwB;AAAA,EAC1C,KAAK,MAAM,OAAO,oBAAoB;AAAA,EACtC,KAAK,MAAM,OAAO,oBAAoB;AAAA,EACtC,QAAQ,MAAM,OAAO,uBAAuB;AAAA,EAC5C,OAAO,MAAM,OAAO,uBAAuB;AAAA;AAAA,EAE3C,GAAG,MAAM,OAAO,kBAAkB;AAAA,EAClC,KAAK,MAAM,OAAO,oBAAoB;AAAA,EACtC,OAAO,MAAM,OAAO,oBAAoB;AAAA,EACxC,QAAQ,MAAM,OAAO,uBAAuB;AAAA,EAC5C,IAAI,MAAM,OAAO,uBAAuB;AAAA,EACxC,MAAM,MAAM,OAAO,qBAAqB;AAAA,EACxC,IAAI,MAAM,OAAO,mBAAmB;AAAA,EACpC,MAAM,MAAM,OAAO,qBAAqB;AAAA,EACxC,IAAI,MAAM,OAAO,qBAAqB;AAAA,EACtC,OAAO,MAAM,OAAO,sBAAsB;AAAA,EAC1C,QAAQ,MAAM,OAAO,uBAAuB;AAAA,EAC5C,IAAI,MAAM,OAAO,uBAAuB;AAAA,EACxC,OAAO,MAAM,OAAO,sBAAsB;AAAA,EAC1C,eAAe,MAAM,OAAO,4BAA4B;AAAA,EACxD,MAAM,MAAM,OAAO,4BAA4B;AAAA,EAC/C,iBAAiB,MAAM,OAAO,8BAA8B;AAAA;AAAA,EAE5D,QAAQ,MAAM,OAAO,uBAAuB;AAAA,EAC5C,IAAI,MAAM,OAAO,uBAAuB;AAAA,EACxC,MAAM,MAAM,OAAO,qBAAqB;AAAA,EACxC,IAAI,MAAM,OAAO,qBAAqB;AAAA,EACtC,MAAM,MAAM,OAAO,qBAAqB;AAAA,EACxC,KAAK,MAAM,OAAO,oBAAoB;AAAA,EACtC,YAAY,MAAM,OAAO,2BAA2B;AAAA,EACpD,IAAI,MAAM,OAAO,2BAA2B;AAAA,EAC5C,KAAK,MAAM,OAAO,2BAA2B;AAAA,EAC7C,MAAM,MAAM,OAAO,qBAAqB;AAAA,EACxC,KAAK,MAAM,OAAO,oBAAoB;AAAA,EACtC,GAAG,MAAM,OAAO,kBAAkB;AAAA;AAAA,EAElC,MAAM,MAAM,OAAO,qBAAqB;AAAA,EACxC,KAAK,MAAM,OAAO,oBAAoB;AAAA,EACtC,YAAY,MAAM,OAAO,2BAA2B;AAAA,EACpD,QAAQ,MAAM,OAAO,2BAA2B;AAAA,EAChD,UAAU,MAAM,OAAO,qBAAqB;AAAA,EAC5C,MAAM,MAAM,OAAO,qBAAqB;AAAA,EACxC,OAAO,MAAM,OAAO,sBAAsB;AAAA,EAC1C,OAAO,MAAM,OAAO,sBAAsB;AAAA,EAC1C,YAAY,MAAM,OAAO,2BAA2B;AAAA,EACpD,QAAQ,MAAM,OAAO,uBAAuB;AAAA,EAC5C,KAAK,MAAM,OAAO,oBAAoB;AAAA,EACtC,WAAW,MAAM,OAAO,0BAA0B;AAAA,EAClD,IAAI,MAAM,OAAO,0BAA0B;AAAA,EAC3C,KAAK,MAAM,OAAO,oBAAoB;AAAA,EACtC,OAAO,MAAM,OAAO,sBAAsB;AAAA,EAC1C,OAAO,MAAM,OAAO,sBAAsB;AAAA,EAC1C,KAAK,MAAM,OAAO,oBAAoB;AAAA,EACtC,KAAK,MAAM,OAAO,oBAAoB;AAAA;AAAA,EAEtC,MAAM,MAAM,OAAO,qBAAqB;AAAA,EACxC,QAAQ,MAAM,OAAO,uBAAuB;AAAA,EAC5C,QAAQ,MAAM,OAAO,uBAAuB;AAAA,EAC5C,SAAS,MAAM,OAAO,wBAAwB;AAAA,EAC9C,IAAI,MAAM,OAAO,wBAAwB;AAAA,EACzC,SAAS,MAAM,OAAO,wBAAwB;AAAA,EAC9C,KAAK,MAAM,OAAO,wBAAwB;AAAA,EAC1C,OAAO,MAAM,OAAO,sBAAsB;AAAA,EAC1C,QAAQ,MAAM,OAAO,uBAAuB;AAAA,EAC5C,IAAI,MAAM,OAAO,mBAAmB;AAAA,EACpC,KAAK,MAAM,OAAO,oBAAoB;AAAA,EACtC,MAAM,MAAM,OAAO,qBAAqB;AAAA,EACxC,UAAU,MAAM,OAAO,yBAAyB;AAAA,EAChD,QAAQ,MAAM,OAAO,uBAAuB;AAAA,EAC5C,OAAO,MAAM,OAAO,sBAAsB;AAAA;AAAA,EAE1C,OAAO,MAAM,OAAO,sBAAsB;AAAA,EAC1C,KAAK,MAAM,OAAO,sBAAsB;AAAA,EACxC,UAAU,MAAM,OAAO,yBAAyB;AAAA,EAChD,MAAM,MAAM,OAAO,yBAAyB;AAAA;AAAA,EAE5C,MAAM,MAAM,OAAO,qBAAqB;AAAA,EACxC,MAAM,MAAM,OAAO,qBAAqB;AAAA,EACxC,KAAK,MAAM,OAAO,qBAAqB;AACzC;;;ACpIA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,8BAAAA;AAAA,OACK;;;ACfP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;ACmBP,IAAM,sBAAsB;AAG5B,IAAM,kBAAkB;AAGxB,IAAM,kBAAkB;AAGxB,IAAM,mBAAmB;AAIzB,IAAM,2BAA2B;AAGjC,IAAM,8BAA8B;AAGpC,IAAM,mBAAmB;AAGzB,IAAM,oBAAoB;AAG1B,IAAM,sBAAsB;AAG5B,IAAM,uBAAuB;AAG7B,IAAM,oBAAoB;AAO1B,SAAS,wBAAwB,WAA2B;AAC1D,SAAO,KAAK,IAAI,iBAAiB,KAAK,IAAI,iBAAiB,SAAS,CAAC;AACvE;;;ADpCA,IAAM,qBAAqB;AAG3B,IAAM,+BAA+B,oBAAI,QAA8C;AAGvF,IAAM,oCAAoC,oBAAI,QAG5C;AA+CF,SAAS,0BAA0B,eAAyB,WAAyB;AAEnF,QAAM,cAAc,sCAAsC,aAAa;AAGvE,cAAY,cAAc,YAAY;AACtC,QAAM,gBAAgB,yBAAyB,YAAY,aAAa;AACxE,cAAY,eAAe,cAAc;AACzC,cAAY,gBAAgB,cAAc;AAC1C,cAAY,gBAAgB,aAAa;AACzC,cAAY,gBAAgB,YAAY;AACxC,cAAY,YAAY;AAExB,kCAAgC,WAAW;AAC3C,qCAAmC,WAAW;AAChD;AAOA,SAAS,yBAAyB,eAGhC;AAEA,QAAM,aAAa,cAAc,cAA6B,KAAK;AAEnE,MAAI,CAAC,YAAY;AACf,WAAO,EAAE,OAAO,KAAK,QAAQ,IAAI;AAAA,EACnC;AAGA,QAAM,UAAU,WAAW,SAAS;AAEpC,MAAI,WAAW,QAAQ,QAAQ,KAAK,QAAQ,SAAS,GAAG;AACtD,WAAO,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO;AAAA,EACxD;AAGA,QAAM,iBAAiB,WAAW,WAAW,aAAa,OAAO,KAAK,EAAE;AACxE,QAAM,kBAAkB,WAAW,WAAW,aAAa,QAAQ,KAAK,EAAE;AAE1E,MAAI,OAAO,SAAS,cAAc,KAAK,OAAO,SAAS,eAAe,GAAG;AACvE,WAAO;AAAA,MACL,OAAO,iBAAiB,IAAI,iBAAiB;AAAA,MAC7C,QAAQ,kBAAkB,IAAI,kBAAkB;AAAA,IAClD;AAAA,EACF;AAEA,SAAO,EAAE,OAAO,KAAK,QAAQ,IAAI;AACnC;AAOA,SAAS,sCACP,eAC4B;AAC5B,QAAM,sBAAsB,6BAA6B,IAAI,aAAa;AAE1E,MAAI,qBAAqB;AACvB,WAAO;AAAA,EACT;AAEA,QAAM,qBAAqB,iCAAiC,aAAa;AAEzE,+BAA6B,IAAI,eAAe,kBAAkB;AAElE,SAAO;AACT;AAOA,SAAS,iCAAiC,eAAqD;AAE7F,QAAM,gBAAgB,cAAc,cAAc,QAAQ;AAE1D,QAAM,gBAAgB,cAAc,cAAc,KAAK;AAEvD,QAAM,iBAAiB,cAAc,cAAc,GAAG;AAEtD,QAAM,kBAAkB,cAAc,cAAc,KAAK;AAEzD,QAAM,uBAAuB,kCAAkC,eAAe;AAAA,IAC5E,WAAW;AAAA,IACX,MAAM;AAAA,EACR,CAAC;AAED,QAAM,mBAAmB,cAAc,cAAc,MAAM;AAE3D,QAAM,sBAAsB,kCAAkC,eAAe;AAAA,IAC3E,WAAW;AAAA,IACX,MAAM;AAAA,EACR,CAAC;AAED,QAAM,qBAAqB,kCAAkC,eAAe;AAAA,IAC1E,WAAW;AAAA,IACX,MAAM;AAAA,EACR,CAAC;AAED,QAAM,qBAAqB,kCAAkC,eAAe;AAAA,IAC1E,WAAW;AAAA,IACX,WAAW;AAAA,IACX,MAAM;AAAA,EACR,CAAC;AAED,QAAM,kBAAkB,cAAc,cAAc,KAAK;AAEzD,QAAM,gBAAgB,cAAc,cAAc,KAAK;AAGvD,QAAM,cAA0C;AAAA,IAC9C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,eAAe;AAAA,IACf,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,kBAAkB;AAAA,IAClB,kBAAkB;AAAA,IAClB,qBAAqB;AAAA,IACrB,oBAAoB;AAAA,EACtB;AAEA,QAAM,cAAc,cAAc;AAElC,gBAAc,YAAY;AAC1B,gBAAc,aAAa,cAAc,MAAM;AAC/C,gBAAc,aAAa,cAAc,+CAAiB;AAC1D,gBAAc,aAAa,YAAY,IAAI;AAC3C,gBAAc,YAAY;AAC1B,iBAAe,YAAY;AAC3B,iBAAe,cAAc;AAC7B,kBAAgB,YAAY;AAC5B,mBAAiB,YAAY;AAC7B,kBAAgB,YAAY;AAC5B,gBAAc,YAAY;AAE1B,oCAAkC,IAAI,eAAe,WAAW;AAChE,kBAAgB;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,gBAAc,OAAO,gBAAgB,eAAe;AACpD,kBAAgB,OAAO,aAAa;AACpC,gBAAc,OAAO,eAAe,eAAe;AACnD,gBAAc,KAAK,OAAO,aAAa;AAEvC,gBAAc,iBAAiB,SAAS,wCAAwC;AAChF,gBAAc,iBAAiB,SAAS,gCAAgC;AACxE,gBAAc,iBAAiB,WAAW,kCAAkC;AAC5E,kBAAgB,iBAAiB,SAAS,kCAAkC,EAAE,SAAS,MAAM,CAAC;AAC9F,kBAAgB,iBAAiB,eAAe,sCAAsC;AACtF,kBAAgB,iBAAiB,eAAe,sCAAsC;AACtF,kBAAgB,iBAAiB,aAAa,oCAAoC;AAClF,kBAAgB,iBAAiB,iBAAiB,oCAAoC;AACtF,uBAAqB,iBAAiB,SAAS,uCAAuC;AACtF,sBAAoB,iBAAiB,SAAS,sCAAsC;AACpF,qBAAmB,iBAAiB,SAAS,qCAAqC;AAClF,qBAAmB,iBAAiB,SAAS,qCAAqC;AAElF,MAAI,aAAa;AACf,gBAAY,iBAAiB,UAAU,uCAAuC;AAAA,EAChF;AAEA,kCAAgC,aAAa,mBAAmB;AAEhE,SAAO;AACT;AAQA,SAAS,kCACP,eACA,SACmB;AACnB,QAAM,gBAAgB,cAAc,cAAc,QAAQ;AAC1D,gBAAc,OAAO;AACrB,gBAAc,YAAY,QAAQ,YAC9B,GAAG,gCAAgC,IAAI,QAAQ,SAAS,KACxD;AACJ,gBAAc,cAAc,QAAQ;AACpC,gBAAc,aAAa,cAAc,QAAQ,SAAS;AAC1D,SAAO;AACT;AAMA,SAAS,gCAAgC,aAA+C;AACtF,MAAI,OAAO,YAAY,cAAc,cAAc,YAAY;AAC7D,QAAI,CAAC,YAAY,cAAc,MAAM;AACnC,kBAAY,cAAc,UAAU;AAAA,IACtC;AAAA,EACF,OAAO;AACL,gBAAY,cAAc,aAAa,QAAQ,EAAE;AAAA,EACnD;AACA,cAAY,cAAc,MAAM;AAClC;AAMA,SAAS,mCAAmC,aAA+C;AACzF,QAAM,cAAc,YAAY,cAAc,cAAc;AAE5D,MAAI,CAAC,aAAa;AAChB,oCAAgC,aAAa,YAAY,SAAS;AAClE;AAAA,EACF;AAEA,cAAY,WAAW,2CAA2C,GAAG,WAAW;AAClF;AAMA,SAAS,0CACP,aACM;AACN,kCAAgC,aAAa,YAAY,SAAS;AACpE;AAMA,SAAS,yCAAyC,OAAyB;AACzE,MAAI,MAAM,WAAW,MAAM,eAAe;AACxC;AAAA,EACF;AACA,QAAM,gBAAgB,MAAM;AAC5B,QAAM,cAAc,kCAAkC,IAAI,aAAa;AACvE,MAAI,CAAC,aAAa;AAChB;AAAA,EACF;AACA,6BAA2B,WAAW;AACxC;AAMA,SAAS,iCAAiC,OAAoB;AAC5D,QAAM,gBAAgB,MAAM;AAC5B,QAAM,cAAc,kCAAkC,IAAI,aAAa;AACvE,MAAI,CAAC,aAAa;AAChB;AAAA,EACF;AACA,uCAAqC,WAAW;AAClD;AAMA,SAAS,qCAAqC,aAA+C;AAC3F,cAAY,cAAc,gBAAgB;AAC1C,cAAY,gBAAgB,aAAa;AACzC,cAAY,gBAAgB,YAAY;AACxC,cAAY,YAAY;AACxB,cAAY,aAAa;AACzB,cAAY,cAAc,UAAU,OAAO,kCAAkC;AAC7E,cAAY,cAAc,UAAU,OAAO,gCAAgC;AAC7E;AAMA,SAAS,mCAAmC,OAA4B;AACtE,QAAM,cAAc,uCAAuC,KAAK;AAChE,MAAI,CAAC,aAAa;AAChB;AAAA,EACF;AAEA,MAAI,MAAM,QAAQ,OAAO,MAAM,QAAQ,KAAK;AAC1C,UAAM,eAAe;AACrB,oCAAgC,aAAa,YAAY,YAAY,gBAAgB;AACrF;AAAA,EACF;AAEA,MAAI,MAAM,QAAQ,KAAK;AACrB,UAAM,eAAe;AACrB,oCAAgC,aAAa,YAAY,YAAY,gBAAgB;AACrF;AAAA,EACF;AAEA,MAAI,MAAM,QAAQ,KAAK;AACrB,UAAM,eAAe;AACrB,oCAAgC,aAAa,mBAAmB;AAChE;AAAA,EACF;AAEA,MAAI,MAAM,QAAQ,UAAU;AAC1B,UAAM,eAAe;AACrB,+BAA2B,WAAW;AAAA,EACxC;AACF;AAMA,SAAS,iCAAiC,OAAyB;AACjE,MAAI,CAAC,MAAM,WAAW,CAAC,MAAM,SAAS;AACpC;AAAA,EACF;AACA,QAAM,cAAc,uCAAuC,KAAK;AAChE,MAAI,CAAC,aAAa;AAChB;AAAA,EACF;AAEA,QAAM,WAAW,CAAC,MAAM,SAAS;AACjC,QAAM,YAAY,KAAK;AAAA,IACrB,CAAC;AAAA,IACD,KAAK,IAAI,6BAA6B,QAAQ;AAAA,EAChD;AACA,QAAM,eAAe;AACrB,kCAAgC,aAAa,YAAY,YAAY,WAAW;AAAA,IAC9E,GAAG,MAAM;AAAA,IACT,GAAG,MAAM;AAAA,EACX,CAAC;AACH;AAMA,SAAS,uCAAuC,OAA2B;AACzE,MAAI,MAAM,WAAW,GAAG;AACtB;AAAA,EACF;AACA,QAAM,kBAAkB,MAAM;AAC9B,QAAM,cAAc,uCAAuC,KAAK;AAChE,MAAI,CAAC,aAAa;AAChB;AAAA,EACF;AACA,QAAM,wBAAwB,gBAAgB,cAAc,gBAAgB;AAC5E,QAAM,sBAAsB,gBAAgB,eAAe,gBAAgB;AAC3E,MAAI,CAAC,yBAAyB,CAAC,qBAAqB;AAClD;AAAA,EACF;AACA,cAAY,aAAa;AACzB,cAAY,mBAAmB,MAAM;AACrC,cAAY,mBAAmB,MAAM;AACrC,cAAY,sBAAsB,gBAAgB;AAClD,cAAY,qBAAqB,gBAAgB;AACjD,cAAY,cAAc,UAAU,IAAI,kCAAkC;AAC1E,MAAI,OAAO,gBAAgB,sBAAsB,YAAY;AAC3D,oBAAgB,kBAAkB,MAAM,SAAS;AAAA,EACnD;AACA,QAAM,eAAe;AACvB;AAMA,SAAS,uCAAuC,OAA2B;AACzE,QAAM,kBAAkB,MAAM;AAC9B,QAAM,cAAc,uCAAuC,KAAK;AAChE,MAAI,CAAC,eAAe,CAAC,YAAY,YAAY;AAC3C;AAAA,EACF;AACA,QAAM,SAAS,MAAM,UAAU,YAAY;AAC3C,QAAM,SAAS,MAAM,UAAU,YAAY;AAC3C,kBAAgB,aAAa,YAAY,sBAAsB;AAC/D,kBAAgB,YAAY,YAAY,qBAAqB;AAC/D;AAMA,SAAS,qCAAqC,OAA2B;AACvE,QAAM,kBAAkB,MAAM;AAC9B,QAAM,cAAc,uCAAuC,KAAK;AAChE,MAAI,CAAC,eAAe,CAAC,YAAY,YAAY;AAC3C;AAAA,EACF;AACA,cAAY,aAAa;AACzB,cAAY,cAAc,UAAU,OAAO,kCAAkC;AAC7E,MACE,OAAO,gBAAgB,0BAA0B,cACjD,gBAAgB,kBAAkB,MAAM,SAAS,GACjD;AACA,oBAAgB,sBAAsB,MAAM,SAAS;AAAA,EACvD;AACF;AAMA,SAAS,wCAAwC,OAAoB;AACnE,QAAM,cAAc,MAAM;AAC1B,QAAM,cAAc,6BAA6B,IAAI,YAAY,QAAQ;AACzE,MAAI,CAAC,eAAe,CAAC,4BAA4B,WAAW,GAAG;AAC7D;AAAA,EACF;AACA,kCAAgC,aAAa,YAAY,SAAS;AACpE;AAMA,SAAS,wCAAwC,OAAyB;AACxE,QAAM,cAAc,uCAAuC,KAAK;AAChE,MAAI,CAAC,aAAa;AAChB;AAAA,EACF;AACA,kCAAgC,aAAa,YAAY,YAAY,gBAAgB;AACvF;AAMA,SAAS,uCAAuC,OAAyB;AACvE,QAAM,cAAc,uCAAuC,KAAK;AAChE,MAAI,CAAC,aAAa;AAChB;AAAA,EACF;AACA,kCAAgC,aAAa,YAAY,YAAY,gBAAgB;AACvF;AAMA,SAAS,sCAAsC,OAAyB;AACtE,QAAM,cAAc,uCAAuC,KAAK;AAChE,MAAI,CAAC,aAAa;AAChB;AAAA,EACF;AACA,kCAAgC,aAAa,mBAAmB;AAClE;AAMA,SAAS,sCAAsC,OAAyB;AACtE,QAAM,cAAc,uCAAuC,KAAK;AAChE,MAAI,CAAC,aAAa;AAChB;AAAA,EACF;AACA,6BAA2B,WAAW;AACxC;AAOA,SAAS,uCACP,OACwC;AAExC,QAAM,gBAAgB,MAAM;AAC5B,QAAM,gBACJ,eAAe,QAA2B,IAAI,gCAAgC,EAAE,KAAK;AAEvF,MAAI,CAAC,eAAe;AAClB,WAAO;AAAA,EACT;AACA,SAAO,kCAAkC,IAAI,aAAa;AAC5D;AAMA,SAAS,2BAA2B,aAA+C;AACjF,MAAI,OAAO,YAAY,cAAc,UAAU,cAAc,YAAY,cAAc,MAAM;AAC3F,gBAAY,cAAc,MAAM;AAChC;AAAA,EACF;AACA,cAAY,cAAc,gBAAgB,MAAM;AAChD,uCAAqC,WAAW;AAClD;AAOA,SAAS,4BAA4B,aAAkD;AACrF,SAAO,YAAY,cAAc,SAAS;AAC5C;AAQA,SAAS,gCACP,aACA,UACA,YACM;AAEN,QAAM,iBAAiB,wBAAwB,QAAQ;AAEvD,QAAM,aAAa,4BAA4B,WAAW,IACtD,uCAAuC,aAAa,UAAU,IAC9D;AAEJ,cAAY,YAAY;AACxB,cAAY,cAAc,UAAU;AAAA,IAClC;AAAA,IACA,iBAAiB;AAAA,EACnB;AACA,cAAY,iBAAiB,cAAc,GAAG,KAAK,MAAM,iBAAiB,GAAG,CAAC;AAC9E,cAAY,qBAAqB,WAAW,kBAAkB;AAC9D,cAAY,oBAAoB,WAAW,kBAAkB;AAC7D,cAAY,mBAAmB,WAAW,mBAAmB;AAC7D,wCAAsC,WAAW;AAEjD,MAAI,YAAY;AACd,yCAAqC,aAAa,UAAU;AAAA,EAC9D;AACF;AAQA,SAAS,uCACP,aACA,YACsC;AAEtC,QAAM,gBAAgB,YAAY,cAAc,sBAAsB;AACtE,MAAI,cAAc,SAAS,KAAK,cAAc,UAAU,GAAG;AACzD,WAAO;AAAA,EACT;AACA,QAAM,eAAe,YAAY,gBAAgB,sBAAsB;AACvE,QAAM,gBAAgB,YAAY,KAAK,aAAa,OAAO,aAAa,QAAQ;AAChF,QAAM,gBAAgB,YAAY,KAAK,aAAa,MAAM,aAAa,SAAS;AAChF,QAAM,eAAe,gBAAgB,cAAc,QAAQ,cAAc;AACzE,QAAM,eAAe,gBAAgB,cAAc,OAAO,cAAc;AACxE,SAAO,EAAE,eAAe,eAAe,aAAa,YAAY;AAClE;AAOA,SAAS,qCACP,aACA,YACM;AACN,cAAY,gBAAgB,aAAa;AACzC,cAAY,gBAAgB,YAAY;AACxC,QAAM,iBAAiB,YAAY,cAAc,sBAAsB;AACvE,MAAI,eAAe,SAAS,KAAK,eAAe,UAAU,GAAG;AAC3D;AAAA,EACF;AACA,QAAM,yBACJ,WAAW,gBAAgB,WAAW,cAAc,eAAe;AACrE,QAAM,wBACJ,WAAW,gBAAgB,WAAW,cAAc,eAAe;AACrE,cAAY,gBAAgB,aAAa,eAAe,OAAO;AAC/D,cAAY,gBAAgB,YAAY,eAAe,MAAM;AAC/D;AAMA,SAAS,sCAAsC,aAA+C;AAC5F,QAAM,gBAAgB,KAAK;AAAA,IACzB,YAAY,gBAAgB,cAAc;AAAA,IAC1C;AAAA,EACF;AACA,QAAM,iBAAiB,KAAK;AAAA,IAC1B,YAAY,gBAAgB,eAAe;AAAA,IAC3C;AAAA,EACF;AACA,QAAM,eAAe,KAAK,IAAI,YAAY,cAAc,CAAC;AACzD,QAAM,gBAAgB,KAAK,IAAI,YAAY,eAAe,CAAC;AAE3D,QAAM,WAAW,KAAK,IAAI,GAAG,gBAAgB,cAAc,iBAAiB,aAAa;AACzF,QAAM,eAAe,KAAK,IAAI,GAAG,KAAK,MAAM,eAAe,WAAW,YAAY,SAAS,CAAC;AAC5F,QAAM,gBAAgB,KAAK,IAAI,GAAG,KAAK,MAAM,gBAAgB,WAAW,YAAY,SAAS,CAAC;AAE9F,cAAY,cAAc,MAAM,QAAQ,GAAG,YAAY;AACvD,cAAY,cAAc,MAAM,SAAS,GAAG,aAAa;AAGzD,QAAM,kBAAkB,YAAY,cAAc,cAA6B,KAAK;AACpF,MAAI,iBAAiB;AACnB,oBAAgB,gBAAgB,OAAO;AACvC,oBAAgB,gBAAgB,QAAQ;AACxC,oBAAgB,MAAM,QAAQ;AAC9B,oBAAgB,MAAM,SAAS;AAC/B,oBAAgB,MAAM,WAAW;AACjC,oBAAgB,MAAM,UAAU;AAAA,EAClC;AACF;;;AD5rBA,IAAM,sBAAsB;AAG5B,IAAM,4BAA4B;AAGlC,IAAM,kCAAkC;AAGxC,IAAM,0BAA0B;AAGhC,IAAM,gCAAgC;AAGtC,IAAM,2BAA2B;AAGjC,IAAI,yBAAyB;AAG7B,IAAM,uCAAuC;AAG7C,IAAM,wCAAwC;AAG9C,IAAM,iCAAiC;AAMvC,IAAI;AAyBJ,SAAS,qBAAqB,aAA+B;AAE3D,QAAM,sBAAsB,YAAY;AAAA,IACtC,uBAAuB,mBAAmB;AAAA,EAC5C;AAEA,sBAAoB,QAAQ,CAAC,gBAAgB;AAE3C,UAAM,aAAa,YAAY;AAC/B,QAAI,CAAC,YAAY;AACf;AAAA,IACF;AAGA,QAAI,WAAW,QAAQ,4BAA4B,MAAM,QAAQ;AAC/D;AAAA,IACF;AAEA,yBAAqB,YAAY,WAAW;AAAA,EAC9C,CAAC;AAED,+BAA6B,WAAW;AAC1C;AAOA,SAAS,6BAA6B,aAA+B;AAEnE,QAAM,iBAAiB,YAAY;AAAA,IACjC,IAAI,yBAAyB,IAAI,iCAAiC;AAAA,EACpE;AAEA,iBAAe,QAAQ,CAAC,kBAAkB;AAExC,QAAI,CAAC,cAAc,aAAa;AAC9B;AAAA,IACF;AACA,QAAI,cAAc,QAAQ,+BAA+B,MAAM,QAAQ;AACrE;AAAA,IACF;AAGA,UAAM,gBAAgB,cAAc;AAAA,MAClC,IAAI,yBAAyB;AAAA,IAC/B;AACA,UAAM,gBAAgB,cAAc,QAAQ,uBAAuB,KAAK;AAExE,QAAI,CAAC,iBAAiB,cAAc,WAAW,GAAG;AAChD;AAAA,IACF;AAEA,kBAAc,QAAQ,+BAA+B,IAAI;AACzD,SAAK,wBAAwB,eAAe,eAAe,aAAa;AAAA,EAC1E,CAAC;AACH;AAOA,SAAS,qBAAqB,YAA4B,aAAgC;AAExF,MAAI,WAAW,QAAQ,yBAAyB,MAAM,QAAQ;AAC5D;AAAA,EACF;AACA,aAAW,QAAQ,yBAAyB,IAAI;AAGhD,QAAM,gBAAgB,WAAW;AAEjC,QAAM,iBAAiB,YAAY,eAAe,IAAI,QAAQ,SAAS,EAAE;AAGzE,QAAM,gBAAgB,cAAc,cAAc,QAAQ;AAC1D,gBAAc,YAAY,GAAG,yBAAyB,IAAI,iCAAiC;AAG3F,QAAM,gBAAgB,cAAc,cAAc,KAAK;AACvD,gBAAc,YAAY;AAE1B,QAAM,eAAe,cAAc,cAAc,MAAM;AACvD,eAAa,YAAY;AACzB,eAAa,cAAc;AAC3B,gBAAc,OAAO,YAAY;AAGjC,QAAM,cAAc,cAAc,cAAc,KAAK;AACrD,cAAY,YAAY;AAGxB,QAAM,gBAAgB,cAAc,cAAc,KAAK;AACvD,gBAAc,YAAY;AAC1B,gBAAc,aAAa,QAAQ,KAAK;AACxC,gBAAc,aAAa,cAAc,kBAAkB;AAG3D,QAAM,0BAA0B,cAAc,cAAc,QAAQ;AACpE,0BAAwB,OAAO;AAC/B,0BAAwB,YAAY;AACpC,0BAAwB,aAAa,cAAc,oCAAoC;AAEvF,0BAAwB,WAAW;AACnC,0BAAwB,YAAY;AACpC,0BAAwB,iBAAiB,SAAS,kCAAkC;AAEpF,cAAY,OAAO,eAAe,uBAAuB;AACzD,gBAAc,OAAO,eAAe,WAAW;AAG/C,QAAM,aAAa,YAAY,aAAaC,2BAA0B;AACtE,MAAI,eAAe,MAAM;AACvB,kBAAc,aAAaA,6BAA4B,UAAU;AAAA,EACnE;AAKA,gBAAc,QAAQ,uBAAuB,IAAI;AAEjD,aAAW,YAAY,aAAa;AACtC;AAMA,eAAe,cAA+C;AAC5D,MAAI,OAAO,WAAW,eAAe,OAAO,aAAa,aAAa;AACpE,WAAO;AAAA,EACT;AAEA,MAAI,CAAC,sBAAsB;AACzB,4BAAwB,YAAY;AAElC,YAAM,gBAAiB,MAAM,OAAO,SAAS;AAC7C,YAAM,aAAa,cAAc;AAIjC,iBAAW,WAAW;AAAA,QACpB,aAAa;AAAA,QACb,eAAe;AAAA,QACf,OAAO;AAAA,QACP,WAAW,EAAE,aAAa,MAAM;AAAA,QAChC,UAAU,EAAE,aAAa,MAAM;AAAA,QAC/B,OAAO,EAAE,aAAa,MAAM;AAAA,QAC5B,OAAO,EAAE,aAAa,MAAM;AAAA,QAC5B,OAAO,EAAE,aAAa,MAAM;AAAA,QAC5B,IAAI,EAAE,aAAa,MAAM;AAAA,QACzB,SAAS,EAAE,aAAa,MAAM;AAAA,QAC9B,KAAK,EAAE,aAAa,MAAM;AAAA,QAC1B,aAAa,EAAE,aAAa,MAAM;AAAA,QAClC,IAAI,EAAE,aAAa,MAAM;AAAA,QACzB,SAAS,EAAE,aAAa,MAAM;AAAA,QAC9B,UAAU,EAAE,aAAa,MAAM;AAAA,QAC/B,UAAU,EAAE,aAAa,MAAM;AAAA,QAC/B,eAAe,EAAE,aAAa,MAAM;AAAA,QACpC,SAAS,EAAE,aAAa,MAAM;AAAA,QAC9B,QAAQ,EAAE,aAAa,MAAM;AAAA,QAC7B,OAAO,EAAE,aAAa,MAAM;AAAA,MAC9B,CAAC;AACD,aAAO;AAAA,IACT,GAAG,EAAE,MAAM,CAAC,cAAuB;AAEjC,6BAAuB;AACvB,YAAM;AAAA,IACR,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAQA,eAAe,wBACb,eACA,eACA,eACe;AACf,MAAI;AACF,UAAM,aAAa,MAAM,YAAY;AACrC,QAAI,CAAC,YAAY;AAEf;AAAA,IACF;AAEA,8BAA0B;AAC1B,UAAM,WAAW,GAAG,wBAAwB,GAAG,sBAAsB;AACrE,UAAM,EAAE,KAAK,cAAc,IAAI,MAAM,WAAW,OAAO,UAAU,aAAa;AAE9E,kBAAc,YAAY;AAC1B,oBAAgB,aAAa;AAC7B,kBAAc,UAAU,OAAO,iCAAiC;AAChE,kBAAc,UAAU,IAAI,gCAAgC;AAG5D,kBAAc,QAAQ,8BAA8B,IAAI;AACxD,kBAAc,QAAQ,qCAAqC,IAAI;AAG/D,UAAM,0BAA0B,cAAc;AAAA,MAC5C,IAAI,oCAAoC;AAAA,IAC1C;AACA,QAAI,yBAAyB;AAC3B,8BAAwB,WAAW;AAAA,IACrC;AAAA,EACF,SAAS,aAAsB;AAC7B,wBAAoB,eAAe,eAAe,eAAe,WAAW;AAAA,EAC9E;AACF;AASA,SAAS,oBACP,eACA,eACA,eACA,aACM;AACN,QAAM,gBAAgB,cAAc;AACpC,gBAAc,UAAU,OAAO,iCAAiC;AAChE,gBAAc,UAAU,IAAI,gCAAgC;AAG5D,QAAM,0BAA0B,cAAc;AAAA,IAC5C,IAAI,oCAAoC;AAAA,EAC1C;AACA,2BAAyB,OAAO;AAGhC,gBAAc,gBAAgB;AAG9B,4BAA0B,aAAa;AAEvC,QAAM,kBAAkB,cAAc,cAAc,KAAK;AACzD,kBAAgB,YAAY;AAE5B,QAAM,cAAc,cAAc,cAAc,MAAM;AACtD,cAAY,YAAY;AACxB,cAAY,aAAa,eAAe,MAAM;AAC9C,kBAAgB,OAAO,WAAW;AAElC,QAAM,cAAc,cAAc,cAAc,GAAG;AACnD,cAAY,YAAY;AACxB,cAAY,cAAc;AAC1B,kBAAgB,OAAO,WAAW;AAGlC,QAAM,eAAe,oBAAoB,WAAW;AACpD,MAAI,cAAc;AAChB,UAAM,iBAAiB,cAAc,cAAc,GAAG;AACtD,mBAAe,YAAY;AAC3B,mBAAe,cAAc;AAC7B,oBAAgB,OAAO,cAAc;AAAA,EACvC;AAGA,MAAI,cAAc,SAAS,GAAG;AAC5B,UAAM,gBAAgB,cAAc,cAAc,KAAK;AACvD,kBAAc,YAAY;AAC1B,kBAAc,cAAc;AAC5B,oBAAgB,OAAO,aAAa;AAAA,EACtC;AAEA,gBAAc,YAAY,eAAe;AAC3C;AAOA,SAAS,oBAAoB,aAA8B;AACzD,MAAI,uBAAuB,OAAO;AAChC,WAAO,YAAY;AAAA,EACrB;AACA,MAAI,OAAO,gBAAgB,UAAU;AACnC,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAOA,SAAS,0BAA0B,eAA+B;AAEhE,QAAM,cAAc,cAAc;AAAA,IAChC,gBAAgB,wBAAwB;AAAA,EAC1C;AACA,cAAY,QAAQ,CAAC,eAAe;AAClC,eAAW,OAAO;AAAA,EACpB,CAAC;AACH;AAKA,IAAM,8BACJ;AASF,SAAS,mCAAmC,OAAyB;AAEnE,QAAM,gBAAgB,MAAM;AAE5B,QAAM,gBAAgB,cAAc,QAAqB,IAAI,yBAAyB,EAAE;AAExF,MAAI,CAAC,eAAe;AAClB;AAAA,EACF;AAEA,MAAI,cAAc,QAAQ,qCAAqC,MAAM,QAAQ;AAC3E;AAAA,EACF;AAGA,QAAM,YAAY,cAAc,QAAQ,8BAA8B,KAAK;AAE3E,MAAI,UAAU,WAAW,GAAG;AAC1B;AAAA,EACF;AAEA,QAAM,eAAe;AACrB,QAAM,gBAAgB;AACtB,4BAA0B,cAAc,eAAe,SAAS;AAClE;;;AFhaA,IAAM,yBAAyB;AAG/B,IAAI;AAGJ,IAAM,kCAAkC,oBAAI,IAA2B;AAKvE,IAAM,+BACJ;AAGF,IAAM,2BAA2B;AAOjC,eAAe,qBAA+C;AAC5D,MAAI,CAAC,wBAAwB;AAC3B,6BAAyB,sBAAsB;AAAA,MAC7C,QAAQ,CAAC,gBAAgB;AAAA;AAAA;AAAA,MAGzB,OAAO;AAAA;AAAA,MAEP,QAAQ,sBAAsB,OAAO,YAAY,CAAC;AAAA,IACpD,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAOA,eAAe,4BAA4B,MAA+B;AACxE,MAAI,CAAC,6BAA6B,KAAK,IAAI,GAAG;AAC5C,WAAO;AAAA,EACT;AAEA,+BAA6B,YAAY;AAGzC,QAAM,cAAc,MAAM,mBAAmB;AAE7C,MAAI,aAAa;AAEjB,MAAI,YAAY;AAEhB,aAAW,SAAS,KAAK,SAAS,4BAA4B,GAAG;AAC/D,UAAM,CAAC,SAAS,eAAe,aAAa,gBAAgB,WAAW,IAAI;AAC3E,UAAM,aAAa,MAAM,SAAS;AAElC,kBAAc,KAAK,MAAM,WAAW,UAAU;AAC9C,kBAAc,MAAM,yBAAyB,aAAa;AAAA,MACxD;AAAA,MACA;AAAA,MACA,aAAa;AAAA,MACb;AAAA,MACA;AAAA,IACF,CAAC;AACD,gBAAY,aAAa,QAAQ;AAAA,EACnC;AAEA,gBAAc,KAAK,MAAM,SAAS;AAClC,SAAO;AACT;AAwBA,eAAe,yBACb,aACA,OACiB;AAEjB,QAAM,EAAE,aAAa,aAAa,aAAa,eAAe,eAAe,IAAI;AAEjF,QAAM,qBAAqB,YAAY,YAAY;AAInD,MAAI,uBAAuB,qBAAqB;AAC9C,WAAO;AAAA,EACT;AAEA,QAAM,mBAAmB,MAAM,0BAA0B,aAAa,kBAAkB;AAGxF,QAAM,WAAW,mBAAmB,WAAW;AAE/C,MAAI;AAEF,UAAM,kBAAkB,YAAY,WAAW,UAAU;AAAA,MACvD,MAAM;AAAA,MACN,OAAO;AAAA,IACT,CAAC;AAED,UAAM,aAAa,gBAAgB,MAAM,wBAAwB;AACjE,UAAM,YAAY,aAAa,CAAC,KAAK;AAErC,WAAO,OAAO,aAAa,0BAA0B,WAAW,IAAI,cAAc,IAAI,SAAS;AAAA,EACjG,QAAQ;AAEN,WAAO;AAAA,EACT;AACF;AAQA,eAAe,0BACb,aACA,oBACiB;AACjB,MAAI,mBAAmB,WAAW,GAAG;AACnC,WAAO;AAAA,EACT;AAGA,MAAI,YAAY,mBAAmB,EAAE,SAAS,kBAAkB,GAAG;AACjE,WAAO;AAAA,EACT;AAGA,QAAM,aAAa,4BAA4B,kBAAkB;AACjE,MAAI,CAAC,YAAY;AACf,WAAO;AAAA,EACT;AAGA,MAAI,UAAU,gCAAgC,IAAI,kBAAkB;AACpE,MAAI,CAAC,SAAS;AACZ,eAAW,YAAY;AACrB,YAAM,gBAAgB,MAAM,WAAW;AACvC,YAAM,YAAY,aAAa,cAAc,OAAO;AAAA,IACtD,GAAG;AACH,oCAAgC,IAAI,oBAAoB,OAAO;AAAA,EACjE;AAEA,MAAI;AACF,UAAM;AACN,WAAO;AAAA,EACT,QAAQ;AAGN,oCAAgC,OAAO,kBAAkB;AACzD,WAAO;AAAA,EACT;AACF;AAGA,IAAM,sBAA8C;AAAA,EAClD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,KAAK;AACP;AAGA,IAAM,sBAAsB;AAW5B,SAAS,mBAAmB,aAA6B;AACvD,SAAO,YAAY;AAAA,IACjB;AAAA,IACA,CAAC,SAAS,aAAsB,cAAuB,qBAA8B;AACnF,UAAI,aAAa;AACf,eAAO,oBAAoB,WAAW,KAAK;AAAA,MAC7C;AACA,UAAI,cAAc;AAChB,eAAO,kBAAkB,SAAS,cAAc,EAAE,GAAG,OAAO;AAAA,MAC9D;AACA,UAAI,kBAAkB;AACpB,eAAO,kBAAkB,SAAS,kBAAkB,EAAE,GAAG,OAAO;AAAA,MAClE;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAQA,SAAS,kBAAkB,WAAmB,UAA0B;AACtE,MAAI,CAAC,OAAO,SAAS,SAAS,KAAK,YAAY,KAAK,YAAY,SAAU;AACxE,WAAO;AAAA,EACT;AACA,MAAI;AACF,WAAO,OAAO,cAAc,SAAS;AAAA,EACvC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AKrPA,SAAS,8BAAAC,mCAAkC;AAY3C,IAAM,4BAA4BA,4BAA2B;AAAA,EAC3D;AAAA,EACA,CAAC,QAAgB,WAAmB,OAAO,YAAY;AACzD;AAQA,SAAS,qBAAqB,MAAyD;AACrF,SAAO,KAAK,SAAS,gBAAgB,KAAK,SAAS,UAAU,KAAK,SAAS;AAC7E;AAOA,SAAS,mBAAyC;AAChD,SAAO,CAAC,SAAe;AAErB,UAAM,aAAa,KAAK;AAExB,eAAW,QAAQ,CAAC,cAAc;AAChC,yBAAmB,SAAS;AAG5B,qCAA+B,SAAS;AAAA,IAC1C,CAAC;AAAA,EACH;AACF;AAMA,SAAS,mBAAmB,MAAkB;AAE5C,QAAM,YAAY,KAAK,UAAU,MAAM;AAIvC,MAAI,OAAO,cAAc,UAAU;AACjC;AAAA,EACF;AAGA,QAAM,WAAiB,KAAK,QAAQ,CAAC;AAErC,QAAM,cAA0B,SAAS,eAAe,CAAC;AAEzD,cAAY,yBAAyB,IAAI;AACzC,WAAS,cAAc;AACvB,OAAK,OAAO;AACd;AAOA,SAAS,+BAA+B,MAAyB;AAE/D,MAAI,CAAC,qBAAqB,IAAI,GAAG;AAC/B;AAAA,EACF;AAGA,QAAM,aAAa,KAAK;AAExB,aAAW,QAAQ,CAAC,cAAc;AAChC,uBAAmB,SAAS;AAC5B,mCAA+B,SAAS;AAAA,EAC1C,CAAC;AACH;;;ACxFA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,MAAM,aAAa;;;ACA5B,SAAS,YAAY,qBAAqB;AAgN1C,SAAS,gBAAgB,MAAoB;AAC3C,SAAO,cAAc,MAAM,EAAE,aAAa,MAAM,CAAC;AACnD;AAOA,SAAS,YAAY,MAA4C;AAC/D,SAAO,KAAK,SAAS,WAAW,KAAK,SAAS;AAChD;;;ACzOA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAmDP,IAAM,yBACJ;AAGF,IAAM,8BAA8B;AAGpC,IAAM,6BAA6B,oBAAI,QAA4C;AAGnF,IAAM,kCAAkC,oBAAI,QAAqD;AAMjG,SAAS,wBAAwB,cAAsC;AAErE,QAAM,cAAc,aAAa,QAAQ,SAAS;AAElD,MAAI,aAAa;AACf;AAAA,EACF;AAEA,eAAa,aAAa,QAAQ,QAAQ;AAC1C,eAAa,aAAa,YAAY,GAAG;AACzC,eAAa,aAAa,cAAc,mCAAmC,YAAY,CAAC;AACxF,eAAa,iBAAiB,SAAS,kCAAkC;AACzE,eAAa,iBAAiB,WAAW,oCAAoC;AAC/E;AAMA,SAAS,mCAAmC,OAAyB;AAEnE,QAAM,eAAe,MAAM;AAE3B,MAAI,CAAC,2BAA2B,YAAY,GAAG;AAC7C;AAAA,EACF;AAEA,QAAM,eAAe;AACrB,QAAM,gBAAgB;AACtB,0BAAwB,YAAY;AACtC;AAMA,SAAS,qCAAqC,OAA4B;AAExE,QAAM,YAAY,MAAM,QAAQ,WAAW,MAAM,QAAQ;AAEzD,QAAM,eAAe,MAAM;AAE3B,MAAI,CAAC,aAAa,CAAC,2BAA2B,YAAY,GAAG;AAC3D;AAAA,EACF;AAEA,QAAM,eAAe;AACrB,0BAAwB,YAAY;AACtC;AAOA,SAAS,2BAA2B,cAAyC;AAE3E,QAAM,cAAc,uBAAuB,YAAY;AAEvD,SACE,YAAY,SAAS,KACrB,aAAa,YACb,aAAa,eAAe,KAC5B,aAAa,gBAAgB;AAEjC;AAMA,SAAS,wBAAwB,oBAA4C;AAE3E,QAAM,cAAc,uBAAuB,kBAAkB;AAE7D,MAAI,YAAY,WAAW,GAAG;AAC5B;AAAA,EACF;AAGA,QAAM,gBAAgB,mBAAmB;AAEzC,QAAM,cAAc,oCAAoC,aAAa;AAErE,QAAM,aAAa,iCAAiC,oBAAoB,WAAW;AAEnF,cAAY,qBAAqB;AACjC,cAAY,eAAe,mBAAmB;AAC9C,cAAY,gBAAgB,mBAAmB;AAC/C,cAAY,aAAa,MAAM;AAC/B,cAAY,aAAa,MAAM,mBAAmB;AAClD,cAAY,eAAe,cAAc;AACzC,cAAY,cAAc;AAAA,IACxB;AAAA,IACA,mCAAmC,kBAAkB;AAAA,EACvD;AACA,cAAY,gBAAgB,aAAa;AACzC,cAAY,gBAAgB,YAAY;AACxC,cAAY,YAAY;AAExB,gCAA8B,WAAW;AACzC,mCAAiC,WAAW;AAC9C;AAOA,SAAS,oCAAoC,eAAmD;AAE9F,QAAM,sBAAsB,2BAA2B,IAAI,aAAa;AAExE,MAAI,qBAAqB;AACvB,WAAO;AAAA,EACT;AAGA,QAAM,qBAAqB,+BAA+B,aAAa;AAEvE,6BAA2B,IAAI,eAAe,kBAAkB;AAEhE,SAAO;AACT;AAOA,SAAS,+BAA+B,eAAmD;AAEzF,QAAM,gBAAgB,cAAc,cAAc,QAAQ;AAE1D,QAAM,gBAAgB,cAAc,cAAc,KAAK;AAEvD,QAAM,sBAAsB,cAAc,cAAc,KAAK;AAE7D,QAAM,iBAAiB,cAAc,cAAc,GAAG;AAEtD,QAAM,cAAc,cAAc,cAAc,GAAG;AAEnD,QAAM,kBAAkB,cAAc,cAAc,KAAK;AAEzD,QAAM,uBAAuB,gCAAgC,eAAe;AAAA,IAC1E,WAAW;AAAA,IACX,MAAM;AAAA,EACR,CAAC;AAED,QAAM,mBAAmB,cAAc,cAAc,MAAM;AAE3D,QAAM,sBAAsB,gCAAgC,eAAe;AAAA,IACzE,WAAW;AAAA,IACX,MAAM;AAAA,EACR,CAAC;AAED,QAAM,qBAAqB,gCAAgC,eAAe;AAAA,IACxE,WAAW;AAAA,IACX,MAAM;AAAA,EACR,CAAC;AAED,QAAM,qBAAqB,gCAAgC,eAAe;AAAA,IACxE,WAAW;AAAA,IACX,WAAW;AAAA,IACX,MAAM;AAAA,EACR,CAAC;AAED,QAAM,kBAAkB,cAAc,cAAc,KAAK;AAEzD,QAAM,eAAe,cAAc,cAAc,KAAK;AAEtD,QAAM,cAAwC;AAAA,IAC5C;AAAA,IACA;AAAA,IACA;AAAA,IACA,kBAAkB;AAAA,IAClB,kBAAkB;AAAA,IAClB,qBAAqB;AAAA,IACrB,oBAAoB;AAAA,IACpB;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,cAAc;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX;AAAA,EACF;AAEA,QAAM,cAAc,cAAc;AAElC,gBAAc,YAAY;AAC1B,gBAAc,aAAa,cAAc,MAAM;AAC/C,gBAAc,aAAa,YAAY,IAAI;AAC3C,gBAAc,YAAY;AAC1B,sBAAoB,YAAY;AAChC,iBAAe,YAAY;AAC3B,cAAY,YAAY;AACxB,cAAY,cAAc;AAC1B,kBAAgB,YAAY;AAC5B,mBAAiB,YAAY;AAC7B,kBAAgB,YAAY;AAC5B,eAAa,YAAY;AACzB,eAAa,WAAW;AAExB,kCAAgC,IAAI,eAAe,WAAW;AAC9D,kBAAgB;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,sBAAoB,OAAO,gBAAgB,WAAW;AACtD,gBAAc,OAAO,qBAAqB,eAAe;AACzD,kBAAgB,OAAO,YAAY;AACnC,gBAAc,OAAO,eAAe,eAAe;AACnD,gBAAc,KAAK,OAAO,aAAa;AAEvC,gBAAc,iBAAiB,SAAS,sCAAsC;AAC9E,gBAAc,iBAAiB,SAAS,8BAA8B;AACtE,gBAAc,iBAAiB,WAAW,gCAAgC;AAC1E,kBAAgB,iBAAiB,SAAS,gCAAgC,EAAE,SAAS,MAAM,CAAC;AAC5F,kBAAgB,iBAAiB,eAAe,oCAAoC;AACpF,kBAAgB,iBAAiB,eAAe,oCAAoC;AACpF,kBAAgB,iBAAiB,aAAa,kCAAkC;AAChF,kBAAgB,iBAAiB,iBAAiB,kCAAkC;AACpF,eAAa,iBAAiB,QAAQ,kCAAkC;AACxE,eAAa,iBAAiB,aAAa,uCAAuC;AAClF,uBAAqB,iBAAiB,SAAS,qCAAqC;AACpF,sBAAoB,iBAAiB,SAAS,oCAAoC;AAClF,qBAAmB,iBAAiB,SAAS,mCAAmC;AAChF,qBAAmB,iBAAiB,SAAS,mCAAmC;AAEhF,MAAI,aAAa;AACf,gBAAY,iBAAiB,UAAU,qCAAqC;AAAA,EAC9E;AAEA,gCAA8B,aAAa,mBAAmB;AAE9D,SAAO;AACT;AAQA,SAAS,gCACP,eACA,SACmB;AAEnB,QAAM,gBAAgB,cAAc,cAAc,QAAQ;AAE1D,gBAAc,OAAO;AACrB,gBAAc,YAAY,QAAQ,YAC9B,GAAG,8BAA8B,IAAI,QAAQ,SAAS,KACtD;AACJ,gBAAc,cAAc,QAAQ;AACpC,gBAAc,aAAa,cAAc,QAAQ,SAAS;AAE1D,SAAO;AACT;AAMA,SAAS,8BAA8B,aAA6C;AAClF,MAAI,OAAO,YAAY,cAAc,cAAc,YAAY;AAC7D,QAAI,CAAC,YAAY,cAAc,MAAM;AACnC,kBAAY,cAAc,UAAU;AAAA,IACtC;AAAA,EACF,OAAO;AACL,gBAAY,cAAc,aAAa,QAAQ,EAAE;AAAA,EACnD;AAEA,cAAY,cAAc,MAAM;AAClC;AAMA,SAAS,iCAAiC,aAA6C;AAErF,QAAM,cAAc,YAAY,cAAc,cAAc;AAE5D,MAAI,CAAC,aAAa;AAChB,kCAA8B,aAAa,YAAY,SAAS;AAChE;AAAA,EACF;AAEA,cAAY,WAAW,yCAAyC,GAAG,WAAW;AAChF;AAMA,SAAS,wCAAwC,aAA6C;AAC5F,gCAA8B,aAAa,YAAY,SAAS;AAClE;AAMA,SAAS,uCAAuC,OAAyB;AACvE,MAAI,MAAM,WAAW,MAAM,eAAe;AACxC;AAAA,EACF;AAGA,QAAM,gBAAgB,MAAM;AAE5B,QAAM,cAAc,gCAAgC,IAAI,aAAa;AAErE,MAAI,CAAC,aAAa;AAChB;AAAA,EACF;AAEA,2BAAyB,WAAW;AACtC;AAMA,SAAS,+BAA+B,OAAoB;AAE1D,QAAM,gBAAgB,MAAM;AAE5B,QAAM,cAAc,gCAAgC,IAAI,aAAa;AAErE,MAAI,CAAC,aAAa;AAChB;AAAA,EACF;AAEA,qCAAmC,WAAW;AAChD;AAMA,SAAS,iCAAiC,OAA4B;AAEpE,QAAM,cAAc,qCAAqC,KAAK;AAE9D,MAAI,CAAC,aAAa;AAChB;AAAA,EACF;AAEA,MAAI,MAAM,QAAQ,OAAO,MAAM,QAAQ,KAAK;AAC1C,UAAM,eAAe;AACrB,kCAA8B,aAAa,YAAY,YAAY,gBAAgB;AACnF;AAAA,EACF;AAEA,MAAI,MAAM,QAAQ,KAAK;AACrB,UAAM,eAAe;AACrB,kCAA8B,aAAa,YAAY,YAAY,gBAAgB;AACnF;AAAA,EACF;AAEA,MAAI,MAAM,QAAQ,KAAK;AACrB,UAAM,eAAe;AACrB,kCAA8B,aAAa,mBAAmB;AAC9D;AAAA,EACF;AAEA,MAAI,MAAM,QAAQ,UAAU;AAC1B,UAAM,eAAe;AACrB,6BAAyB,WAAW;AAAA,EACtC;AACF;AAMA,SAAS,+BAA+B,OAAyB;AAC/D,MAAI,CAAC,MAAM,WAAW,CAAC,MAAM,SAAS;AACpC;AAAA,EACF;AAGA,QAAM,cAAc,qCAAqC,KAAK;AAE9D,MAAI,CAAC,aAAa;AAChB;AAAA,EACF;AAGA,QAAM,WAAW,CAAC,MAAM,SAAS;AACjC,QAAM,YAAY,KAAK;AAAA,IACrB,CAAC;AAAA,IACD,KAAK,IAAI,6BAA6B,QAAQ;AAAA,EAChD;AAEA,QAAM,eAAe;AACrB,gCAA8B,aAAa,YAAY,YAAY,WAAW;AAAA,IAC5E,GAAG,MAAM;AAAA,IACT,GAAG,MAAM;AAAA,EACX,CAAC;AACH;AAMA,SAAS,qCAAqC,OAA2B;AAEvE,MAAI,MAAM,WAAW,GAAG;AACtB;AAAA,EACF;AAGA,QAAM,kBAAkB,MAAM;AAE9B,QAAM,cAAc,qCAAqC,KAAK;AAE9D,MAAI,CAAC,aAAa;AAChB;AAAA,EACF;AAGA,QAAM,wBAAwB,gBAAgB,cAAc,gBAAgB;AAC5E,QAAM,sBAAsB,gBAAgB,eAAe,gBAAgB;AAE3E,MAAI,CAAC,yBAAyB,CAAC,qBAAqB;AAClD;AAAA,EACF;AAEA,cAAY,aAAa;AACzB,cAAY,mBAAmB,MAAM;AACrC,cAAY,mBAAmB,MAAM;AACrC,cAAY,sBAAsB,gBAAgB;AAClD,cAAY,qBAAqB,gBAAgB;AACjD,cAAY,cAAc,UAAU,IAAI,gCAAgC;AAExE,MAAI,OAAO,gBAAgB,sBAAsB,YAAY;AAC3D,oBAAgB,kBAAkB,MAAM,SAAS;AAAA,EACnD;AAEA,QAAM,eAAe;AACvB;AAMA,SAAS,qCAAqC,OAA2B;AAEvE,QAAM,kBAAkB,MAAM;AAE9B,QAAM,cAAc,qCAAqC,KAAK;AAE9D,MAAI,CAAC,eAAe,CAAC,YAAY,YAAY;AAC3C;AAAA,EACF;AAGA,QAAM,SAAS,MAAM,UAAU,YAAY;AAC3C,QAAM,SAAS,MAAM,UAAU,YAAY;AAE3C,kBAAgB,aAAa,YAAY,sBAAsB;AAC/D,kBAAgB,YAAY,YAAY,qBAAqB;AAC/D;AAMA,SAAS,mCAAmC,OAA2B;AAErE,QAAM,kBAAkB,MAAM;AAE9B,QAAM,cAAc,qCAAqC,KAAK;AAE9D,MAAI,CAAC,eAAe,CAAC,YAAY,YAAY;AAC3C;AAAA,EACF;AAEA,cAAY,aAAa;AACzB,cAAY,cAAc,UAAU,OAAO,gCAAgC;AAE3E,MACE,OAAO,gBAAgB,0BAA0B,cACjD,gBAAgB,kBAAkB,MAAM,SAAS,GACjD;AACA,oBAAgB,sBAAsB,MAAM,SAAS;AAAA,EACvD;AACF;AAMA,SAAS,wCAAwC,OAAwB;AACvE,QAAM,eAAe;AACvB;AAMA,SAAS,mCAAmC,OAAoB;AAE9D,QAAM,eAAe,MAAM;AAE3B,QAAM,gBAAgB,aAAa;AAAA,IACjC,IAAI,8BAA8B;AAAA,EACpC;AAEA,MAAI,CAAC,eAAe;AAClB;AAAA,EACF;AAGA,QAAM,cAAc,gCAAgC,IAAI,aAAa;AAErE,MAAI,CAAC,aAAa;AAChB;AAAA,EACF;AAEA,cAAY,eAAe,aAAa,gBAAgB,YAAY;AACpE,cAAY,gBAAgB,aAAa,iBAAiB,YAAY;AACtE,gCAA8B,aAAa,YAAY,SAAS;AAClE;AAMA,SAAS,sCAAsC,OAAoB;AAEjE,QAAM,cAAc,MAAM;AAE1B,QAAM,cAAc,2BAA2B,IAAI,YAAY,QAAQ;AAEvE,MAAI,CAAC,eAAe,CAAC,0BAA0B,WAAW,GAAG;AAC3D;AAAA,EACF;AAEA,gCAA8B,aAAa,YAAY,SAAS;AAClE;AAMA,SAAS,sCAAsC,OAAyB;AAEtE,QAAM,cAAc,qCAAqC,KAAK;AAE9D,MAAI,CAAC,aAAa;AAChB;AAAA,EACF;AAEA,gCAA8B,aAAa,YAAY,YAAY,gBAAgB;AACrF;AAMA,SAAS,qCAAqC,OAAyB;AAErE,QAAM,cAAc,qCAAqC,KAAK;AAE9D,MAAI,CAAC,aAAa;AAChB;AAAA,EACF;AAEA,gCAA8B,aAAa,YAAY,YAAY,gBAAgB;AACrF;AAMA,SAAS,oCAAoC,OAAyB;AAEpE,QAAM,cAAc,qCAAqC,KAAK;AAE9D,MAAI,CAAC,aAAa;AAChB;AAAA,EACF;AAEA,gCAA8B,aAAa,mBAAmB;AAChE;AAMA,SAAS,oCAAoC,OAAyB;AAEpE,QAAM,cAAc,qCAAqC,KAAK;AAE9D,MAAI,CAAC,aAAa;AAChB;AAAA,EACF;AAEA,2BAAyB,WAAW;AACtC;AAOA,SAAS,qCAAqC,OAAoD;AAEhG,QAAM,uBAAuB,MAAM;AAEnC,MAAI,CAAC,sBAAsB;AACzB,WAAO;AAAA,EACT;AAGA,QAAM,gBAAgB,qBAAqB;AAAA,IACzC,IAAI,8BAA8B;AAAA,EACpC;AAEA,MAAI,CAAC,eAAe;AAClB,WAAO;AAAA,EACT;AAEA,SAAO,gCAAgC,IAAI,aAAa;AAC1D;AAQA,SAAS,8BACP,aACA,UACA,YACM;AAEN,QAAM,iBAAiB,wBAAwB,QAAQ;AAEvD,QAAM,aAAa,0BAA0B,WAAW,IACpD,qCAAqC,aAAa,UAAU,IAC5D;AAEJ,cAAY,YAAY;AACxB,cAAY,cAAc,UAAU;AAAA,IAClC;AAAA,IACA,iBAAiB;AAAA,EACnB;AACA,cAAY,iBAAiB,cAAc,GAAG,KAAK,MAAM,iBAAiB,GAAG,CAAC;AAC9E,cAAY,qBAAqB,WAAW,kBAAkB;AAC9D,cAAY,oBAAoB,WAAW,kBAAkB;AAC7D,cAAY,mBAAmB,WAAW,mBAAmB;AAC7D,qCAAmC,WAAW;AAE9C,MAAI,YAAY;AACd,uCAAmC,aAAa,UAAU;AAAA,EAC5D;AACF;AAQA,SAAS,qCACP,aACA,YACsC;AAEtC,QAAM,eAAe,YAAY,aAAa,sBAAsB;AAEpE,MAAI,aAAa,SAAS,KAAK,aAAa,UAAU,GAAG;AACvD,WAAO;AAAA,EACT;AAGA,QAAM,eAAe,YAAY,gBAAgB,sBAAsB;AAEvE,QAAM,gBAAgB,YAAY,KAAK,aAAa,OAAO,aAAa,QAAQ;AAChF,QAAM,gBAAgB,YAAY,KAAK,aAAa,MAAM,aAAa,SAAS;AAEhF,QAAM,eAAe,gBAAgB,aAAa,QAAQ,aAAa;AACvE,QAAM,eAAe,gBAAgB,aAAa,OAAO,aAAa;AAEtE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAOA,SAAS,mCACP,aACA,YACM;AAEN,cAAY,gBAAgB,aAAa;AACzC,cAAY,gBAAgB,YAAY;AAGxC,QAAM,gBAAgB,YAAY,aAAa,sBAAsB;AAErE,MAAI,cAAc,SAAS,KAAK,cAAc,UAAU,GAAG;AACzD;AAAA,EACF;AAGA,QAAM,wBACJ,WAAW,gBAAgB,WAAW,cAAc,cAAc;AACpE,QAAM,uBACJ,WAAW,gBAAgB,WAAW,cAAc,cAAc;AAGpE,cAAY,gBAAgB,aAAa,cAAc,OAAO;AAC9D,cAAY,gBAAgB,YAAY,cAAc,MAAM;AAC9D;AAMA,SAAS,mCAAmC,aAA6C;AAEvF,QAAM,gBAAgB,KAAK;AAAA,IACzB,YAAY,gBAAgB,cAAc;AAAA,IAC1C;AAAA,EACF;AAEA,QAAM,iBAAiB,KAAK;AAAA,IAC1B,YAAY,gBAAgB,eAAe;AAAA,IAC3C;AAAA,EACF;AAEA,QAAM,eAAe,KAAK,IAAI,YAAY,cAAc,CAAC;AAEzD,QAAM,gBAAgB,KAAK,IAAI,YAAY,eAAe,CAAC;AAE3D,QAAM,WAAW,KAAK,IAAI,GAAG,gBAAgB,cAAc,iBAAiB,aAAa;AAEzF,QAAM,eAAe,KAAK,IAAI,GAAG,KAAK,MAAM,eAAe,WAAW,YAAY,SAAS,CAAC;AAE5F,QAAM,gBAAgB,KAAK,IAAI,GAAG,KAAK,MAAM,gBAAgB,WAAW,YAAY,SAAS,CAAC;AAE9F,cAAY,aAAa,MAAM,QAAQ,GAAG,YAAY;AACtD,cAAY,aAAa,MAAM,SAAS,GAAG,aAAa;AAC1D;AAMA,SAAS,yBAAyB,aAA6C;AAC7E,MAAI,OAAO,YAAY,cAAc,UAAU,cAAc,YAAY,cAAc,MAAM;AAC3F,gBAAY,cAAc,MAAM;AAChC;AAAA,EACF;AAEA,cAAY,cAAc,gBAAgB,MAAM;AAChD,qCAAmC,WAAW;AAChD;AAMA,SAAS,mCAAmC,aAA6C;AACvF,cAAY,cAAc,UAAU,OAAO,8BAA8B;AACzE,cAAY,cAAc,UAAU,OAAO,gCAAgC;AAC3E,cAAY,aAAa;AACzB,cAAY,aAAa,gBAAgB,KAAK;AAC9C,cAAY,aAAa,gBAAgB,KAAK;AAC9C,cAAY,aAAa,gBAAgB,OAAO;AAChD,cAAY,eAAe,cAAc;AACzC,cAAY,gBAAgB,aAAa;AACzC,cAAY,gBAAgB,YAAY;AACxC,cAAY,YAAY;AAExB,MAAI,YAAY,oBAAoB,aAAa;AAC/C,gBAAY,mBAAmB,MAAM;AAAA,EACvC;AACF;AAOA,SAAS,0BAA0B,aAAgD;AACjF,SAAO,YAAY,cAAc,QAAQ,YAAY,cAAc,aAAa,MAAM;AACxF;AAOA,SAAS,uBAAuB,cAAwC;AACtE,SAAO,aAAa,cAAc,aAAa,aAAa,KAAK,KAAK,aAAa,OAAO;AAC5F;AAOA,SAAS,mCAAmC,cAAwC;AAElF,QAAM,eAAe,aAAa,IAAI,KAAK;AAE3C,MAAI,aAAa,WAAW,GAAG;AAC7B,WAAO;AAAA,EACT;AAEA,SAAO,GAAG,2BAA2B,SAAI,YAAY;AACvD;AAQA,SAAS,iCACP,cACA,aACQ;AAER,QAAM,eAAe,aAAa,IAAI,KAAK;AAE3C,QAAM,iBAAiB,aAAa,MAAM,KAAK;AAE/C,SAAO,gBAAgB,kBAAkB;AAC3C;;;AF54BA,IAAM,0BAA0B;AAMhC,SAAS,qBAA2C;AAClD,SAAO,CAAC,SAAe;AAGrB,UAAM,MAAM,aAAa,CAAC,MAAM,OAAO,WAAW;AAChD,UAAI,WAAW,UAAa,UAAU,QAAW;AAC/C;AAAA,MACF;AAGA,YAAM,kBAAkB,sBAAsB,IAAI;AAElD,UAAI,iBAAiB;AACnB,eAAO,SAAS,KAAK,IAAI;AACzB,eAAO;AAAA,MACT;AAAA,IACF,CAAC;AAED,UAAM,MAAM,aAAa,CAAC,SAAS;AACjC,wBAAkB,IAAI;AAAA,IACxB,CAAC;AAAA,EACH;AACF;AAOA,SAAS,sBAAsB,MAA0C;AACvE,MAAI,KAAK,SAAS,WAAW,GAAG;AAC9B,WAAO;AAAA,EACT;AAGA,QAAM,YAAY,KAAK,SAAS,CAAC;AAEjC,MAAI,CAAC,YAAY,SAAS,GAAG;AAC3B,WAAO;AAAA,EACT;AAEA,oBAAkB,SAAS;AAE3B,SAAO;AAAA,IACL,MAAM;AAAA;AAAA,IAEN,UAAU,KAAK;AAAA,IACf,MAAM;AAAA,MACJ,OAAO;AAAA,MACP,aAAa;AAAA,QACX,WAAW,CAAC,uBAAuB;AAAA,MACrC;AAAA,IACF;AAAA,IACA,UAAU,CAAC,qBAAqB,SAAS,GAAG,GAAG,wBAAwB,SAAS,CAAC;AAAA,EACnF;AACF;AAMA,SAAS,kBAAkB,WAAyC;AAClE,YAAU,OAAO;AAAA,IACf,GAAG,UAAU;AAAA,IACb,aAAa;AAAA,MACX,GAAG,UAAU,MAAM;AAAA,MACnB,WAAW,CAAC,wBAAwB;AAAA,IACtC;AAAA,EACF;AACF;AAOA,SAAS,qBAAqB,WAA+C;AAC3E,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,MACJ,OAAO;AAAA,MACP,aAAa;AAAA,QACX,WAAW,CAAC,sBAAsB;AAAA,MACpC;AAAA,IACF;AAAA,IACA,UAAU,CAAC,WAAW,wBAAwB,SAAS,CAAC;AAAA,EAC1D;AACF;AAOA,SAAS,wBAAwB,WAAmD;AAElF,QAAM,aACJ,UAAU,SAAS,WAAW,OAAO,UAAU,UAAU,WACrD,UAAU,MAAM,KAAK,IACrB;AAEN,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,CAAC;AAAA,EACV;AAEA,SAAO;AAAA,IACL;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,QACJ,OAAO;AAAA,QACP,aAAa;AAAA,UACX,WAAW,CAAC,wBAAwB;AAAA,QACtC;AAAA,MACF;AAAA,MACA,UAAU,CAAC,EAAE,MAAM,QAAQ,OAAO,WAAW,CAAC;AAAA,IAChD;AAAA,EACF;AACF;AAOA,SAAS,wBAAwB,WAAkD;AAEjF,QAAM,eAAe,UAAU,KAAK,KAAK,KAAK;AAE9C,QAAM,iBAAiB,UAAU,SAAS,UAAU,UAAU,MAAM,UAAU;AAE9E,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,MACJ,OAAO;AAAA,MACP,aAAa;AAAA,QACX,WAAW,CAAC,yBAAyB;AAAA,MACvC;AAAA,IACF;AAAA,IACA,UAAU;AAAA,MACR;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,UACJ,OAAO;AAAA,UACP,aAAa;AAAA,YACX,WAAW,CAAC,8BAA8B;AAAA,UAC5C;AAAA,QACF;AAAA,MACF;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,UACJ,OAAO;AAAA,UACP,aAAa;AAAA,YACX,WAAW,CAAC,8BAA8B;AAAA,UAC5C;AAAA,QACF;AAAA,QACA,UAAU,CAAC,EAAE,MAAM,QAAQ,OAAO,aAAa,CAAC;AAAA,MAClD;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,UACJ,OAAO;AAAA,UACP,aAAa;AAAA,YACX,WAAW,CAAC,gCAAgC;AAAA,UAC9C;AAAA,QACF;AAAA,QACA,UAAU,CAAC,EAAE,MAAM,QAAQ,OAAO,eAAe,CAAC;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AACF;AAMA,SAAS,sBAAsB,aAA+B;AAE5D,QAAM,gBAAgB,YAAY;AAAA,IAChC,OAAO,wBAAwB;AAAA,EACjC;AAEA,gBAAc,QAAQ,sBAAsB;AAC9C;AAMA,SAAS,uBAAuB,cAAsC;AACpE,2BAAyB,YAAY;AAErC,MAAI,aAAa,QAAQ,uBAAuB,MAAM,QAAQ;AAC5D;AAAA,EACF;AAEA,eAAa,QAAQ,uBAAuB,IAAI;AAChD,eAAa,iBAAiB,QAAQ,uBAAuB;AAC7D,eAAa,iBAAiB,SAAS,wBAAwB;AAC/D,0BAAwB,YAAY;AACtC;AAMA,SAAS,wBAAwB,OAAoB;AAEnD,QAAM,eAAe,MAAM;AAE3B,2BAAyB,YAAY;AACvC;AAMA,SAAS,yBAAyB,OAAoB;AAEpD,QAAM,eAAe,MAAM;AAE3B,2BAAyB,YAAY;AACvC;AAMA,SAAS,yBAAyB,cAAsC;AAEtE,QAAM,eAAe,aAAa,QAAqB,IAAI,sBAAsB,EAAE;AAEnF,MAAI,CAAC,cAAc;AACjB;AAAA,EACF;AAGA,QAAM,WAAW,aAAa,YAAY,aAAa,iBAAiB;AAExE,QAAM,WAAW,aAAa,YAAY,aAAa,eAAe;AAEtE,eAAa,UAAU,OAAO,+BAA+B,QAAQ;AACrE,eAAa,UAAU,OAAO,+BAA+B,QAAQ;AACvE;;;AGnRA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,kBAAkB;AAC3B,SAAS,QAAAC,OAAM,SAAAC,cAAa;AAG5B,IAAM,0BAA0B;AAGhC,IAAM,8BAA8B;AAQpC,SAAS,qBAA2C;AAClD,SAAO,CAAC,SAAe;AACrB,IAAAA,OAAM,MAAM,WAAW,CAAC,MAAM,OAAO,WAAW;AAC9C,UAAI,WAAW,UAAa,UAAU,QAAW;AAC/C;AAAA,MACF;AAEA,UAAI,mBAAmB,IAAI,GAAG;AAC5B,eAAO,SAAS,KAAK,IAAI,sBAAsB,IAAI;AAEnD,eAAOD;AAAA,MACT;AAIA,YAAM,kBAAkB,gCAAgC,IAAI;AAC5D,UAAI,iBAAiB;AACnB,eAAO,SAAS,KAAK,IAAI,sBAAsB,eAAe;AAC9D,eAAOA;AAAA,MACT;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAOA,SAAS,mBAAmB,MAAuC;AACjE,SAAO,KAAK,SAAS,aAAa,KAAK,YAAY;AACrD;AAOA,SAAS,gCAAgC,MAAoC;AAC3E,MAAI,KAAK,YAAY,KAAK;AACxB,WAAO;AAAA,EACT;AAGA,QAAM,sBAAsB,KAAK,SAAS,OAAO,CAAC,cAAc;AAC9D,QAAI,UAAU,SAAS,QAAQ;AAC7B,aAAO,UAAU,MAAM,KAAK,EAAE,SAAS;AAAA,IACzC;AACA,WAAO;AAAA,EACT,CAAC;AAED,MACE,oBAAoB,WAAW,KAC/B,mBAAmB,oBAAoB,CAAC,CAAC,GACzC;AACA,WAAO,oBAAoB,CAAC;AAAA,EAC9B;AAEA,SAAO;AACT;AAOA,SAAS,sBAAsB,WAA6B;AAC1D,2BAAyB,SAAS;AAGlC,QAAM,YAAY,uBAAuB,SAAS;AAElD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY,EAAE,WAAW,CAAC,uBAAuB,EAAE;AAAA,IACnD,UAAU;AAAA,MACR;AAAA,QACE,MAAM;AAAA,QACN,SAAS;AAAA,QACT,YAAY,EAAE,WAAW,CAAC,sBAAsB,EAAE;AAAA,QAClD,UAAU,CAAC,WAAW,wBAAwB,SAAS,CAAC;AAAA,MAC1D;AAAA,IACF;AAAA,EACF;AACF;AAMA,SAAS,yBAAyB,WAA0B;AAC1D,aAAW,WAAW,wBAAwB;AAChD;AAOA,SAAS,uBAAuB,WAA4B;AAC1D,MAAI,OAAO,UAAU,WAAW,QAAQ,UAAU;AAChD,WAAO,UAAU,WAAW;AAAA,EAC9B;AAEA,aAAW,aAAa,UAAU,UAAU;AAC1C,QACE,UAAU,SAAS,aACnB,UAAU,YAAY,YACtB,OAAO,UAAU,WAAW,QAAQ,UACpC;AACA,aAAO,UAAU,WAAW;AAAA,IAC9B;AAAA,EACF;AAEA,SAAO;AACT;AAOA,SAAS,wBAAwB,WAA4B;AAC3D,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,YAAY,EAAE,WAAW,CAAC,yBAAyB,EAAE;AAAA,IACrD,UAAU;AAAA,MACR;AAAA,QACE,MAAM;AAAA,QACN,SAAS;AAAA,QACT,YAAY,EAAE,WAAW,CAAC,8BAA8B,EAAE;AAAA,QAC1D,UAAU,CAAC;AAAA,MACb;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,SAAS;AAAA,QACT,YAAY,EAAE,WAAW,CAAC,8BAA8B,EAAE;AAAA,QAC1D,UAAU,CAAC,EAAE,MAAM,QAAQ,OAAO,4BAA4B,CAAC;AAAA,MACjE;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,SAAS;AAAA,QACT,YAAY,EAAE,WAAW,CAAC,gCAAgC,EAAE;AAAA,QAC5D,UAAU,CAAC,EAAE,MAAM,QAAQ,OAAO,UAAU,CAAC;AAAA,MAC/C;AAAA,IACF;AAAA,EACF;AACF;AAMA,SAAS,sBAAsB,aAA+B;AAE5D,QAAM,gBAAgB,YAAY;AAAA,IAChC,SAAS,wBAAwB;AAAA,EACnC;AAEA,gBAAc,QAAQ,sBAAsB;AAC9C;AAMA,SAAS,uBAAuB,cAAsC;AACpE,2BAAyB,YAAY;AAErC,MAAI,aAAa,QAAQ,uBAAuB,MAAM,QAAQ;AAC5D;AAAA,EACF;AAEA,eAAa,QAAQ,uBAAuB,IAAI;AAChD,eAAa,iBAAiB,cAAc,yBAAyB;AACrE,eAAa,iBAAiB,SAAS,wBAAwB;AAG/D,eACG,iBAAoC,QAAQ,EAC5C,QAAQ,CAAC,kBAAkB;AAC1B,kBAAc,iBAAiB,SAAS,8BAA8B;AAAA,EACxE,CAAC;AACL;AAMA,SAAS,0BAA0B,OAAoB;AAErD,QAAM,eAAe,MAAM;AAE3B,2BAAyB,YAAY;AACvC;AAMA,SAAS,yBAAyB,OAAoB;AAEpD,QAAM,eAAe,MAAM;AAE3B,0BAAwB,YAAY;AACtC;AAOA,SAAS,+BAA+B,OAAoB;AAE1D,QAAM,gBAAgB,MAAM;AAE5B,QAAM,eAAe,cAAc;AAAA,IACjC,SAAS,wBAAwB;AAAA,EACnC;AAEA,MAAI,CAAC,cAAc;AACjB;AAAA,EACF;AACA,MAAI,aAAa,iBAAiB,aAAa,mBAAmB;AAChE;AAAA,EACF;AAEA,0BAAwB,YAAY;AACtC;AAMA,SAAS,yBAAyB,cAAsC;AAEtE,QAAM,eAAe,aAAa;AAAA,IAChC,IAAI,sBAAsB;AAAA,EAC5B;AAEA,MAAI,CAAC,cAAc;AACjB;AAAA,EACF;AAGA,QAAM,WACJ,aAAa,UAAU,QACvB,aAAa,iBAAiB,aAAa;AAE7C,QAAM,WAAW,aAAa,cAAc,aAAa;AAEzD,eAAa,UAAU,OAAO,+BAA+B,QAAQ;AACrE,eAAa,UAAU;AAAA,IACrB;AAAA,IACA,YAAY,CAAC;AAAA,EACf;AACF;AAMA,SAAS,wBAAwB,cAAsC;AAErE,QAAM,eAAe,aAAa;AAAA,IAChC,IAAI,sBAAsB;AAAA,EAC5B;AAEA,MAAI,CAAC,cAAc;AACjB;AAAA,EACF;AAEA,eAAa,UAAU,IAAI,6BAA6B;AACxD,eAAa,UAAU,OAAO,6BAA6B;AAC7D;;;AChTA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,4BAAAE;AAAA,EACA,4BAAAC;AAAA,EACA,6BAAAC;AAAA,EACA,kCAAAC;AAAA,EACA,oCAAAC;AAAA,EACA,kCAAAC;AAAA,EACA,2BAAAC;AAAA,EACA,0BAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,4BAAAC;AAAA,EACA,6BAAAC;AAAA,EACA,kCAAAC;AAAA,EACA,oCAAAC;AAAA,EACA,kCAAAC;AAAA,EACA,2BAAAC;AAAA,EACA,0BAAAC;AAAA,OACK;AAEP,OAAO,eAAe;AACtB,SAAS,qBAAqB;AAS9B,SAAS,0BAA0B,YAA4B;AAE7D,QAAM,YAAY;AAElB,MAAI,OAAO,UAAU,aAAa,YAAY;AAC5C,WAAO;AAAA,EACT;AAEA,SAAO,UAAU,SAAS,UAAU;AACtC;AAMA,SAAS,gCAAsD;AAE7D,QAAM,kBAAkB,cAAc,YAAY,CAAC;AAEnD,QAAM,oBAAoB,cAAc,cAAc,CAAC;AAEvD,QAAM,oBAAoB;AAAA,IACxB;AAAA,IACA,CAAC,aAAa,oBAAoB,kBAAkB,cAAc;AAAA,EAMpE;AAEA,QAAM,oBAAoB;AAAA,IACxB,GAAI,kBAAkB,WAAW,CAAC;AAAA,IAClC,CAAC,aAAa,sBAAsB;AAAA,EACtC;AAEA,QAAM,gBAAgB,CAAC,aAAa,CAAC,aAAa,kBAAkB,CAAqB;AAEzF,QAAM,iBAAiB;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,CAAC,aAAa,yBAAyB,mBAAmB;AAAA,IAC1D;AAAA,EACF;AAEA,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA,CAAC,aAAa,qBAAqB;AAAA,EACrC;AAEA,QAAM,wBAAwB;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,IACA,CAAC,aAAa,sBAAsB,qBAAqB,0BAA0B;AAAA,EAIrF;AAEA,QAAM,gBAAgB;AAAA,IACpB,GAAI,kBAAkB,OAAO,CAAC;AAAA,IAC9B,CAAC,aAAa,wBAAwB,kBAAkB,6BAA6B;AAAA,EAIvF;AAEA,QAAM,2BAA2B;AAAA,IAC/B,CAAC,aAAa,6BAA6B,kCAAkC;AAAA,EAK/E;AAEA,QAAM,iBAAiB;AAAA,IACrB,GAAI,kBAAkB,QAAQ,CAAC;AAAA,IAC/B;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACAC;AAAA,MACAC;AAAA,MACAC;AAAA,MACAC;AAAA,MACAC;AAAA,MACAC;AAAA,MACAC;AAAA,MACAC;AAAA,MACAC;AAAA,MACAC;AAAA,IACF;AAAA,EACF;AAEA,QAAM,mBAAmB;AAAA,IACvB,CAAC,aAAaC,0BAAyBC,wBAAuB;AAAA,EAKhE;AAEA,QAAM,uBAAuB,CAAC,CAAC,aAAaC,yBAAwB,CAAqB;AAEzF,QAAM,kBAAkB;AAAA,IACtB,GAAI,kBAAkB,OAAO,CAAC;AAAA,IAC9B,CAAC,aAAaC,yBAAwB;AAAA,EACxC;AAGA,QAAM,kBAAkB;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,CAAC,aAAaC,yBAAwB;AAAA,EACxC;AAEA,QAAM,mBAAmB,CAAC,OAAO,QAAQ,OAAO;AAEhD,QAAM,iBAAiB,CAAC,OAAO;AAE/B,QAAM,qBAAqB;AAAA,IACzB;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA,IAAI,OAAO,IAAI,qBAAqB,kCAAkC,GAAG;AAAA,IAC3E;AAAA,EACF;AAEA,QAAM,qBAAqB,CAAC,GAAI,kBAAkB,GAAG,KAAK,CAAC,GAAI,yBAAyB;AAExF,SAAO;AAAA,IACL,GAAG;AAAA,IACH,eAAe;AAAA,IACf,UAAU,MAAM;AAAA,MACd,oBAAI,IAAI;AAAA,QACN,GAAG;AAAA,QACH;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,YAAY;AAAA,MACV,GAAG;AAAA,MACH,KAAK;AAAA,MACL,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,MACT,KAAK;AAAA,MACL,GAAG;AAAA,MACH,IAAI;AAAA,MACJ,IAAI;AAAA,MACJ,KAAK;AAAA,MACL,IAAI;AAAA,MACJ,MAAM;AAAA,MACN,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,KAAK;AAAA,MACL,OAAO;AAAA,MACP,QAAQ;AAAA,IACV;AAAA,EACF;AACF;;;AC9OA,SAAS,SAAAC,cAAa;AAKtB,IAAM,oCAAoC;AAM1C,SAAS,wBAA8C;AACrD,SAAO,CAAC,SAAe;AACrB,IAAAC,OAAM,MAAM,aAAa,CAAC,MAAM,OAAO,WAAW;AAChD,UAAI,WAAW,UAAa,UAAU,QAAW;AAC/C;AAAA,MACF;AAGA,YAAM,qBAAqB,yBAAyB,IAAI;AAExD,UAAI,oBAAoB;AACtB,eAAO,SAAS,KAAK,IAAI;AAAA,MAC3B;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAOA,SAAS,yBAAyB,MAA6C;AAE7E,QAAM,gBAAgB,gBAAgB,IAAI;AAE1C,QAAM,kBAAkB,kCAAkC,KAAK,aAAa;AAE5E,MAAI,CAAC,iBAAiB;AACpB,WAAO;AAAA,EACT;AAGA,QAAM,iBAAiB,gBAAgB,CAAC,EAAE,KAAK;AAE/C,QAAM,wBAAwB,gBAAgB,CAAC,EAAE,KAAK;AAEtD,MAAI,eAAe,WAAW,KAAK,sBAAsB,WAAW,GAAG;AACrE,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAM;AAAA;AAAA,IAEN,UAAU,KAAK;AAAA,IACf,MAAM;AAAA,MACJ,OAAO;AAAA,IACT;AAAA,IACA,UAAU;AAAA,MACR;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,UACJ,OAAO;AAAA,QACT;AAAA,QACA,UAAU,CAAC,EAAE,MAAM,QAAQ,OAAO,eAAe,CAAC;AAAA,MACpD;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,UACJ,OAAO;AAAA,QACT;AAAA,QACA,UAAU,CAAC,EAAE,MAAM,QAAQ,OAAO,sBAAsB,CAAC;AAAA,MAC3D;AAAA,IACF;AAAA,EACF;AACF;;;AC5EA,SAAS,sBAAAC,qBAAoB,oBAAAC,mBAAkB,kBAAAC,uBAAsB;AAGrE,SAAS,cAAAC,mBAAkB;AAC3B,SAAS,SAAAC,cAAa;AAStB,SAAS,mBAAyC;AAChD,SAAO,CAAC,SAAe;AACrB,IAAAA,OAAM,MAAM,WAAW,CAAC,SAAS;AAC/B,UAAI,KAAK,YAAY,WAAW;AAC9B;AAAA,MACF;AAGA,YAAM,aAAa,oBAAoB,KAAK,WAAW,SAAS;AAGhE,UAAI,CAAC,WAAW,SAASF,eAAc,GAAG;AAExC,QAAAC,YAAW,MAAMH,qBAAoBC,iBAAgB;AAAA,MACvD;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAOA,SAAS,oBAAoB,WAA8B;AACzD,MAAI,MAAM,QAAQ,SAAS,GAAG;AAC5B,WAAO,UAAU,OAAO,CAAC,SAAyB,OAAO,SAAS,QAAQ;AAAA,EAC5E;AACA,MAAI,OAAO,cAAc,UAAU;AACjC,WAAO,UAAU,MAAM,MAAM,EAAE,OAAO,OAAO;AAAA,EAC/C;AACA,SAAO,CAAC;AACV;;;AC9CA;AAAA,EACE,oBAAAI;AAAA,EACA,iCAAAC;AAAA,EACA,0BAAAC;AAAA,EACA,gCAAAC;AAAA,EACA,+BAAAC;AAAA,EACA,sCAAAC;AAAA,OACK;AAGP,SAAS,SAAS,uBAAuB;AAUzC,IAAM,yBAAyB;AAG/B,IAAM,8BAA8B;AAGpC,IAAM,mCAAmC;AAQzC,SAAS,4BAAkD;AACzD,SAAO,CAAC,SAAe;AAErB,UAAM,aAAa,KAAK;AAExB,UAAM,WAAW,WAAW,CAAC;AAE7B,QAAI,CAAC,YAAY,SAAS,SAAS,QAAQ;AACzC;AAAA,IACF;AAEA,eAAW,CAAC,IAAI,sBAAsB,QAAQ;AAAA,EAChD;AACF;AAOA,SAAS,sBAAsB,UAA4C;AAEzE,QAAM,aAAa,SAAS;AAE5B,MAAI;AAEJ,MAAI;AACF,kBAAc,gBAAgB,UAAU;AAAA,EAC1C,QAAQ;AACN,kBAAc;AAAA,EAChB;AAGA,MAAI,CAAC,cAAc,WAAW,GAAG;AAC/B,WAAO;AAAA,MACL,MAAM;AAAA,MACN,MAAM;AAAA,MACN,UAAU,SAAS;AAAA,MACnB,OAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA;AAAA,IAEN,UAAU,SAAS;AAAA,IACnB,MAAM;AAAA,MACJ,OAAO;AAAA,MACP,aAAa,EAAE,WAAW,CAACH,yBAAwBF,iBAAgB,EAAE;AAAA,IACvE;AAAA,IACA,UAAU;AAAA,MACR;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,UACJ,OAAO;AAAA,UACP,aAAa,EAAE,WAAW,CAACC,8BAA6B,EAAE;AAAA,QAC5D;AAAA,QACA,UAAU;AAAA,UACR;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,cACJ,OAAO;AAAA,cACP,aAAa,EAAE,WAAW,CAACE,6BAA4B,EAAE;AAAA,YAC3D;AAAA,YACA,UAAU,CAAC,EAAE,MAAM,QAAQ,OAAO,uBAAuB,CAAC;AAAA,UAC5D;AAAA,QACF;AAAA,MACF;AAAA,MACA,0BAA0B,aAAa,KAAK;AAAA,IAC9C;AAAA,EACF;AACF;AAOA,SAAS,cAAc,OAAkD;AACvE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAQA,SAAS,0BAA0B,QAAiC,QAAkC;AAEpG,QAAM,iBAAiB,SACnB,CAACC,8BAA6BC,mCAAkC,IAChE,CAACD,4BAA2B;AAEhC,QAAM,eAA6D,CAAC;AAEpE,SAAO,QAAQ,MAAM,EAAE,QAAQ,CAAC,CAAC,UAAU,UAAU,MAAM;AACzD,iBAAa,KAAK;AAAA,MAChB,MAAM;AAAA,MACN,MAAM,EAAE,OAAO,KAAK;AAAA,MACpB,UAAU,CAAC,EAAE,MAAM,QAAQ,OAAO,SAAS,CAAC;AAAA,IAC9C,CAAC;AACD,iBAAa,KAAK;AAAA,MAChB,MAAM;AAAA,MACN,MAAM,EAAE,OAAO,KAAK;AAAA,MACpB,UAAU,4BAA4B,UAAU;AAAA,IAClD,CAAC;AAAA,EACH,CAAC;AAED,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,MACJ,OAAO;AAAA,MACP,aAAa,EAAE,WAAW,eAAe;AAAA,IAC3C;AAAA,IACA,UAAU;AAAA,EACZ;AACF;AAOA,SAAS,4BAA4B,OAAoD;AAEvF,MAAI,cAAc,KAAK,GAAG;AACxB,WAAO,CAAC,0BAA0B,OAAO,IAAI,CAAC;AAAA,EAChD;AAGA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,WAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,OAAO,MAAM,IAAI,uBAAuB,EAAE,KAAK,2BAA2B;AAAA,MAC5E;AAAA,IACF;AAAA,EACF;AAEA,SAAO,CAAC,EAAE,MAAM,QAAQ,OAAO,wBAAwB,KAAK,EAAE,CAAC;AACjE;AAOA,SAAS,wBAAwB,OAAwB;AACvD,MAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B;AACA,SAAO,OAAO,KAAK;AACrB;;;AC9LA,SAAS,QAAAE,OAAM,SAAAC,cAAa;AAG5B,IAAM,2BAA2B;AAOjC,SAAS,sBAA4C;AACnD,SAAO,CAAC,SAAe;AACrB,IAAAA,OAAM,MAAM,QAAQ,CAAC,MAAM,OAAO,WAAW;AAC3C,UAAI,WAAW,UAAa,UAAU,UAAa,CAAC,KAAK,MAAM,SAAS,IAAI,GAAG;AAC7E;AAAA,MACF;AAGA,YAAM,WAAW,oBAAoB,KAAK,KAAK;AAE/C,aAAO,SAAS,OAAO,OAAO,GAAG,GAAG,QAAQ;AAG5C,aAAO,CAACD,OAAM,QAAQ,SAAS,MAAM;AAAA,IACvC,CAAC;AAAA,EACH;AACF;AAOA,SAAS,oBAAoB,WAAsC;AAEjE,QAAM,WAA8B,CAAC;AAErC,MAAI,SAAS;AAEb,2BAAyB,YAAY;AAErC,MAAI,QAAgC,yBAAyB,KAAK,SAAS;AAE3E,SAAO,UAAU,MAAM;AAErB,UAAM,cAAc,UAAU,MAAM,QAAQ,MAAM,KAAK;AAEvD,QAAI,YAAY,SAAS,GAAG;AAC1B,eAAS,KAAK,EAAE,MAAM,QAAQ,OAAO,YAAY,CAAC;AAAA,IACpD;AAEA,aAAS,KAAK;AAAA,MACZ,MAAM;AAAA,MACN,MAAM;AAAA,QACJ,OAAO;AAAA,MACT;AAAA,MACA,UAAU,CAAC,EAAE,MAAM,QAAQ,OAAO,MAAM,CAAC,EAAE,CAAC;AAAA,IAC9C,CAAC;AAED,aAAS,MAAM,QAAQ,MAAM,CAAC,EAAE;AAChC,YAAQ,yBAAyB,KAAK,SAAS;AAAA,EACjD;AAGA,QAAM,eAAe,UAAU,MAAM,MAAM;AAC3C,MAAI,aAAa,SAAS,GAAG;AAC3B,aAAS,KAAK,EAAE,MAAM,QAAQ,OAAO,aAAa,CAAC;AAAA,EACrD;AAEA,SAAO;AACT;;;ACtEA;AAAA,EACE,oBAAAE;AAAA,EACA,2BAAAC;AAAA,EACA;AAAA,EACA,kBAAAC;AAAA,EACA;AAAA,EACA,yBAAAC;AAAA,EACA;AAAA,EACA,uBAAAC;AAAA,EACA,uBAAAC;AAAA,EACA,8BAAAC;AAAA,EACA,sBAAAC;AAAA,EACA,0BAAAC;AAAA,EACA,yBAAAC;AAAA,OACK;AAEP,OAAO,mBAAmB;AAE1B,SAAS,SAAAC,cAAa;AA4BtB,IAAM,qBAAqB;AAG3B,IAAM,wBAAwB;AAG9B,IAAM,+BAA+B;AAGrC,IAAM,6BAA6B;AAGnC,IAAM,iBAAiB;AAGvB,IAAM,mBAAmB;AAGzB,IAAM,4BAA4B;AAMlC,SAAS,wBAA8C;AACrD,SAAO,CAAC,SAAe;AAErB,UAAM,cAAc,mBAAmB,IAAI;AAG3C,sBAAkB,MAAM,WAAW;AAAA,EACrC;AACF;AAOA,SAAS,mBAAmB,MAA0B;AAGpD,QAAM,iBAAiB,IAAI,cAAc;AAEzC,QAAM,cAA4B,CAAC;AAEnC,QAAM,qBAAqB,MAAM,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;AAE5D,MAAI;AAEJ,EAAAC,OAAM,MAAM,WAAW,CAAC,SAAS;AAE/B,UAAM,eAAe,KAAK;AAE1B,UAAM,cAAc,gBAAgB,IAAI,EAAE,KAAK;AAE/C,UAAM,YAAY,gBAAgB,gBAAgB,aAAa,YAAY,SAAS,CAAC;AAErF,UAAM,eAAe;AAAA,MACnB;AAAA,MACA;AAAA,MACA,oBAAoB;AAAA,IACtB;AAEA,uBAAmB,oBAAoB;AAEvC,SAAK,OAAO;AAAA,MACV,GAAG,KAAK;AAAA,MACR,aAAa;AAAA,QACX,GAAG,KAAK,MAAM;AAAA,QACd,IAAI;AAAA,MACN;AAAA,IACF;AAIA,SAAK,WAAW;AAAA,MACd;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,UACJ,OAAO;AAAA,UACP,aAAa;AAAA,YACX,WAAW,CAACC,wBAAuB;AAAA,UACrC;AAAA,QACF;AAAA,QACA,UAAU,KAAK;AAAA,MACjB;AAAA,IACF;AAEA,gBAAY,KAAK;AAAA,MACf,OAAO;AAAA,MACP,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,MAAM,eAAe;AAAA,IACvB,CAAC;AAAA,EACH,CAAC;AAED,SAAO;AACT;AAOA,SAAS,kBAAkB,MAAY,aAAiC;AACtE,EAAAD,OAAM,MAAM,aAAa,CAAC,MAAM,OAAO,WAAW;AAChD,QAAI,WAAW,UAAa,UAAU,UAAa,CAAC,qBAAqB,IAAI,GAAG;AAC9E;AAAA,IACF;AAEA,WAAO,SAAS,KAAK,IAAI,cAAc,aAAa,IAAI;AAAA,EAC1D,CAAC;AACH;AAOA,SAAS,qBAAqB,MAA0B;AACtD,MAAI,KAAK,SAAS,WAAW,GAAG;AAC9B,WAAO;AAAA,EACT;AAGA,QAAM,YAAY,KAAK,SAAS,CAAC;AAEjC,SAAO,UAAU,SAAS,UAAU,mBAAmB,KAAK,UAAU,KAAK;AAC7E;AAQA,SAAS,cAAc,aAA2B,YAA4B;AAE5E,QAAM,UAAU,cAAc,WAAW;AAEzC,SAAO;AAAA,IACL,MAAM;AAAA;AAAA,IAEN,UAAU,WAAW;AAAA,IACrB,MAAM;AAAA,MACJ,OAAO;AAAA,MACP,aAAa;AAAA;AAAA,QAEX,WAAW,CAACE,iBAAgBC,iBAAgB;AAAA,MAC9C;AAAA,IACF;AAAA,IACA,UAAU;AAAA,MACR;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,UACJ,OAAO;AAAA,UACP,aAAa;AAAA,YACX,WAAW,CAACC,uBAAsB;AAAA,UACpC;AAAA,QACF;AAAA,QACA,UAAU;AAAA,UACR;AAAA,YACE,MAAM;AAAA,YACN,OAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,MAAM;AAAA,UACJ,OAAO;AAAA,UACP,aAAa;AAAA,YACX,WAAW;AAAA,YACX,WAAW,CAACC,mBAAkB;AAAA,UAChC;AAAA,QACF;AAAA,QACA,UAAU,CAAC,kBAAkB,SAAS,KAAK,CAAC;AAAA,MAC9C;AAAA,IACF;AAAA,EACF;AACF;AAOA,SAAS,cAAc,aAA0C;AAE/D,QAAM,YAA2B,CAAC;AAElC,QAAM,YAA2B,CAAC;AAGlC,WAAS,eAAe,GAAG,eAAe,YAAY,QAAQ,gBAAgB,GAAG;AAE/E,UAAM,aAAa,YAAY,YAAY;AAE3C,UAAM,cAA2B;AAAA,MAC/B,GAAG;AAAA,MACH,UAAU,CAAC;AAAA,IACb;AAGA,WAAO,UAAU,SAAS,KAAK,UAAU,UAAU,SAAS,CAAC,EAAE,SAAS,YAAY,OAAO;AACzF,gBAAU,IAAI;AAAA,IAChB;AAEA,QAAI,UAAU,WAAW,GAAG;AAC1B,gBAAU,KAAK,WAAW;AAAA,IAC5B,OAAO;AACL,gBAAU,UAAU,SAAS,CAAC,EAAE,SAAS,KAAK,WAAW;AAAA,IAC3D;AAEA,cAAU,KAAK,WAAW;AAAA,EAC5B;AAEA,SAAO;AACT;AAQA,SAAS,kBAAkB,UAAyB,UAA4B;AAE9E,QAAM,oBAAoB,WACtB,CAACC,sBAAqBC,2BAA0B,IAChD,CAACD,oBAAmB;AAExB,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,MACJ,OAAO;AAAA,MACP,aAAa;AAAA,QACX,WAAW;AAAA,MACb;AAAA,IACF;AAAA,IACA,UAAU,SAAS,IAAI,iBAAiB;AAAA,EAC1C;AACF;AAOA,SAAS,kBAAkB,SAA+B;AAExD,QAAM,cAAc,QAAQ,SAAS,SAAS;AAE9C,QAAM,oBAAoB,wBAAwB,QAAQ,OAAO,WAAW;AAK5E,QAAM,eAAoC,cACtC,CAAC,oBAAoB,GAAG,kBAAkB,OAAO,GAAG,kBAAkB,QAAQ,UAAU,IAAI,CAAC,IAC7F,CAAC,kBAAkB,OAAO,CAAC;AAE/B,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,MACJ,OAAO;AAAA,MACP,aAAa;AAAA,QACX,cAAc,QAAQ;AAAA,QACtB,WAAW;AAAA,MACb;AAAA,IACF;AAAA,IACA,UAAU;AAAA,EACZ;AACF;AAQA,SAAS,wBAAwB,OAAe,aAAgC;AAC9E,SAAO;AAAA,IACLE;AAAA,IACA,GAAGA,sBAAqB,WAAW,KAAK;AAAA,IACxC,GAAI,cAAc,CAAC,0BAA0B,IAAI,CAAC;AAAA,EACpD;AACF;AAOA,SAAS,sBAAiC;AACxC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM;AAAA,MACJ,OAAO;AAAA,MACP,aAAa;AAAA,QACX,MAAM;AAAA,QACN,WAAW,CAACC,sBAAqB;AAAA,QACjC,cAAc;AAAA,QACd,WAAW;AAAA,MACb;AAAA,IACF;AAAA,IACA,UAAU,CAAC;AAAA,EACb;AACF;AASA,SAAS,kBAAkB,SAA4B;AACrD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,KAAK,IAAI,QAAQ,EAAE;AAAA,IACnB,MAAM;AAAA,MACJ,aAAa;AAAA,QACX,WAAW,CAACC,oBAAmB;AAAA,MACjC;AAAA,IACF;AAAA,IACA,UAAU;AAAA,MACR;AAAA,QACE,MAAM;AAAA,QACN,OAAO,QAAQ;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AACF;AAWA,SAAS,gBACP,gBACA,aACA,eACQ;AAER,QAAM,WAAW,eAAe,KAAK,WAAW;AAEhD,MAAI,UAAU;AACZ,WAAO;AAAA,EACT;AAGA,SAAO,eAAe,KAAK,GAAG,yBAAyB,IAAI,aAAa,EAAE;AAC5E;AASA,SAAS,mBACP,cACA,oBACA,kBACQ;AAER,QAAM,aAAa,KAAK,IAAI,kBAAkB,YAAY;AAE1D,qBAAmB,YAAY,KAAK;AAGpC,WAAS,aAAa,eAAe,GAAG,aAAa,mBAAmB,QAAQ,cAAc,GAAG;AAC/F,uBAAmB,UAAU,IAAI;AAAA,EACnC;AAGA,WAAS,aAAa,YAAY,aAAa,cAAc,cAAc,GAAG;AAC5E,QAAI,mBAAmB,UAAU,MAAM,GAAG;AACxC,yBAAmB,UAAU,IAAI;AAAA,IACnC;AAAA,EACF;AAEA,SAAO,mBAAmB,MAAM,YAAY,eAAe,CAAC,EAAE,KAAK,GAAG;AACxE;AAQA,SAAS,0BAA0B,aAAuC;AAExE,QAAM,oBACJ,uBAAuB,WAAW,YAAY,QAAQ,IAAI,6BAA6B,EAAE,IACrF,cACA,YAAY,cAA2B,IAAI,6BAA6B,EAAE;AAChF,MAAI,CAAC,mBAAmB;AACtB,WAAO,CAAC;AAAA,EACV;AAGA,QAAM,kBAAkB,MAAM;AAAA,IAC5B,kBAAkB;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAGA,QAAM,cAA4B,CAAC;AAEnC,QAAM,qBAAqB,MAAM,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;AAE5D,MAAI;AAEJ,aAAW,kBAAkB,iBAAiB;AAE5C,UAAM,eAAe,SAAS,eAAe,QAAQ,MAAM,CAAC,GAAG,EAAE;AACjE,QAAI,CAAC,OAAO,SAAS,YAAY,GAAG;AAClC;AAAA,IACF;AAGA,UAAM,cAAc,eAAe,aAAa,KAAK,KAAK;AAE1D,UAAM,YAAY,eAAe;AAEjC,UAAM,eAAe;AAAA,MACnB;AAAA,MACA;AAAA,MACA,oBAAoB;AAAA,IACtB;AACA,uBAAmB,oBAAoB;AAEvC,gBAAY,KAAK;AAAA,MACf,OAAO;AAAA,MACP,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,MAAM,eAAe;AAAA,IACvB,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAUA,SAAS,oBAAoB,eAAyB,cAA0C;AAC9F,QAAM,aAAa,cAAc,cAAc,KAAK;AACpD,aAAW,aAAa,cAAc,cAAc;AACpD,aAAW,YAAYL;AACvB,aAAW,YAAY,qBAAqB,eAAe,cAAc,KAAK,CAAC;AAC/E,SAAO;AACT;AAUA,SAAS,qBACP,eACA,UACA,UACkB;AAClB,QAAM,cAAc,cAAc,cAAc,IAAI;AACpD,cAAY,YAAY,WACpB,GAAGC,oBAAmB,IAAIC,2BAA0B,KACpDD;AAEJ,aAAW,WAAW,UAAU;AAC9B,gBAAY,YAAY,yBAAyB,eAAe,OAAO,CAAC;AAAA,EAC1E;AAEA,SAAO;AACT;AAYA,SAAS,yBAAyB,eAAyB,SAAqC;AAE9F,QAAM,cAAc,QAAQ,SAAS,SAAS;AAE9C,QAAM,cAAc,cAAc,cAAc,IAAI;AACpD,cAAY,YAAY,wBAAwB,QAAQ,OAAO,WAAW,EAAE,KAAK,GAAG;AACpF,cAAY,QAAQ,WAAW,QAAQ;AAGvC,MAAI,aAAa;AACf,UAAM,gBAAgB,cAAc,cAAc,QAAQ;AAC1D,kBAAc,OAAO;AACrB,kBAAc,YAAYG;AAC1B,kBAAc,aAAa,iBAAiB,MAAM;AAClD,kBAAc,aAAa,cAAc,qBAAqB;AAC9D,gBAAY,YAAY,aAAa;AAAA,EACvC;AAGA,QAAM,cAAc,cAAc,cAAc,GAAG;AACnD,cAAY,YAAYC;AACxB,cAAY,OAAO,IAAI,QAAQ,EAAE;AACjC,cAAY,cAAc,QAAQ;AAClC,cAAY,YAAY,WAAW;AAGnC,MAAI,aAAa;AACf,gBAAY,YAAY,qBAAqB,eAAe,QAAQ,UAAU,IAAI,CAAC;AAAA,EACrF;AAEA,SAAO;AACT;AAYA,SAAS,WACP,aACA,iBACM;AAEN,QAAM,iBAAiB,YAAY;AAAA,IACjC,UAAUD,sBAAqB;AAAA,EACjC;AAEA,iBAAe,QAAQ,CAAC,kBAAkB;AACxC,QAAI,cAAc,QAAQ,4BAA4B,MAAM,QAAQ;AAClE;AAAA,IACF;AACA,kBAAc,QAAQ,4BAA4B,IAAI;AAEtD,kBAAc,iBAAiB,SAAS,MAAM;AAE5C,YAAM,cAAc,cAAc,QAAuB,IAAID,sBAAqB,EAAE;AACpF,UAAI,CAAC,aAAa;AAChB;AAAA,MACF;AAEA,YAAM,gBAAgB,CAAC,YAAY,UAAU,SAAS,6BAA6B;AACnF,kBAAY,UAAU,OAAO,+BAA+B,aAAa;AAEzE,oBAAc,aAAa,iBAAiB,OAAO,CAAC,aAAa,CAAC;AAAA,IACpE,CAAC;AAAA,EACH,CAAC;AAGD,QAAM,eAAe,YAAY;AAAA,IAC/B,KAAKE,oBAAmB;AAAA,EAC1B;AAEA,eAAa,QAAQ,CAAC,gBAAgB;AACpC,QAAI,YAAY,QAAQ,0BAA0B,MAAM,QAAQ;AAC9D;AAAA,IACF;AACA,gBAAY,QAAQ,0BAA0B,IAAI;AAElD,gBAAY,iBAAiB,SAAS,CAAC,UAAU;AAG/C,YAAM,eAAe;AAErB,YAAM,gBAAgB,YAAY;AAElC,YAAM,eAAe,YAAY,aAAa,MAAM,KAAK,IAAI,MAAM,CAAC;AACpE,UAAI,CAAC,aAAa;AAChB;AAAA,MACF;AAEA,UAAI,gBAAgB,cAAc,eAAe,WAAW;AAC5D,UAAI,CAAC,eAAe;AAClB,YAAI;AACF,0BAAgB,cAAc,eAAe,mBAAmB,WAAW,CAAC;AAAA,QAC9E,QAAQ;AACN,0BAAgB;AAAA,QAClB;AAAA,MACF;AACA,UAAI,eAAe;AACjB,wBAAgB,aAAa;AAAA,MAC/B;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH;;;AClpBA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,iCAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,uBAAAC;AAAA,OACK;AAYP,IAAM,wBAAiE;AAAA,EACrE,EAAE,OAAO,OAAO,OAAO,QAAQ;AAAA,EAC/B,EAAE,OAAO,OAAO,OAAO,QAAQ;AAAA,EAC/B,EAAE,OAAO,QAAQ,OAAO,SAAS;AAAA,EACjC,EAAE,OAAO,QAAQ,OAAO,OAAO;AACjC;AAGA,IAAM,wBAAwB;AAM9B,SAAS,mBAA2B;AAClC,MAAI;AACF,WAAO,aAAa,QAAQ,yBAAyB,KAAK;AAAA,EAC5D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,SAAS,iBAAiB,OAAqB;AAC7C,MAAI;AACF,iBAAa,QAAQ,2BAA2B,KAAK;AAAA,EACvD,QAAQ;AAAA,EAER;AACF;AAOA,SAAS,kBAAkB,eAAyB,OAAqB;AACvE,gBAAc,gBAAgB,MAAM,YAAY,6BAA6B,KAAK;AACpF;AASA,SAAS,qBACP,eACA,WACA,YACmB;AACnB,QAAM,MAAM,cAAc,cAAc,QAAQ;AAChD,MAAI,OAAO;AACX,MAAI,YAAY;AAChB,MAAI,aAAa,cAAc,SAAS;AACxC,MAAI,YAAY;AAChB,SAAO;AACT;AAKA,IAAM,uBACJ;AASF,IAAM,wBACJ;AASF,IAAM,4BACJ;AAQF,IAAM,yBACJ;AAQF,IAAM,yBACJ;AAWF,IAAM,uBAAuB,oBAAI,QAA6B;AAW9D,SAAS,iBACP,eACA,WACA,iBACM;AAEN,uBAAqB,IAAI,SAAS,IAAI;AACtC,uBAAqB,OAAO,SAAS;AAGrC,QAAM,oBAAuC,CAAC;AAG9C,YAAU,cAAc,aAAa,4BAA4B,EAAE,GAAG,OAAO;AAC7E,YAAU,cAAc,aAAa,sCAAsC,EAAE,GAAG,OAAO;AAMvF,YAAU,UAAU,IAAI,6BAA6B;AAGrD,MAAI,cAAc,UAAU;AAAA,IAC1B,aAAa,iCAAiC;AAAA,EAChD;AACA,MAAI,CAAC,aAAa;AAChB,kBAAc,cAAc,cAAc,KAAK;AAC/C,gBAAY,YAAY;AACxB,cAAU,YAAY,WAAW;AAAA,EACnC;AAGA,MAAI,gBAAgB,YAAY;AAAA,IAC9B,aAAa,mCAAmC;AAAA,EAClD;AACA,MAAI,CAAC,eAAe;AAClB,oBAAgB,cAAc,cAAc,KAAK;AAEjD,kBAAc,YAAY,GAAG,mCAAmC,IAAI,mCAAmC;AACvG,gBAAY,YAAY,aAAa;AAAA,EACvC;AAIA,QAAM,uBAAuB;AAC7B,QAAM,qBAAqB;AAC3B,GAAC,GAAG,MAAM,KAAK,UAAU,QAAQ,GAAG,GAAG,MAAM,KAAK,YAAY,QAAQ,CAAC,EAAE,QAAQ,CAAC,UAAU;AAC1F,QACE,UAAU,sBACV,UAAU,wBACV,MAAM,UAAU,SAAS,4BAA4B,KACrD,MAAM,UAAU,SAAS,sCAAsC,GAC/D;AACA;AAAA,IACF;AACA,yBAAqB,YAAY,KAAK;AAAA,EACxC,CAAC;AAED,QAAM,cAAc,cAAc;AAGlC,QAAM,UAAU,cAAc,cAAc,KAAK;AACjD,UAAQ,YAAY;AAIpB,QAAM,WAAW,cAAc,cAAc,KAAK;AAElD,WAAS,YAAY,GAAG,sCAAsC,IAAI,mCAAmC;AAErG,QAAM,WAAW,cAAc,cAAc,KAAK;AAClD,WAAS,YAAY;AAGrB,QAAM,eAAe,cAAc,cAAc,MAAM;AACvD,eAAa,cAAc;AAC3B,WAAS,YAAY,YAAY;AAGjC,QAAM,cAAc,cAAc,cAAc,QAAQ;AACxD,cAAY,OAAO;AACnB,cAAY,YAAY;AACxB,cAAY,aAAa,cAAc,0BAAM;AAC7C,cAAY,YACV;AAOF,QAAM,aAAa,CAAC,SAAwB;AAC1C,aAAS,UAAU,OAAO,WAAW,IAAI;AAAA,EAC3C;AAEA,cAAY,iBAAiB,SAAS,CAAC,MAAM;AAC3C,MAAE,gBAAgB;AAClB,eAAW,KAAK;AAAA,EAClB,CAAC;AACD,WAAS,YAAY,WAAW;AAEhC,WAAS,YAAY,QAAQ;AAG7B,QAAM,cAAc,0BAA0B,SAAS;AACvD,MAAI,YAAY,WAAW,GAAG;AAC5B,UAAM,eAAe,cAAc,cAAc,GAAG;AACpD,iBAAa,YAAY;AACzB,iBAAa,cAAc;AAC3B,aAAS,YAAY,YAAY;AAAA,EACnC,OAAO;AACL,UAAM,UAAU,cAAc,WAAW;AAGzC,UAAM,gBAAgB,oBAAoB,eAAe,OAAO;AAChE,aAAS,YAAY,aAAa;AAClC,eAAW,eAAe,eAAe;AAAA,EAC3C;AAIA,QAAM,SAAS,qBAAqB,eAAe,gBAAM,oBAAoB;AAI7E,QAAM,UAAU,qBAAqB,eAAe,gBAAM,qBAAqB;AAC/E,UAAQ,UAAU,IAAI,GAAG,gCAAgC,QAAQ;AACjE,UAAQ,aAAa,iBAAiB,MAAM;AAC5C,UAAQ,aAAa,iBAAiB,OAAO;AAI7C,QAAM,OAAO,cAAc,cAAc,KAAK;AAC9C,OAAK,YAAY;AACjB,OAAK,aAAa,QAAQ,MAAM;AAIhC,MAAI,aAAa,sBAAsB,UAAU,CAAC,MAAM,EAAE,UAAU,iBAAiB,CAAC;AACtF,MAAI,eAAe,IAAI;AACrB,iBAAa,sBAAsB,UAAU,CAAC,MAAM,EAAE,UAAU,qBAAqB;AAAA,EACvF;AAGA,QAAM,aAAa,cAAc,cAAc,KAAK;AACpD,aAAW,YAAY;AAGvB,QAAM,eAAe,cAAc,cAAc,QAAQ;AACzD,eAAa,OAAO;AACpB,eAAa,YAAY,GAAG,sCAAsC,IAAI,sCAAsC;AAC5G,eAAa,aAAa,QAAQ,UAAU;AAC5C,eAAa,aAAa,iBAAiB,SAAS;AACpD,eAAa,aAAa,iBAAiB,OAAO;AAGlD,QAAM,oBAAoB,cAAc,cAAc,MAAM;AAC5D,oBAAkB,YAAY;AAC9B,oBAAkB,cAAc,sBAAsB,UAAU,EAAE;AAElE,eAAa,YAAY,GAAG,sBAAsB,gBAAgB,8CAA8C;AAChH,eAAa,YAAY,iBAAiB;AAC1C,eAAa;AAAA,IACX;AAAA,IACA,eAAe,gDAAgD;AAAA,EAGjE;AAEA,aAAW,YAAY,YAAY;AAGnC,QAAM,eAAe,cAAc,cAAc,KAAK;AACtD,eAAa,YAAY;AACzB,eAAa,aAAa,QAAQ,SAAS;AAG3C,QAAM,gBAAqC,CAAC;AAC5C,wBAAsB,QAAQ,CAAC,QAAQ,QAAQ;AAE7C,UAAM,UAAU,cAAc,cAAc,QAAQ;AACpD,YAAQ,OAAO;AACf,YAAQ,YAAY;AACpB,YAAQ,aAAa,QAAQ,QAAQ;AACrC,YAAQ,aAAa,iBAAiB,OAAO,QAAQ,UAAU,CAAC;AAEhE,YAAQ,YACN,gBAAgB,+CAA+C,wOAIxC,gDAAgD,KAAK,OAAO,KAAK;AAC1F,YAAQ,iBAAiB,SAAS,MAAM;AACtC,mBAAa;AACb,wBAAkB,eAAe,OAAO,KAAK;AAC7C,uBAAiB,OAAO,KAAK;AAC7B,wBAAkB,cAAc,OAAO;AACvC,oBAAc,QAAQ,CAAC,MAAM,MAAM;AACjC,aAAK,aAAa,iBAAiB,OAAO,MAAM,GAAG,CAAC;AAAA,MACtD,CAAC;AAED,uBAAiB;AAAA,IACnB,CAAC;AACD,iBAAa,YAAY,OAAO;AAChC,kBAAc,KAAK,OAAO;AAAA,EAC5B,CAAC;AACD,aAAW,YAAY,YAAY;AAGnC,QAAM,mBAAmB,MAAY;AACnC,eAAW,UAAU,OAAO,SAAS;AACrC,iBAAa,aAAa,iBAAiB,OAAO;AAAA,EACpD;AAEA,eAAa,iBAAiB,SAAS,MAAM;AAC3C,UAAM,SAAS,WAAW,UAAU,OAAO,SAAS;AACpD,iBAAa,aAAa,iBAAiB,OAAO,MAAM,CAAC;AAAA,EAC3D,CAAC;AAGD,QAAM,gBAAgB,MAAY;AAChC,SAAK,UAAU,OAAO,SAAS;AAC/B,YAAQ,aAAa,iBAAiB,OAAO;AAC7C,qBAAiB;AAAA,EACnB;AAIA,QAAM,cAAc,cAAc,cAAc,QAAQ;AACxD,cAAY,OAAO;AACnB,cAAY,YAAY;AACxB,cAAY,aAAa,QAAQ,UAAU;AAC3C,cAAY,YAAY,GAAG,yBAAyB;AACpD,cAAY,iBAAiB,SAAS,MAAM;AAG1C,mBAAe,SAAS,EAAE,KAAK,GAAG,UAAU,SAAS,CAAC;AACtD,iBAAa,SAAS,EAAE,KAAK,GAAG,UAAU,SAAS,CAAC;AACpD,kBAAc;AAAA,EAChB,CAAC;AAOD,QAAM,YAAY,cAAc,cAAc,GAAG;AACjD,YAAU,OAAO;AACjB,YAAU,SAAS;AAEnB,YAAU,MAAM;AAChB,YAAU,YAAY;AACtB,YAAU,aAAa,QAAQ,UAAU;AACzC,YAAU,YAAY,GAAG,sBAAsB;AAC/C,YAAU,iBAAiB,SAAS,MAAM;AACxC,kBAAc;AAAA,EAChB,CAAC;AAGD,OAAK,YAAY,WAAW;AAC5B,OAAK,YAAY,UAAU;AAC3B,OAAK,YAAY,SAAS;AAE1B,SAAO,iBAAiB,SAAS,CAAC,MAAM;AACtC,MAAE,gBAAgB;AAElB,UAAM,UAAU,SAAS,UAAU,SAAS,SAAS;AACrD,eAAW,CAAC,OAAO;AAEnB,QAAI,CAAC,SAAS;AACZ,oBAAc;AAAA,IAChB;AAAA,EACF,CAAC;AAED,UAAQ,iBAAiB,SAAS,CAAC,MAAM;AACvC,MAAE,gBAAgB;AAClB,UAAM,SAAS,KAAK,UAAU,OAAO,SAAS;AAC9C,YAAQ,aAAa,iBAAiB,OAAO,MAAM,CAAC;AAAA,EACtD,CAAC;AAKD,QAAM,sBAAsB,CAAC,MAAwB;AACnD,UAAM,SAAS,EAAE;AACjB,QAAI,CAAC,KAAK,SAAS,MAAM,KAAK,WAAW,SAAS;AAChD,oBAAc;AAAA,IAChB;AAAA,EACF;AACA,gBAAc,iBAAiB,SAAS,mBAAmB;AAC3D,oBAAkB,KAAK,MAAM;AAC3B,kBAAc,oBAAoB,SAAS,mBAAmB;AAAA,EAChE,CAAC;AAMD,QAAM,oBAAoB,UAAU,QAAQ,IAAIC,8BAA6B,EAAE,IAC1E,YACD,UAAU,cAA2B,IAAIA,8BAA6B,EAAE;AAE5E,QAAM,kBAAkB,oBACpB,MAAM;AAAA,IACJ,kBAAkB;AAAA,MAChB;AAAA,IACF;AAAA,EACF,IACA,CAAC;AAEL,MAAI,gBAAgB,SAAS,GAAG;AAE9B,UAAM,wBAAwB;AAG9B,UAAM,iBAAiB,cAAc,cAAc,KAAK;AACxD,mBAAe,YAAY;AAE3B,mBAAe,aAAa,QAAQ,QAAQ;AAC5C,mBAAe,WAAW;AAG1B,UAAM,qBAAqB,cAAc,cAAc,MAAM;AAC7D,uBAAmB,YAAY;AAC/B,mBAAe,YAAY,kBAAkB;AAG7C,QAAI,kBAAkB;AAEtB,QAAI,uBAA2C,gBAAgB,CAAC;AAMhE,UAAM,mBAAmB,oBAAI,IAA2C;AACxE,aACG,iBAAoC,KAAKC,oBAAmB,EAAE,EAC9D,QAAQ,CAAC,gBAAgB;AAExB,YAAM,eAAe,YAAY,aAAa,MAAM,KAAK,IAAI,MAAM,CAAC;AACpE,UAAI,CAAC,aAAa;AAChB;AAAA,MACF;AAEA,UAAI,gBAAgB,cAAc,eAAe,WAAW;AAC5D,UAAI,CAAC,eAAe;AAClB,YAAI;AACF,0BAAgB,cAAc,eAAe,mBAAmB,WAAW,CAAC;AAAA,QAC9E,QAAQ;AACN,0BAAgB;AAAA,QAClB;AAAA,MACF;AACA,UAAI,yBAAyB,oBAAoB;AAC/C,yBAAiB,IAAI,eAAe,WAAW;AAAA,MACjD;AAAA,IACF,CAAC;AAGH,QAAI;AAOJ,UAAM,mBAAmB,CAAC,mBAA6C;AAErE,YAAM,WAAW,iBAAiB,IAAI,cAAc;AACpD,UAAI,aAAa,eAAe;AAC9B;AAAA,MACF;AACA,UAAI,eAAe;AACjB,sBAAc,UAAU,OAAO,0BAA0B;AACzD,sBAAc,gBAAgB,cAAc;AAAA,MAC9C;AACA,UAAI,UAAU;AACZ,iBAAS,UAAU,IAAI,0BAA0B;AAEjD,iBAAS,aAAa,gBAAgB,UAAU;AAAA,MAClD;AACA,sBAAgB;AAAA,IAClB;AAMA,UAAM,uBAAuB,MAAY;AAEvC,UAAI,gBAAoC,gBAAgB,CAAC;AACzD,iBAAW,kBAAkB,iBAAiB;AAC5C,YAAI,eAAe,sBAAsB,EAAE,OAAO,uBAAuB;AACvE,0BAAgB;AAAA,QAClB,OAAO;AACL;AAAA,QACF;AAAA,MACF;AAEA,6BAAuB;AAEvB,uBAAiB,aAAa;AAE9B,YAAM,cAAc,cAAc,aAAa,KAAK,KAAK;AACzD,UAAI,gBAAgB,iBAAiB;AACnC;AAAA,MACF;AACA,wBAAkB;AAClB,yBAAmB,cAAc;AAEjC,qBAAe,aAAa,cAAc,WAAW;AAAA,IACvD;AAGA,UAAM,sBAAsB,MAAY;AACtC,2BAAqB,eAAe,EAAE,UAAU,UAAU,OAAO,QAAQ,CAAC;AAAA,IAC5E;AACA,mBAAe,iBAAiB,SAAS,mBAAmB;AAC5D,mBAAe,iBAAiB,WAAW,CAAC,UAAU;AAEpD,UAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,KAAK;AAC9C,cAAM,eAAe;AACrB,4BAAoB;AAAA,MACtB;AAAA,IACF,CAAC;AAGD,QAAI,cAAc;AAElB,UAAM,mBAAmB,MAAY;AACnC,UAAI,aAAa;AACf;AAAA,MACF;AACA,oBACE,aAAa,sBAAsB,MAAM;AACvC,sBAAc;AACd,6BAAqB;AAAA,MACvB,CAAC,KAAK;AAAA,IACV;AAIA,iBAAa,iBAAiB,UAAU,kBAAkB;AAAA,MACxD,SAAS;AAAA,MACT,SAAS;AAAA,IACX,CAAC;AACD,iBAAa,iBAAiB,UAAU,kBAAkB,EAAE,SAAS,KAAK,CAAC;AAE3E,yBAAqB;AAGrB,sBAAkB,KAAK,MAAM;AAC3B,UAAI,aAAa;AACf,qBAAa,qBAAqB,WAAW;AAAA,MAC/C;AACA,mBAAa,oBAAoB,UAAU,kBAAkB,EAAE,SAAS,KAAK,CAAC;AAC9E,mBAAa,oBAAoB,UAAU,gBAAgB;AAAA,IAC7D,CAAC;AAGD,YAAQ,YAAY,cAAc;AAAA,EACpC;AAEA,UAAQ,YAAY,MAAM;AAC1B,UAAQ,YAAY,OAAO;AAE3B,UAAQ,YAAY,IAAI;AAIxB,YAAU,YAAY,OAAO;AAC7B,YAAU,aAAa,UAAU,WAAW;AAG5C,uBAAqB,IAAI,WAAW,MAAM;AACxC,sBAAkB,QAAQ,CAAC,qBAAqB;AAC9C,uBAAiB;AAAA,IACnB,CAAC;AAAA,EACH,CAAC;AACH;;;AlBxmBA,eAAsB,eAAe,cAAuC;AAI1E,QAAM,YAAY,QAAQ,EACvB,IAAI,WAAW,EACf,IAAI,SAAS,EACb,IAAI,iBAAiB,EACrB,IAAI,yBAAyB,EAC7B,IAAI,mBAAmB,EACvB,IAAI,qBAAqB,EACzB,IAAI,qBAAqB,EACzB,IAAI,kBAAkB,EACtB,IAAI,gBAAgB,EACpB,IAAI,cAAc,EAAE,oBAAoB,KAAK,CAAC,EAC9C,IAAI,SAAS,EACb,IAAI,gBAAgB,EACpB,IAAI,kBAAkB,EACtB,IAAI,gBAAgB,8BAA8B,CAAC,EACnD,IAAI,eAAe;AAGtB,QAAM,eAAe,OAAO,MAAM,UAAU,QAAQ,YAAY,CAAC;AAGjE,QAAM,gBAAgB,0BAA0B,YAAY;AAG5D,SAAO,4BAA4B,aAAa;AAClD;AAqBA,SAAS,uBAAuB,eAAkC;AAChE,gBAAc,eAAe,EAAE,UAAU,UAAU,OAAO,QAAQ,CAAC;AACrE;AASO,SAAS,gBACd,aACA,UAAsC,CAAC,GACjC;AACN,wBAAsB,WAAW;AACjC,wBAAsB,WAAW;AAEjC,uBAAqB,WAAW;AAChC,oBAAkB,WAAW;AAE7B,aAAW,aAAa,QAAQ,mBAAmB,sBAAsB;AAC3E;AAaO,SAAS,qBACd,WACA,UAAsC,CAAC,GACjC;AAEN,QAAM,gBAAgB,UAAU;AAChC,MAAI,CAAC,eAAe;AAClB;AAAA,EACF;AACA,MAAI,OAAO,cAAc,aAAa,aAAa,YAAY;AAC7D;AAAA,EACF;AAEA,oBAAkB,eAAe,iBAAiB,CAAC;AACnD,mBAAiB,eAAe,WAAW,QAAQ,mBAAmB,sBAAsB;AAC9F;","names":["SOURCE_LINE_DATA_ATTRIBUTE","SOURCE_LINE_DATA_ATTRIBUTE","SOURCE_LINE_DATA_ATTRIBUTE","SKIP","visit","IMAGE_CAPTION_CLASS_NAME","IMAGE_ELEMENT_CLASS_NAME","IMAGE_FALLBACK_CLASS_NAME","IMAGE_FALLBACK_ICON_CLASS_NAME","IMAGE_FALLBACK_SOURCE_CLASS_NAME","IMAGE_FALLBACK_TEXT_CLASS_NAME","IMAGE_FIGURE_CLASS_NAME","IMAGE_FRAME_CLASS_NAME","VIDEO_ELEMENT_CLASS_NAME","VIDEO_FALLBACK_CLASS_NAME","VIDEO_FALLBACK_ICON_CLASS_NAME","VIDEO_FALLBACK_SOURCE_CLASS_NAME","VIDEO_FALLBACK_TEXT_CLASS_NAME","VIDEO_FIGURE_CLASS_NAME","VIDEO_FRAME_CLASS_NAME","IMAGE_FRAME_CLASS_NAME","IMAGE_FALLBACK_CLASS_NAME","IMAGE_FALLBACK_ICON_CLASS_NAME","IMAGE_FALLBACK_TEXT_CLASS_NAME","IMAGE_FALLBACK_SOURCE_CLASS_NAME","VIDEO_FRAME_CLASS_NAME","VIDEO_FALLBACK_CLASS_NAME","VIDEO_FALLBACK_ICON_CLASS_NAME","VIDEO_FALLBACK_TEXT_CLASS_NAME","VIDEO_FALLBACK_SOURCE_CLASS_NAME","IMAGE_FIGURE_CLASS_NAME","VIDEO_FIGURE_CLASS_NAME","IMAGE_CAPTION_CLASS_NAME","IMAGE_ELEMENT_CLASS_NAME","VIDEO_ELEMENT_CLASS_NAME","visit","visit","DETAILS_CLASS_NAME","FRAME_CLASS_NAME","TOC_CLASS_NAME","classnames","visit","FRAME_CLASS_NAME","FRONTMATTER_CHROME_CLASS_NAME","FRONTMATTER_CLASS_NAME","FRONTMATTER_LABEL_CLASS_NAME","FRONTMATTER_LIST_CLASS_NAME","FRONTMATTER_LIST_NESTED_CLASS_NAME","SKIP","visit","FRAME_CLASS_NAME","HEADING_MARK_CLASS_NAME","TOC_CLASS_NAME","TOC_ITEM_CLASS_PREFIX","TOC_LINK_CLASS_NAME","TOC_LIST_CLASS_NAME","TOC_LIST_NESTED_CLASS_NAME","TOC_NAV_CLASS_NAME","TOC_SUMMARY_CLASS_NAME","TOC_TOGGLE_CLASS_NAME","visit","visit","HEADING_MARK_CLASS_NAME","TOC_CLASS_NAME","FRAME_CLASS_NAME","TOC_SUMMARY_CLASS_NAME","TOC_NAV_CLASS_NAME","TOC_LIST_CLASS_NAME","TOC_LIST_NESTED_CLASS_NAME","TOC_ITEM_CLASS_PREFIX","TOC_TOGGLE_CLASS_NAME","TOC_LINK_CLASS_NAME","SCRIBDOWN_MARKDOWN_CLASS_NAME","TOC_LINK_CLASS_NAME","SCRIBDOWN_MARKDOWN_CLASS_NAME","TOC_LINK_CLASS_NAME"]}