feat: add shuoshuo micro-posts - #451
Conversation
✅ Deploy Preview for demo-firefly ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Reviewer's Guide通过添加专门的内容集合、时间线页面、侧边栏挂件、共享文本/格式化工具、导航入口以及站点统计集成(同时提供示例内容和 i18n 文案),实现新的 “shuoshuo” 微博客功能。 File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your Experience访问你的 dashboard 以:
Getting HelpOriginal review guide in EnglishReviewer's GuideImplements a new "shuoshuo" micro-post feature by adding a dedicated content collection, timeline page, sidebar widget, shared text/formatting utilities, navigation entry, and site statistics integration, along with demo content and i18n keys. File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey,我这边发现了 3 个问题,并给了一些整体层面的反馈:
shuoshuo.astro里的initShuoshuoCards函数绑定到了多个生命周期事件,但从不清理事件监听器或ResizeObserver,所以在多次导航后可能会不断累积处理函数;建议让它对每张卡片是幂等的,或者添加相应的销毁/清理逻辑。countCjkAndEnglishWords目前只统计\u4e00-\u9fa5范围内的字符,这会遗漏很多 CJK 字符(例如扩展区以及日文汉字);如果你希望支持更广泛的 CJK 内容,建议扩展正则表达式以覆盖完整的 CJK Unicode 区块。getSortedShuoshuo中的排序比较函数始终返回-1或1,从不返回0,这会导致在published日期相同的情况下排序不稳定;在相等的情况下返回0会更正确、更可预测。
给 AI Agent 的提示(Prompt)
请根据这次代码审查中的评论进行修改:
## 整体反馈
- `shuoshuo.astro` 里的 `initShuoshuoCards` 函数绑定到了多个生命周期事件,但从不清理事件监听器或 `ResizeObserver`,所以在多次导航后可能会不断累积处理函数;建议让它对每张卡片是幂等的,或者添加相应的销毁/清理逻辑。
- `countCjkAndEnglishWords` 目前只统计 `\u4e00-\u9fa5` 范围内的字符,这会遗漏很多 CJK 字符(例如扩展区以及日文汉字);如果你希望支持更广泛的 CJK 内容,建议扩展正则表达式以覆盖完整的 CJK Unicode 区块。
- `getSortedShuoshuo` 中的排序比较函数始终返回 `-1` 或 `1`,从不返回 `0`,这会导致在 `published` 日期相同的情况下排序不稳定;在相等的情况下返回 `0` 会更正确、更可预测。
## 逐条评论
### 评论 1
<location path="src/pages/shuoshuo.astro" line_range="87-93" />
<code_context>
+
+ <script>
+ function initShuoshuoCards() {
+ const cards = document.querySelectorAll<HTMLElement>("[data-shuoshuo-card]");
+ for (const card of cards) {
+ const maxHeight = Number(card.getAttribute("data-max-height") || "260");
+ const collapsible = card.querySelector<HTMLElement>("[data-collapsible]");
+ const content = card.querySelector<HTMLElement>("[data-collapsible-content]");
+ const fade = card.querySelector<HTMLElement>("[data-fade]");
+ const toggle = card.querySelector<HTMLButtonElement>("[data-toggle]");
+
+ if (!collapsible || !content || !toggle) continue;
</code_context>
<issue_to_address>
**issue (bug_risk):** 请从内联的浏览器脚本中移除 TypeScript 特有的泛型和类型标注。
由于这个内联的 `<script>` 会在浏览器中作为普通 JavaScript 运行,像 `querySelectorAll<HTMLElement>(...)` 和 `querySelector<HTMLButtonElement>(...)` 这样的 TypeScript 泛型语法是无效的,会导致运行时语法错误。去掉这些泛型即可保持现有行为不变:
```js
const cards = document.querySelectorAll("[data-shuoshuo-card]");
...
const toggle = card.querySelector("[data-toggle]");
```
如果需要类型检查,请把这段逻辑移到由打包工具处理的 `.ts` 文件中,而不要在内联脚本中使用 TS 标注。
</issue_to_address>
### 评论 2
<location path="src/pages/shuoshuo.astro" line_range="86-95" />
<code_context>
+ function initShuoshuoCards() {
</code_context>
<issue_to_address>
**suggestion (bug_risk):** 避免在每次页面加载/内容替换时重复初始化同一批卡片,并重复绑定事件监听器。
`initShuoshuoCards` 会在函数定义后立即运行一次,同时在 `astro:page-load` 和 `swup:contentReplaced` 事件触发时再次运行,每次都会为所有 `[data-shuoshuo-card]` 元素重新绑定点击事件处理器和 `ResizeObserver`。当前的 `data-initialized` 标志只控制初始展开/折叠状态,而并没有表示监听器是否已经绑定。
建议跳过已经初始化过的卡片,例如:
```js
function initShuoshuoCards() {
const cards = document.querySelectorAll('[data-shuoshuo-card]');
for (const card of cards) {
if (card.hasAttribute('data-initialized')) continue;
// existing initialization code...
card.setAttribute('data-initialized', 'true');
}
}
```
这样可以在保持现有行为的前提下,避免在多次导航后累积重复的监听器和观察器。
推荐实现方式:
```
function initShuoshuoCards() {
const cards = document.querySelectorAll<HTMLElement>("[data-shuoshuo-card]");
for (const card of cards) {
// 跳过已经初始化的卡片,避免重复绑定监听器/观察器
if (card.hasAttribute("data-initialized")) continue;
const maxHeight = Number(card.getAttribute("data-max-height") || "260");
const collapsible = card.querySelector<HTMLElement>("[data-collapsible]");
const content = card.querySelector<HTMLElement>("[data-collapsible-content]");
const fade = card.querySelector<HTMLElement>("[data-fade]");
const toggle = card.querySelector<HTMLButtonElement>("[data-toggle]");
if (!collapsible || !content || !toggle) continue;
// 注意:在每张卡片初始化逻辑的末尾,记得标记:
// card.setAttribute("data-initialized", "true");
```
我目前只能看到循环体的开头。要完整实现这个行为:
1. 在完成每张卡片的所有事件监听器绑定、`ResizeObserver` 设置以及初始展开/折叠状态逻辑之后,添加 `card.setAttribute("data-initialized", "true");`。
2. 确保这行代码只在初始化成功时执行(也就是不要放在任何提前 `return` 的分支中),避免在卡片只初始化了一半时就被标记为已初始化。
</issue_to_address>
### 评论 3
<location path="src/components/widget/LatestShuoshuo.astro" line_range="41-43" />
<code_context>
+ slot="title-icon"
+ href={url("/shuoshuo/")}
+ class="btn-plain rounded-lg h-7 px-2 text-xs flex items-center gap-1 text-[var(--primary)] hover:opacity-80 active:opacity-60"
+ aria-label="Go to shuoshuo page"
+ >
+ <span>{isZh ? "更多" : "More"}</span>
</code_context>
<issue_to_address>
**suggestion:** 请将 `aria-label` 本地化,使其像可见文本一样随当前语言切换。
可见文本已经基于 `isZh` 在中英文之间切换,但 `aria-label` 始终是英文,这会导致使用中文区域设置的屏幕阅读器用户体验不一致。建议同样根据 `isZh` 派生 `aria-label`,例如:
```astro
aria-label={isZh ? "前往说说页面" : "Go to shuoshuo page"}
```
或者复用可见文本,并加上一句简短的说明。
```suggestion
class="btn-plain rounded-lg h-7 px-2 text-xs flex items-center gap-1 text-[var(--primary)] hover:opacity-80 active:opacity-60"
aria-label={isZh ? "前往说说页面" : "Go to shuoshuo page"}
>
```
</issue_to_address>帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续的代码审查。
Original comment in English
Hey - I've found 3 issues, and left some high level feedback:
- The
initShuoshuoCardsfunction inshuoshuo.astrois bound to multiple lifecycle events but never cleans up listeners orResizeObservers, so repeated navigations could accumulate handlers; consider making it idempotent per card or adding teardown logic. countCjkAndEnglishWordsonly counts characters in the\u4e00-\u9fa5range, which omits many CJK characters (e.g., extended ranges and Japanese Kanji); if you intend to support broader CJK content, consider expanding the regex to cover the full CJK blocks.- The sort comparator in
getSortedShuoshuoalways returns-1or1and never0, which can make sorting unstable whenpublisheddates are equal; returning0in the equal case would be more correct and predictable.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `initShuoshuoCards` function in `shuoshuo.astro` is bound to multiple lifecycle events but never cleans up listeners or `ResizeObserver`s, so repeated navigations could accumulate handlers; consider making it idempotent per card or adding teardown logic.
- `countCjkAndEnglishWords` only counts characters in the `\u4e00-\u9fa5` range, which omits many CJK characters (e.g., extended ranges and Japanese Kanji); if you intend to support broader CJK content, consider expanding the regex to cover the full CJK blocks.
- The sort comparator in `getSortedShuoshuo` always returns `-1` or `1` and never `0`, which can make sorting unstable when `published` dates are equal; returning `0` in the equal case would be more correct and predictable.
## Individual Comments
### Comment 1
<location path="src/pages/shuoshuo.astro" line_range="87-93" />
<code_context>
+
+ <script>
+ function initShuoshuoCards() {
+ const cards = document.querySelectorAll<HTMLElement>("[data-shuoshuo-card]");
+ for (const card of cards) {
+ const maxHeight = Number(card.getAttribute("data-max-height") || "260");
+ const collapsible = card.querySelector<HTMLElement>("[data-collapsible]");
+ const content = card.querySelector<HTMLElement>("[data-collapsible-content]");
+ const fade = card.querySelector<HTMLElement>("[data-fade]");
+ const toggle = card.querySelector<HTMLButtonElement>("[data-toggle]");
+
+ if (!collapsible || !content || !toggle) continue;
</code_context>
<issue_to_address>
**issue (bug_risk):** Remove TypeScript-specific generics and type annotations from the inline browser script.
Because this inline `<script>` runs as plain JavaScript in the browser, TypeScript-style generics like `querySelectorAll<HTMLElement>(...)` and `querySelector<HTMLButtonElement>(...)` are invalid and will cause runtime syntax errors. Drop the generics to keep the behavior the same:
```js
const cards = document.querySelectorAll("[data-shuoshuo-card]");
...
const toggle = card.querySelector("[data-toggle]");
```
If you need type checking, move this logic into a `.ts` file handled by your bundler instead of using TS annotations inline.
</issue_to_address>
### Comment 2
<location path="src/pages/shuoshuo.astro" line_range="86-95" />
<code_context>
+ function initShuoshuoCards() {
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Avoid re-initializing the same cards and attaching duplicate event listeners on each page-load/content-replacement.
`initShuoshuoCards` runs immediately, on `astro:page-load`, and on `swup:contentReplaced`, and each time it re-wires all `[data-shuoshuo-card]` elements with new click handlers / `ResizeObserver`s. The `data-initialized` flag currently only controls initial open/collapsed state, not whether listeners are already attached.
Consider skipping already-initialized cards instead:
```js
function initShuoshuoCards() {
const cards = document.querySelectorAll('[data-shuoshuo-card]');
for (const card of cards) {
if (card.hasAttribute('data-initialized')) continue;
// existing initialization code...
card.setAttribute('data-initialized', 'true');
}
}
```
This avoids accumulating duplicate listeners and observers across navigations while preserving existing behavior.
Suggested implementation:
```
function initShuoshuoCards() {
const cards = document.querySelectorAll<HTMLElement>("[data-shuoshuo-card]");
for (const card of cards) {
// Skip cards that have already been initialized to avoid duplicate listeners/observers
if (card.hasAttribute("data-initialized")) continue;
const maxHeight = Number(card.getAttribute("data-max-height") || "260");
const collapsible = card.querySelector<HTMLElement>("[data-collapsible]");
const content = card.querySelector<HTMLElement>("[data-collapsible-content]");
const fade = card.querySelector<HTMLElement>("[data-fade]");
const toggle = card.querySelector<HTMLButtonElement>("[data-toggle]");
if (!collapsible || !content || !toggle) continue;
// NOTE: at the end of the per-card initialization logic, remember to mark:
// card.setAttribute("data-initialized", "true");
```
I can only see the beginning of the loop body. To fully implement the behavior:
1. After all event listeners, `ResizeObserver` setup, and initial open/collapsed state logic for each card, add `card.setAttribute("data-initialized", "true");`.
2. Ensure this line runs only when initialization succeeds (i.e., not inside any early-return branches), so that partially-initialized cards don't get marked as initialized prematurely.
</issue_to_address>
### Comment 3
<location path="src/components/widget/LatestShuoshuo.astro" line_range="41-43" />
<code_context>
+ slot="title-icon"
+ href={url("/shuoshuo/")}
+ class="btn-plain rounded-lg h-7 px-2 text-xs flex items-center gap-1 text-[var(--primary)] hover:opacity-80 active:opacity-60"
+ aria-label="Go to shuoshuo page"
+ >
+ <span>{isZh ? "更多" : "More"}</span>
</code_context>
<issue_to_address>
**suggestion:** Localize the `aria-label` to match the current language like the visible text does.
The visible text already switches on `isZh`, but the `aria-label` is always English, which is inconsistent for screen-reader users in the Chinese locale. Please derive the `aria-label` from `isZh` as well, e.g.:
```astro
aria-label={isZh ? "前往说说页面" : "Go to shuoshuo page"}
```
or reuse the visible text plus a short descriptive phrase.
```suggestion
class="btn-plain rounded-lg h-7 px-2 text-xs flex items-center gap-1 text-[var(--primary)] hover:opacity-80 active:opacity-60"
aria-label={isZh ? "前往说说页面" : "Go to shuoshuo page"}
>
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| const cards = document.querySelectorAll<HTMLElement>("[data-shuoshuo-card]"); | ||
| for (const card of cards) { | ||
| const maxHeight = Number(card.getAttribute("data-max-height") || "260"); | ||
| const collapsible = card.querySelector<HTMLElement>("[data-collapsible]"); | ||
| const content = card.querySelector<HTMLElement>("[data-collapsible-content]"); | ||
| const fade = card.querySelector<HTMLElement>("[data-fade]"); | ||
| const toggle = card.querySelector<HTMLButtonElement>("[data-toggle]"); |
There was a problem hiding this comment.
issue (bug_risk): 请从内联的浏览器脚本中移除 TypeScript 特有的泛型和类型标注。
由于这个内联的 <script> 会在浏览器中作为普通 JavaScript 运行,像 querySelectorAll<HTMLElement>(...) 和 querySelector<HTMLButtonElement>(...) 这样的 TypeScript 泛型语法是无效的,会导致运行时语法错误。去掉这些泛型即可保持现有行为不变:
const cards = document.querySelectorAll("[data-shuoshuo-card]");
...
const toggle = card.querySelector("[data-toggle]");如果需要类型检查,请把这段逻辑移到由打包工具处理的 .ts 文件中,而不要在内联脚本中使用 TS 标注。
Original comment in English
issue (bug_risk): Remove TypeScript-specific generics and type annotations from the inline browser script.
Because this inline <script> runs as plain JavaScript in the browser, TypeScript-style generics like querySelectorAll<HTMLElement>(...) and querySelector<HTMLButtonElement>(...) are invalid and will cause runtime syntax errors. Drop the generics to keep the behavior the same:
const cards = document.querySelectorAll("[data-shuoshuo-card]");
...
const toggle = card.querySelector("[data-toggle]");If you need type checking, move this logic into a .ts file handled by your bundler instead of using TS annotations inline.
|
已根据 review 处理合理项:
One note on the inline script TypeScript comment: I do not think that issue applies here. This is a normal Astro Validation after changes:
|
Summary
/shuoshuo/timeline pageTests
pnpm exec biome check src/components/pages/shuoshuo/ShuoshuoCard.astro src/components/widget/LatestShuoshuo.astro src/pages/shuoshuo.astro src/utils/shuoshuo-utils.ts src/utils/text-utils.ts src/content.config.ts src/components/widget/SiteStats.astro src/components/layout/SideBar.astro src/config/navBarConfig.ts src/config/sidebarConfig.ts src/types/sidebarConfig.ts src/i18n/i18nKey.ts src/i18n/languages/en.ts src/i18n/languages/ja.ts src/i18n/languages/ru.ts src/i18n/languages/zh_CN.ts src/i18n/languages/zh_TW.tspnpm checkpnpm buildSummary by Sourcery
添加了新的说说(shuoshuo)微帖内容类型及其专用时间线页面,并将其整合进站点导航、侧边栏、统计和工具模块中。
新功能:
/shuoshuo/时间线页面,使用卡片视图查看和展开单条说说内容。功能增强:
latestShuoshuo挂件类型。Original summary in English
Summary by Sourcery
Add a new shuoshuo micro-post content type with a dedicated timeline page and integrate it into site navigation, sidebar, stats, and utilities.
New Features:
Enhancements: