-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
fix(webui): optimize markdown rendering and remove redundant code #4415
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+392
−175
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Contributor
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey - 我发现了 3 个问题,并给出了一些整体性的反馈:
- 在模块作用域中使用全局的
DOMPurify.addHook('afterSanitizeAttributes', ...)会影响到应用中所有对 DOMPurify 的使用;如果你只想在这里启用这一行为,建议使用一个独立的 DOMPurify 实例,或者只在本次 sanitize 调用前后注册/注销 hook,而不是全局注册。 - 复制按钮的文案和
title属性被硬编码为中文(复制代码);既然这个组件的其他部分已经使用了useI18n,会更一致的做法是从 i18n 资源中读取这些文案。
给 AI Agent 的提示词
Please address the comments from this code review:
## Overall Comments
- The global `DOMPurify.addHook('afterSanitizeAttributes', ...)` at module scope will affect all DOMPurify usages in the app; if you only want this behavior here, consider using a dedicated DOMPurify instance or registering/unregistering the hook around the sanitize call instead of globally.
- The copy button label and `title` attribute are hardcoded in Chinese (`复制代码`); since the rest of the component uses `useI18n`, it would be more consistent to pull these strings from the i18n resources.
## Individual Comments
### Comment 1
<location> `dashboard/src/components/shared/ReadmeDialog.vue:232-233` </location>
<code_context>
);
+// 监听 renderedHtml 变化,初始化复制按钮
+watch(renderedHtml, () => {
+ nextTick(() => {
+ initCopyButtons();
+ });
</code_context>
<issue_to_address>
**issue (bug_risk):** 每次渲染都会给复制按钮添加监听器但没有清理,这可能导致重复的事件处理和内存泄漏。
当 `renderedHtml` 变化时,`initCopyButtons` 会重新执行,并对所有 `.copy-code-btn` 元素调用 `addEventListener`,这样同一个按钮会积累多个点击处理函数并且保留不必要的引用。为避免这种情况,可以使用事件委托(例如在 `markdownContainer` 上挂一个 click 监听,并检查 `event.target.closest('.copy-code-btn')`),或者确保移除旧的监听器(例如通过 Vue 渲染按钮并使用 `@click`)。
</issue_to_address>
### Comment 2
<location> `dashboard/src/components/shared/ReadmeDialog.vue:251-257` </location>
<code_context>
+}
+
+// 处理复制代码
+function handleCopyCode(event) {
+ const btn = event.currentTarget;
+ const wrapper = btn.closest(".code-block-wrapper");
+ const code = wrapper.querySelector("code");
+
+ if (code) {
+ navigator.clipboard
+ .writeText(code.textContent)
+ .then(() => {
</code_context>
<issue_to_address>
**issue:** 假设 `navigator.clipboard` 总是可用,在不支持或非安全环境下可能会出错。
当 Clipboard API 不可用,或者页面不是通过 HTTPS 提供时,`navigator.clipboard.writeText` 会抛出异常。请添加能力检测(例如 `if (!navigator.clipboard || !navigator.clipboard.writeText)`),并在这条分支中提供合理的降级方案或用户可见的错误提示,而不是只打印到控制台。
</issue_to_address>
### Comment 3
<location> `dashboard/src/components/shared/ReadmeDialog.vue:52` </location>
<code_context>
+
+ return `<div class="code-block-wrapper">
+ ${langLabel}
+ <button class="copy-code-btn" title="复制代码">
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
</code_context>
<issue_to_address>
**suggestion:** 复制按钮的文案被硬编码为中文,而没有使用现有的 i18n 工具。
由于该组件已经在使用 `useI18n`,请从 i18n 文案中获取按钮标题,而不是硬编码 `title="复制代码"`,这样复制按钮就能被本地化,也不会在同一视图中混用不同语言。
建议实现:
```
return `<div class="code-block-wrapper">
${langLabel}
<button class="copy-code-btn" title="${t('readmeDialog.copyCode')}">
```
1. 确保 `useI18n()` 返回的 `t` 能在这段代码中访问,例如在 `<script setup>` 中:
`const { t } = useI18n();`
2. 在各语言的 locale 配置中添加 `readmeDialog.copyCode` 键(例如 `en`、`zh` 等),例如:
- 英文:`"readmeDialog": { "copyCode": "Copy code" }`
- 中文:`"readmeDialog": { "copyCode": "复制代码" }`
3. 如果你的项目对这个对话框使用了不同的命名约定或命名空间,请相应调整键路径(`readmeDialog.copyCode`),以匹配现有的 i18n 结构。
</issue_to_address>请帮我变得更有用!欢迎在每条评论上点击 👍 或 👎,我会根据你的反馈改进代码评审。
Original comment in English
Hey - I've found 3 issues, and left some high level feedback:
- The global
DOMPurify.addHook('afterSanitizeAttributes', ...)at module scope will affect all DOMPurify usages in the app; if you only want this behavior here, consider using a dedicated DOMPurify instance or registering/unregistering the hook around the sanitize call instead of globally. - The copy button label and
titleattribute are hardcoded in Chinese (复制代码); since the rest of the component usesuseI18n, it would be more consistent to pull these strings from the i18n resources.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The global `DOMPurify.addHook('afterSanitizeAttributes', ...)` at module scope will affect all DOMPurify usages in the app; if you only want this behavior here, consider using a dedicated DOMPurify instance or registering/unregistering the hook around the sanitize call instead of globally.
- The copy button label and `title` attribute are hardcoded in Chinese (`复制代码`); since the rest of the component uses `useI18n`, it would be more consistent to pull these strings from the i18n resources.
## Individual Comments
### Comment 1
<location> `dashboard/src/components/shared/ReadmeDialog.vue:232-233` </location>
<code_context>
);
+// 监听 renderedHtml 变化,初始化复制按钮
+watch(renderedHtml, () => {
+ nextTick(() => {
+ initCopyButtons();
+ });
</code_context>
<issue_to_address>
**issue (bug_risk):** Copy button listeners are added on every render without cleanup, which can cause duplicated handlers and leaks.
When `renderedHtml` changes, `initCopyButtons` re-runs and calls `addEventListener` on all `.copy-code-btn` elements, so the same button can accumulate multiple click handlers and leak references. To avoid this, either use event delegation (e.g. a single click listener on `markdownContainer` that checks `event.target.closest('.copy-code-btn')`) or ensure old listeners are removed (e.g. render the buttons via Vue and use `@click`).
</issue_to_address>
### Comment 2
<location> `dashboard/src/components/shared/ReadmeDialog.vue:251-257` </location>
<code_context>
+}
+
+// 处理复制代码
+function handleCopyCode(event) {
+ const btn = event.currentTarget;
+ const wrapper = btn.closest(".code-block-wrapper");
+ const code = wrapper.querySelector("code");
+
+ if (code) {
+ navigator.clipboard
+ .writeText(code.textContent)
+ .then(() => {
</code_context>
<issue_to_address>
**issue:** Assumes `navigator.clipboard` is always available, which can break on unsupported or non-secure contexts.
`navigator.clipboard.writeText` will throw when the Clipboard API is unavailable or the page isn’t served over HTTPS. Add a feature check (e.g. `if (!navigator.clipboard || !navigator.clipboard.writeText)`) and handle that path with a proper fallback or user-facing error instead of just logging to the console.
</issue_to_address>
### Comment 3
<location> `dashboard/src/components/shared/ReadmeDialog.vue:52` </location>
<code_context>
+
+ return `<div class="code-block-wrapper">
+ ${langLabel}
+ <button class="copy-code-btn" title="复制代码">
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+ <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
</code_context>
<issue_to_address>
**suggestion:** The copy button label is hardcoded in Chinese instead of using the existing i18n utilities.
Since this component already uses `useI18n`, please take the button label from the i18n strings instead of hardcoding `title="复制代码"`, so the copy button is localized and doesn’t mix languages within the same view.
Suggested implementation:
```
return `<div class="code-block-wrapper">
${langLabel}
<button class="copy-code-btn" title="${t('readmeDialog.copyCode')}">
```
1. Ensure that `t` from `useI18n()` is in scope where this code runs, e.g. in `<script setup>`:
`const { t } = useI18n();`
2. Add the `readmeDialog.copyCode` key to your locale message files (e.g. `en`, `zh`, etc.), for example:
- English: `"readmeDialog": { "copyCode": "Copy code" }`
- Chinese: `"readmeDialog": { "copyCode": "复制代码" }`
3. If your project uses a different naming convention or namespace for this dialog, adjust the key path (`readmeDialog.copyCode`) accordingly to match your existing i18n structure.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Soulter
approved these changes
Jan 12, 2026
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Labels
area:webui
The bug / feature is about webui(dashboard) of astrbot.
lgtm
This PR has been approved by a maintainer
size:XL
This PR changes 500-999 lines, ignoring generated files.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
本次 PR 旨在重构 Dashboard 的 Markdown 渲染逻辑,以解决现有的渲染兼容性问题并提升安全性。主要解决以下问题:
<details>/<summary>折叠块无法正确显示的问题。DOMPurify进行 HTML 清洗,防止 XSS 攻击;强制所有外部链接添加rel="noopener noreferrer"和target="_blank"。highlight.js实现代码高亮,并添加了带有交互反馈的复制代码按钮。Modifications / 改动点
核心修改文件:
markdown-it替换原有的渲染逻辑。DOMPurify配置安全白名单。highlight.js并自定义代码块渲染器(支持语言标签和复制按钮)。markdown-it,dompurify,highlight.js及其类型定义。@mdit/plugin-katex。实现功能:
Screenshots or Test Results / 运行截图或测试结果
Local CI Status:
npm run build: ✅ Passed (Vue-TSC Type Check OK)Checklist / 检查清单
Summary by Sourcery
将仪表盘 README 弹窗重构为使用自定义的 Markdown-It + DOMPurify + highlight.js 渲染管线,以实现更安全、更丰富的 Markdown 渲染和改进的代码块交互体验。
New Features:
Bug Fixes:
Enhancements:
Build:
Original summary in English
Summary by Sourcery
Refactor the dashboard README dialog to use a custom Markdown-It + DOMPurify + highlight.js pipeline for safer, richer markdown rendering and improved code block UX.
New Features:
Bug Fixes:
Enhancements:
Build: