Skip to content

fix(dashboard): restore README dialog anchor navigation#6083

Merged
Soulter merged 1 commit intoAstrBotDevs:masterfrom
stablegenius49:pr-factory/issue-5914-readme-dialog-anchor
Mar 12, 2026
Merged

fix(dashboard): restore README dialog anchor navigation#6083
Soulter merged 1 commit intoAstrBotDevs:masterfrom
stablegenius49:pr-factory/issue-5914-readme-dialog-anchor

Conversation

@stablegenius49
Copy link
Contributor

@stablegenius49 stablegenius49 commented Mar 12, 2026

Summary

  • generate stable heading ids for rendered README headings in ReadmeDialog
  • intercept in-dialog #anchor clicks and smoothly scroll the dialog container to the matching section
  • add a small heading scroll margin so target sections aren't jammed against the top edge

Verification

  • corepack pnpm run typecheck
  • corepack pnpm run build

Closes #5914.

Summary by Sourcery

在 README 对话框中恢复对话框内锚点导航和标题定位行为。

Bug Fixes:

  • 确保对话框中的 README 标题获得稳定且去重的 ID,以支持锚点链接。
  • 处理对话框内的哈希链接点击事件,使 README 对话框容器平滑滚动到目标区域,而不是导航整个页面。

Enhancements:

  • 为对话框中的 README 标题添加滚动边距,使滚动到的部分不会紧贴顶部边缘。
Original summary in English

Summary by Sourcery

Restore in-dialog anchor navigation and heading targeting behavior in the README dialog.

Bug Fixes:

  • Ensure README headings in the dialog receive stable, de-duplicated IDs to support anchor links.
  • Handle in-dialog hash link clicks by smoothly scrolling the README dialog container to the targeted section instead of navigating the page.

Enhancements:

  • Add a scroll margin to README headings in the dialog so scrolled-to sections are not flush against the top edge.

@auto-assign auto-assign bot requested review from Fridemn and LIghtJUNction March 12, 2026 00:49
@dosubot dosubot bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Mar 12, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses an issue where anchor navigation within the README dialog was not functioning correctly. It introduces robust heading ID generation, intercepts internal anchor clicks to enable smooth scrolling to the target sections, and improves the user experience by adding a scroll margin to ensure headings are fully visible when navigated to.

Highlights

  • Heading ID Generation: Implemented a slugifyHeading function to generate stable and unique IDs for all headings (h1-h6) within the rendered README content, ensuring consistent anchor targets.
  • Anchor Navigation Interception: Added logic to intercept clicks on in-dialog anchor links (#anchor) and programmatically scroll the dialog container smoothly to the corresponding section.
  • Scroll Margin for Headings: Introduced a scroll-margin-top CSS property to headings, providing a small offset when scrolling to an anchor, preventing the target section from being obscured by the top edge of the scroll container.
Changelog
  • dashboard/src/components/shared/ReadmeDialog.vue
    • Added a new scrollContainer ref to manage the scrollable area of the dialog.
    • Introduced a slugifyHeading utility function to create URL-friendly and unique IDs for HTML headings.
    • Modified the renderedHtml computed property to iterate through all headings (h1-h6) and assign stable, unique IDs using the new slugifyHeading function.
    • Updated the handleContainerClick method to detect clicks on internal anchor links and perform smooth scrolling to the corresponding element within the dialog.
    • Applied ref="scrollContainer" to the v-card-text component to link it with the new scroll container ref.
    • Added scroll-margin-top: 12px; to heading styles to prevent content from being hidden behind the top edge during anchor navigation.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@dosubot dosubot bot added the area:webui The bug / feature is about webui(dashboard) of astrbot. label Mar 12, 2026
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题,并给出了一些高层次的反馈:

  • 在直接使用 CSS.escape 的情况下,如果运行环境中没有定义它,会抛出异常;建议在调用 querySelector 之前先做特性检测,并提供一个简单的回退逻辑(例如:const safeId = CSS?.escape ? CSS.escape(targetId) : targetId;)。
  • decodeURIComponent(rawHref.slice(1)) 在遇到格式错误的编码时会抛出异常;相比之下,把它包在 try/catch 中,在锚点值非法时忽略或提前退出,可能会比让点击处理函数报错更安全。
给 AI Agent 的提示
Please address the comments from this code review:

## Overall Comments
- Using `CSS.escape` directly can throw in environments where it's not defined; consider guarding with a feature check and a simple fallback (e.g. `const safeId = CSS?.escape ? CSS.escape(targetId) : targetId;`) before calling `querySelector`.
- `decodeURIComponent(rawHref.slice(1))` will throw on malformed encodings; it may be safer to wrap this in a try/catch and ignore/bail out on invalid anchor values instead of letting the click handler error.

## Individual Comments

### Comment 1
<location path="dashboard/src/components/shared/ReadmeDialog.vue" line_range="302-303" />
<code_context>
+  const anchor = event.target.closest('a[href^="#"]');
+  if (!anchor) return;
+
+  const rawHref = anchor.getAttribute("href");
+  const targetId = rawHref ? decodeURIComponent(rawHref.slice(1)) : "";
+  if (!targetId) return;
+
</code_context>
<issue_to_address>
**issue (bug_risk):** decodeURIComponent on the fragment can throw on malformed encodings, which would break the click handler.

This assumes the fragment is always valid percent-encoding; an invalid `%` sequence will cause `decodeURIComponent` to throw and stop the handler. Consider guarding with a try/catch and falling back to the raw substring:

```js
let targetId = "";
if (rawHref) {
  try {
    targetId = decodeURIComponent(rawHref.slice(1));
  } catch {
    targetId = rawHref.slice(1);
  }
}
```
</issue_to_address>

Sourcery 对开源项目是免费的——如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的评审。
Original comment in English

Hey - I've found 1 issue, and left some high level feedback:

  • Using CSS.escape directly can throw in environments where it's not defined; consider guarding with a feature check and a simple fallback (e.g. const safeId = CSS?.escape ? CSS.escape(targetId) : targetId;) before calling querySelector.
  • decodeURIComponent(rawHref.slice(1)) will throw on malformed encodings; it may be safer to wrap this in a try/catch and ignore/bail out on invalid anchor values instead of letting the click handler error.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Using `CSS.escape` directly can throw in environments where it's not defined; consider guarding with a feature check and a simple fallback (e.g. `const safeId = CSS?.escape ? CSS.escape(targetId) : targetId;`) before calling `querySelector`.
- `decodeURIComponent(rawHref.slice(1))` will throw on malformed encodings; it may be safer to wrap this in a try/catch and ignore/bail out on invalid anchor values instead of letting the click handler error.

## Individual Comments

### Comment 1
<location path="dashboard/src/components/shared/ReadmeDialog.vue" line_range="302-303" />
<code_context>
+  const anchor = event.target.closest('a[href^="#"]');
+  if (!anchor) return;
+
+  const rawHref = anchor.getAttribute("href");
+  const targetId = rawHref ? decodeURIComponent(rawHref.slice(1)) : "";
+  if (!targetId) return;
+
</code_context>
<issue_to_address>
**issue (bug_risk):** decodeURIComponent on the fragment can throw on malformed encodings, which would break the click handler.

This assumes the fragment is always valid percent-encoding; an invalid `%` sequence will cause `decodeURIComponent` to throw and stop the handler. Consider guarding with a try/catch and falling back to the raw substring:

```js
let targetId = "";
if (rawHref) {
  try {
    targetId = decodeURIComponent(rawHref.slice(1));
  } catch {
    targetId = rawHref.slice(1);
  }
}
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +302 to +303
const rawHref = anchor.getAttribute("href");
const targetId = rawHref ? decodeURIComponent(rawHref.slice(1)) : "";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): 在片段(fragment)上调用 decodeURIComponent 时,如果遇到格式错误的编码,会抛出异常,从而导致点击处理函数出错。

这里假设片段总是合法的百分号编码;但一旦出现无效的 % 序列,就会让 decodeURIComponent 抛出异常并中止处理函数。建议用 try/catch 做保护,并在出错时回退到原始子串:

let targetId = "";
if (rawHref) {
  try {
    targetId = decodeURIComponent(rawHref.slice(1));
  } catch {
    targetId = rawHref.slice(1);
  }
}
Original comment in English

issue (bug_risk): decodeURIComponent on the fragment can throw on malformed encodings, which would break the click handler.

This assumes the fragment is always valid percent-encoding; an invalid % sequence will cause decodeURIComponent to throw and stop the handler. Consider guarding with a try/catch and falling back to the raw substring:

let targetId = "";
if (rawHref) {
  try {
    targetId = decodeURIComponent(rawHref.slice(1));
  } catch {
    targetId = rawHref.slice(1);
  }
}

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively restores anchor navigation within the README dialog. It introduces a slugifyHeading function to generate stable IDs for headings, intercepts clicks on anchor links to provide smooth scrolling, and adds a top margin for better visibility of the scrolled-to section. The implementation is solid. I've only suggested a minor improvement to the slug generation logic to make it more robust by trimming leading/trailing hyphens, which is a common practice for creating clean slugs.

Comment on lines +54 to +61
const base = (text || "")
.trim()
.toLowerCase()
.normalize("NFKD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/[^\p{Letter}\p{Number}\s-]/gu, "")
.replace(/\s+/g, "-")
.replace(/-+/g, "-");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current slug generation logic can produce slugs with leading or trailing hyphens (e.g., from a heading like ## - My Title -). While these are valid in HTML5 IDs, it's common practice to trim them for cleaner and more predictable URLs/anchors. I suggest adding a step to remove any leading or trailing hyphens from the generated slug.

  const base = (text || "")
    .trim()
    .toLowerCase()
    .normalize("NFKD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^\p{Letter}\p{Number}\s-]/gu, "")
    .replace(/\s+/g, "-")
    .replace(/-+/g, "-")
    .replace(/^-+|-+$/g, "");

@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Mar 12, 2026
@Soulter Soulter merged commit c0e4f1e into AstrBotDevs:master Mar 12, 2026
1 check passed
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:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] 插件说明文档目录点击无效(ReadmeDialog 内锚点未跳转)

3 participants