Skip to content

feat(sidebar/widget): add 51LA visitor statistics sidebar widget - #435

Open
jacksen168sub wants to merge 7 commits into
CuteLeaf:masterfrom
jacksen168sub:feat/sidebar-51la-stats
Open

feat(sidebar/widget): add 51LA visitor statistics sidebar widget#435
jacksen168sub wants to merge 7 commits into
CuteLeaf:masterfrom
jacksen168sub:feat/sidebar-51la-stats

Conversation

@jacksen168sub

@jacksen168sub jacksen168sub commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Type of change

  • Bug fix (a non-breaking change that fixes an issue)
  • New feature (a non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Other (please describe):

Checklist

  • I have read the CONTRIBUTING document.
  • I have checked to ensure that this Pull Request is not for personal changes.
  • I have performed a self-review of my own code.
  • My changes generate no new warnings.

Related Issue

Changes

  • LaStats 组件:新增 51LA 访问统计侧边栏组件 src/components/widget/LaStats.astro,支持展示当前在线人数、今日访客/浏览量、昨日访客/浏览量、本月浏览量及总浏览量
  • 类型定义:在 src/types/sidebarConfig.ts 中新增 lastats 组件类型
  • 国际化支持:为所有支持语言(en、ja、ru、zh_CN、zh_TW)添加 51LA 统计相关的多语言翻译条目(src/i18n/i18nKey.ts 及各语言文件)
  • 配置预设:在默认侧边栏布局配置 src/config/sidebarConfig.ts 中预启用 LaStats 组件
  • 侧边栏集成:在 src/components/layout/SideBar.astro 中引入 LaStats 组件
  • 数据源配置: 支持读取官方js数据 和 使用第三方自建Api作为数据源

How To Test

  1. 在站点配置中启用 51LA 统计功能并填入有效的 51LA 统计 ID
  2. 启动开发服务器,访问任意包含侧边栏的页面
  3. 确认侧边栏中 LaStats 组件正确显示在线人数、今日/昨日 UV/PV、本月浏览量及总浏览量
  4. 切换不同语言,确认各翻译条目正确显示

Screenshots (if applicable)

20260622-232014

Additional Notes

  • Q: 读取js里的数据来展示, 为什么使用这种看上去很笨的做法?
    A: 51la 官方API是收费的, 但是它们提供的官方数据挂件是不要钱的。还有样式是写死的, 数据是硬编码在js里。所有才用了这个笨方法, 暂未想到更好更实惠的办法来实现。(可选也更推荐cf自建第三方api)

  • Q: 这样做安全吗?
    A: 做了基础的正则匹配来锁定数据内容+parseInt, 可排除较为简单的攻击手段来防止被污染时插入广告/恶意脚本

  • Q: 加入该数据展示组件有什么益处?
    A: 好玩(bushi), 是方便直接登入站点就能查看数据

涉及 10 个文件,共 214 行新增代码,无删除行。

Summary by Sourcery v1

在侧边栏中添加新的 51LA 访客统计挂件,并将其集成到默认布局中,提供完整的国际化(i18n)支持。

新功能:

  • 引入 LaStats 侧边栏挂件,当配置了 51LA ID 时,用于显示 51LA 的访客数和页面浏览量统计。

改进:

  • 扩展侧边栏挂件的配置和类型定义,以支持新的 lastats 挂件类型,并将其映射到侧边栏组件注册表中。
  • 为 51LA 访客统计相关标签在所有受支持语言中添加国际化键和值的翻译。

Summary by Sourcery v2

改进:

  • 扩展侧边栏挂件的配置和类型定义,以支持新的 lastats 挂件类型,并在默认侧边栏布局中启用它。
  • 新增分析配置选项,用于选择 51LA 统计数据源(官方 quote.js 或外部 API)及其 URL 模板。

feat(侧边栏/组件): 新增51LA访问统计侧边栏组件

This commit fully implements the 51LA visitor statistics sidebar widget feature.

New Features:
- **LaStats widget**: Add new sidebar widget component for 51LA analytics, supports displaying online users, today UV/PV, yesterday UV/PV, monthly and total views
- **Type definition**: Add `lastats` widget type in `src/types/sidebarConfig.ts`
- **I18n support**: Add multi-language translation entries for 51LA stats across all supported languages
- **Config preset**: Pre-configure the LaStats widget in default sidebar layout config

本提交完整实现了51LA访问统计侧边栏组件功能:

新增功能:
- **LaStats组件**:新增51LA分析侧边栏组件,支持展示当前在线人数、今日访客/浏览量、昨日访客/浏览量、本月和总浏览量
- **类型定义**:在`src/types/sidebarConfig.ts`中新增`lastats`组件类型
- **国际化支持**:为所有支持语言添加51LA统计相关的多语言翻译条目
- **配置预设**:在默认侧边栏布局配置中预启用LaStats组件
@netlify

netlify Bot commented Jun 22, 2026

Copy link
Copy Markdown

Deploy Preview for demo-firefly ready!

Name Link
🔨 Latest commit a0f59f6
🔍 Latest deploy log https://app.netlify.com/projects/demo-firefly/deploys/6a6586109b08e400088ca2cc
😎 Deploy Preview https://deploy-preview-435--demo-firefly.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@sourcery-ai

sourcery-ai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Reviewer's Guide

添加一个新的 51LA 访客统计侧边栏小部件(LaStats),将其接入侧边栏配置和组件映射,并扩展分析配置与 i18n 键/翻译,以支持可配置的数据源以及新统计展示的本地化标签。

LaStats 小部件数据加载时序图

sequenceDiagram
    actor User
    participant Browser
    participant LaStatsWidget as LaStats_astro
    participant AnalyticsConfig as analyticsConfig
    participant QuoteJs as La51_quote_js
    participant CustomApi as statsApi

    User ->> Browser: load sidebar page
    Browser ->> LaStatsWidget: render with la51Id, la51StatsDataSource, la51StatsApiUrl
    LaStatsWidget ->> AnalyticsConfig: read la51Analytics.Id
    AnalyticsConfig -->> LaStatsWidget: la51Id
    LaStatsWidget ->> Browser: inline script
    Browser ->> LaStatsWidget: updateLaStats()
    alt la51StatsDataSource === "api"
        LaStatsWidget ->> CustomApi: fetch(statsApiUrl)
        CustomApi -->> LaStatsWidget: JSON { success, data }
        LaStatsWidget ->> LaStatsWidget: setStatValues([...])
    else la51StatsDataSource === "quotejs"
        LaStatsWidget ->> QuoteJs: fetch("https://v6-widget.51.la/v6/" + la51Id + "/quote.js")
        QuoteJs -->> LaStatsWidget: quote.js text
        LaStatsWidget ->> LaStatsWidget: updateViaQuoteJs()
        LaStatsWidget ->> LaStatsWidget: setStatValues([...])
    end
    Browser ->> Browser: render values into [data-stat-id] elements

    Browser ->> LaStatsWidget: on swup:contentReplaced
    LaStatsWidget ->> Browser: setTimeout(updateLaStats, 100)
Loading

LaStats 小部件在侧边栏集成的流程图

flowchart LR
    sidebarLayoutConfig -->|type lastats| SideBar_astro
    sidebarLayoutConfig --> analyticsConfig
    SideBar_astro -->|componentMap.lastats| LaStats_astro
    LaStats_astro --> analyticsConfig
    LaStats_astro --> i18n
    i18n --> I18nLanguages
    LaStats_astro -->|stats display| SidebarDOM
Loading

文件级变更

Change Details Files
Introduce LaStats sidebar widget component that renders 51LA visitor statistics when a 51LA ID is configured.
  • Create LaStats.astro widget using WidgetLayout and astro-icon, with configurable title visibility and styling via widgetConfig props.
  • Read 51LA analytics ID and new stats data source settings from analyticsConfig to control whether and how stats are loaded.
  • Render a fixed list of statistic rows (online, today UV/PV, yesterday UV/PV, month PV, total PV) each with icon, label from i18n, and value placeholders addressed via data-stat-id attributes.
  • Inline a client-side script that initializes once globally, validates the 51LA ID, and populates stat values via either parsing 51LA quote.js or calling a third-party JSON API, with basic error handling and swup navigation support.
src/components/widget/LaStats.astro
Wire the LaStats widget into the sidebar system and default layouts.
  • Extend WidgetComponentType union type to include the new lastats widget identifier.
  • Register lastats in the sidebar component map so it renders the LaStats component when configured.
  • Enable the LaStats widget by default in the sidebar layout configuration for relevant sidebar layouts, including placement and visibility settings.
src/types/sidebarConfig.ts
src/components/layout/SideBar.astro
src/config/sidebarConfig.ts
Extend analytics configuration to support LaStats data source configuration.
  • Add statsDataSource option allowing choice between parsing official quote.js or using a third-party API JSON source.
  • Add statsApiUrl template option, including support for an {id} placeholder and documentation comments for expected URL formats and behavior.
src/config/analyticsConfig.ts
src/types/analyticsConfig.ts
Add i18n keys and translations for 51LA visitor statistics labels across all supported languages.
  • Define new I18nKey enum entries for LaStats widget title and all statistic labels (online, today/yesterday UV/PV, month PV, total PV).
  • Provide corresponding translations for each new key in English, Japanese, Korean, Russian, Simplified Chinese, and Traditional Chinese language files, matching existing style and terminology.
src/i18n/i18nKey.ts
src/i18n/languages/en.ts
src/i18n/languages/ja.ts
src/i18n/languages/ko.ts
src/i18n/languages/ru.ts
src/i18n/languages/zh_CN.ts
src/i18n/languages/zh_TW.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Original review guide in English

Reviewer's Guide

Adds a new 51LA visitor statistics sidebar widget (LaStats), wires it into the sidebar configuration and component map, and extends analytics configuration and i18n keys/translations to support configurable data sources and localized labels for the new stats display.

Sequence diagram for LaStats widget data loading

sequenceDiagram
    actor User
    participant Browser
    participant LaStatsWidget as LaStats_astro
    participant AnalyticsConfig as analyticsConfig
    participant QuoteJs as La51_quote_js
    participant CustomApi as statsApi

    User ->> Browser: load sidebar page
    Browser ->> LaStatsWidget: render with la51Id, la51StatsDataSource, la51StatsApiUrl
    LaStatsWidget ->> AnalyticsConfig: read la51Analytics.Id
    AnalyticsConfig -->> LaStatsWidget: la51Id
    LaStatsWidget ->> Browser: inline script
    Browser ->> LaStatsWidget: updateLaStats()
    alt la51StatsDataSource === "api"
        LaStatsWidget ->> CustomApi: fetch(statsApiUrl)
        CustomApi -->> LaStatsWidget: JSON { success, data }
        LaStatsWidget ->> LaStatsWidget: setStatValues([...])
    else la51StatsDataSource === "quotejs"
        LaStatsWidget ->> QuoteJs: fetch("https://v6-widget.51.la/v6/" + la51Id + "/quote.js")
        QuoteJs -->> LaStatsWidget: quote.js text
        LaStatsWidget ->> LaStatsWidget: updateViaQuoteJs()
        LaStatsWidget ->> LaStatsWidget: setStatValues([...])
    end
    Browser ->> Browser: render values into [data-stat-id] elements

    Browser ->> LaStatsWidget: on swup:contentReplaced
    LaStatsWidget ->> Browser: setTimeout(updateLaStats, 100)
Loading

Flow diagram for LaStats widget integration in sidebar

flowchart LR
    sidebarLayoutConfig -->|type lastats| SideBar_astro
    sidebarLayoutConfig --> analyticsConfig
    SideBar_astro -->|componentMap.lastats| LaStats_astro
    LaStats_astro --> analyticsConfig
    LaStats_astro --> i18n
    i18n --> I18nLanguages
    LaStats_astro -->|stats display| SidebarDOM
Loading

File-Level Changes

Change Details Files
Introduce LaStats sidebar widget component that renders 51LA visitor statistics when a 51LA ID is configured.
  • Create LaStats.astro widget using WidgetLayout and astro-icon, with configurable title visibility and styling via widgetConfig props.
  • Read 51LA analytics ID and new stats data source settings from analyticsConfig to control whether and how stats are loaded.
  • Render a fixed list of statistic rows (online, today UV/PV, yesterday UV/PV, month PV, total PV) each with icon, label from i18n, and value placeholders addressed via data-stat-id attributes.
  • Inline a client-side script that initializes once globally, validates the 51LA ID, and populates stat values via either parsing 51LA quote.js or calling a third-party JSON API, with basic error handling and swup navigation support.
src/components/widget/LaStats.astro
Wire the LaStats widget into the sidebar system and default layouts.
  • Extend WidgetComponentType union type to include the new lastats widget identifier.
  • Register lastats in the sidebar component map so it renders the LaStats component when configured.
  • Enable the LaStats widget by default in the sidebar layout configuration for relevant sidebar layouts, including placement and visibility settings.
src/types/sidebarConfig.ts
src/components/layout/SideBar.astro
src/config/sidebarConfig.ts
Extend analytics configuration to support LaStats data source configuration.
  • Add statsDataSource option allowing choice between parsing official quote.js or using a third-party API JSON source.
  • Add statsApiUrl template option, including support for an {id} placeholder and documentation comments for expected URL formats and behavior.
src/config/analyticsConfig.ts
src/types/analyticsConfig.ts
Add i18n keys and translations for 51LA visitor statistics labels across all supported languages.
  • Define new I18nKey enum entries for LaStats widget title and all statistic labels (online, today/yesterday UV/PV, month PV, total PV).
  • Provide corresponding translations for each new key in English, Japanese, Korean, Russian, Simplified Chinese, and Traditional Chinese language files, matching existing style and terminology.
src/i18n/i18nKey.ts
src/i18n/languages/en.ts
src/i18n/languages/ja.ts
src/i18n/languages/ko.ts
src/i18n/languages/ru.ts
src/i18n/languages/zh_CN.ts
src/i18n/languages/zh_TW.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - 我发现了 1 个问题,并留下了一些整体性的反馈:

  • LaStats.astro 中,INDEX_MAP 重复了 stats 数组中已经定义的 ID,这有导致两者不同步的风险;建议直接从 stats 衍生出这个映射(例如 const INDEX_MAP = stats.map(s => s.id)),这样就只有一个可信来源。
  • 用于解析 quote.js 的正则(r.innerHTML = "..." 以及 <p><span>...</span><span>...</span></p> 这一模式)相当严格,假设不存在额外的空白字符/属性,也假设引文不会改变;你可能需要适当放宽它们(例如允许空白和非贪婪匹配,或者在提取出的 HTML 上使用 DOM 解析器),以便在上游标记略有变化时更具鲁棒性。
供 AI 代理使用的提示词
Please address the comments from this code review:

## Overall Comments
-`LaStats.astro` 中,`INDEX_MAP` 重复了 `stats` 数组中已经定义的 ID,这有导致两者不同步的风险;建议直接从 `stats` 衍生出这个映射(例如 `const INDEX_MAP = stats.map(s => s.id)`),这样就只有一个可信来源。
- 用于解析 `quote.js` 的正则(`r.innerHTML = "..."` 以及 `<p><span>...</span><span>...</span></p>` 这一模式)相当严格,假设不存在额外的空白字符/属性,也假设引文不会改变;你可能需要适当放宽它们(例如允许空白和非贪婪匹配,或者在提取出的 HTML 上使用 DOM 解析器),以便在上游标记略有变化时更具鲁棒性。

## Individual Comments

### Comment 1
<location path="src/components/widget/LaStats.astro" line_range="110-114" />
<code_context>
+
+          while ((m = pRegex.exec(html)) !== null) {
+            var idx = parseInt(m[1], 10);
+            var val = parseInt(m[2], 10);
+            if (idx >= 0 && idx < INDEX_MAP.length) {
+              var elements = document.querySelectorAll('[data-stat-id="' + INDEX_MAP[idx] + '"]');
+              elements.forEach(function(el) {
+                el.textContent = val.toLocaleString();
+              });
+            }
</code_context>
<issue_to_address>
**issue (bug_risk):** 在调用 toLocaleString 之前,对非数值或意外的值进行保护,以避免运行时错误。

如果 `m[2]` 缺失或为非数值(例如由于格式变更),`parseInt` 会返回 `NaN`,而此时调用 `val.toLocaleString()` 会抛出异常。建议使用 `!Number.isNaN(val)` 做保护检查,在解析值无效时跳过更新,或使用安全的回退值(例如 `'-'`)。
</issue_to_address>

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

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

  • In LaStats.astro, INDEX_MAP duplicates the IDs already defined in the stats array, which risks them getting out of sync; consider deriving the mapping directly from stats (e.g., const INDEX_MAP = stats.map(s => s.id)) so there's a single source of truth.
  • The regexes used to parse quote.js (r.innerHTML = "..." and the <p><span>...</span><span>...</span></p> pattern) are quite strict and assume no extra whitespace/attributes or quote changes; you might want to loosen them slightly (e.g., allow whitespace and non-greedy matches, or use a DOM parser on the extracted HTML) to be more robust against minor upstream markup changes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `LaStats.astro`, `INDEX_MAP` duplicates the IDs already defined in the `stats` array, which risks them getting out of sync; consider deriving the mapping directly from `stats` (e.g., `const INDEX_MAP = stats.map(s => s.id)`) so there's a single source of truth.
- The regexes used to parse `quote.js` (`r.innerHTML = "..."` and the `<p><span>...</span><span>...</span></p>` pattern) are quite strict and assume no extra whitespace/attributes or quote changes; you might want to loosen them slightly (e.g., allow whitespace and non-greedy matches, or use a DOM parser on the extracted HTML) to be more robust against minor upstream markup changes.

## Individual Comments

### Comment 1
<location path="src/components/widget/LaStats.astro" line_range="110-114" />
<code_context>
+
+          while ((m = pRegex.exec(html)) !== null) {
+            var idx = parseInt(m[1], 10);
+            var val = parseInt(m[2], 10);
+            if (idx >= 0 && idx < INDEX_MAP.length) {
+              var elements = document.querySelectorAll('[data-stat-id="' + INDEX_MAP[idx] + '"]');
+              elements.forEach(function(el) {
+                el.textContent = val.toLocaleString();
+              });
+            }
</code_context>
<issue_to_address>
**issue (bug_risk):** Guard against non-numeric or unexpected values before calling toLocaleString to avoid runtime errors.

If `m[2]` is missing or non-numeric (e.g. due to a format change), `parseInt` will yield `NaN` and `val.toLocaleString()` will throw. Consider guarding with `!Number.isNaN(val)` and either skipping the update or using a safe fallback (e.g. `'-'`) when the parsed value is invalid.
</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 thread src/components/widget/LaStats.astro Outdated
@CuteLeaf
CuteLeaf marked this pull request as draft June 24, 2026 05:48
jacksen168sub and others added 2 commits June 26, 2026 13:37
…aStats.astro

fix(components/widget): 修复因自动合并冲突导致的 LaStats.astro 组件模板语法错误

The automatic merge resolution introduced a misplaced closing curly brace
in the LaStats.astro component template, resulting in invalid template syntax.
This fix restores the correct structure and resolves the compilation error.

修复因自动合并冲突导致的 LaStats.astro 组件模板语法错误。
合并过程中引入了一个位置错误的闭合大括号,导致模板语法无效。
@jacksen168sub
jacksen168sub marked this pull request as ready for review June 26, 2026 14:37

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - 我这边发现了两个问题,并且有一些整体性的反馈:

  • quote.js 的解析逻辑依赖非常具体的字符串模式(例如 r.innerHTML = "..." 以及固定的 <p><span> 标记结构),一旦 51LA 修改实现,这里就会在没有任何提示的情况下失效;建议增加一层小的抽象(配合注释和后备 UI 状态),既方便后续修改,也更容易在解析失败时被发现。
  • 每次触发 swup:contentReplaced 事件都会重新向 51LA 发起一次请求;可以考虑加一个带短 TTL 的内存缓存,既能减少不必要的网络请求、避免配额/限流问题,又能在合理范围内保持统计数据的实时性。
给 AI Agent 的提示
Please address the comments from this code review:

## Overall Comments
- The parsing logic for `quote.js` relies on very specific string patterns (e.g. `r.innerHTML = "..."` and fixed `<p><span>` markup), which will break silently if 51LA changes their implementation; consider adding a small abstraction (with comments and a fallback UI state) to make this easier to update or detect when parsing fails.
- Each `swup:contentReplaced` event triggers a new fetch to 51LA; adding a simple in-memory cache with a short TTL would reduce unnecessary network calls and avoid rate/quotas issues while still keeping the stats reasonably fresh.

## Individual Comments

### Comment 1
<location path="src/components/widget/LaStats.astro" line_range="102-103" />
<code_context>
+          return res.text();
+        })
+        .then(function(text) {
+          var match = text.match(/r\.innerHTML\s*=\s*"([^"]+)"/);
+          if (!match) return;
+
+          var html = match[1];
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Parsing `quote.js` via a very specific regex on `r.innerHTML` is brittle to upstream implementation changes.

It currently relies on 51LA’s script using `r.innerHTML="..."` with that exact variable name, double quotes, and assignment shape. Any variation (e.g. single quotes, different variable, or spacing change) will break extraction and silently stop updating stats. Please make the regex more tolerant to these variations, and/or log when `match` is falsy so failures are visible.

Suggested implementation:

```
        .then(function(text) {
          // Match any innerHTML assignment, allowing for different variable names,
          // single or double quotes, and flexible whitespace/newlines.
          var match = text.match(/innerHTML\s*=\s*(["'])([\s\S]*?)\1/);
          if (!match) {
            console.warn("51.LA stats: failed to extract innerHTML from quote.js", { url: url, textSample: text.slice(0, 200) });
            return;
          }

          var html = match[2];

```

The updated regex `/innerHTML\s*=\s*(["'])([\s\S]*?)\1/` now:
- Ignores the specific variable name (`r`) and focuses on any `innerHTML` assignment.
- Supports both single and double quotes.
- Handles arbitrary spacing and newlines between `=` and the assigned string.

The new `console.warn` ensures that if extraction fails, there is a visible log with the URL and a small sample of the response body to aid debugging. No other changes are required if this is the only place that parses `quote.js`.
</issue_to_address>

### Comment 2
<location path="src/components/widget/LaStats.astro" line_range="91-100" />
<code_context>
+    if (!la51Id || !/^[A-Za-z0-9]+$/.test(la51Id)) return;
+
+    // 索引到键名的映射,与 51LA quote.js 接口返回的 <p><span>索引</span><span>值</span></p> 格式对应
+    var INDEX_MAP = ["la-online", "la-today-uv", "la-today-pv", "la-yesterday-uv", "la-yesterday-pv", "la-month-pv", "la-total-pv"];
+
+    function updateLaStats() {
+      var url = "https://v6-widget.51.la/v6/" + la51Id + "/quote.js";
+
+      fetch(url)
+        .then(function(res) {
+          if (!res.ok) throw new Error("HTTP " + res.status);
+          return res.text();
+        })
+        .then(function(text) {
+          var match = text.match(/r\.innerHTML\s*=\s*"([^"]+)"/);
+          if (!match) return;
+
+          var html = match[1];
+          var pRegex = /<p><span>(\d+)<\/span><span>(\d+)<\/span><\/p>/g;
+          var m;
+
+          while ((m = pRegex.exec(html)) !== null) {
+            var idx = parseInt(m[1], 10);
+            var val = parseInt(m[2], 10);
+            if (idx >= 0 && idx < INDEX_MAP.length) {
</code_context>
<issue_to_address>
**suggestion (bug_risk):** The `stats` definition and `INDEX_MAP` need to stay manually in sync, which is easy to break over time.

Because the mapping lives partly in `stats` and partly in `INDEX_MAP`, any reordering or additions can silently desync labels and values. It would be safer to derive `INDEX_MAP` from a single source of truth (for example, store the 51LA index on each `stats` entry and generate the map from that).

Suggested implementation:

```
    // 通过 stats 配置派生出“51LA 索引 → statId”的映射,避免手动维护两份列表
    // 期望在上方有形如:
    // const stats = [
    //   { id: "la-online", /* ... */, laIndex: 0 },
    //   { id: "la-today-uv", /* ... */, laIndex: 1 },
    //   ...
    // ];
    var INDEX_MAP = (function() {
      var map = [];
      if (Array.isArray(stats)) {
        stats.forEach(function(stat) {
          if (
            stat &&
            typeof stat.laIndex === "number" &&
            stat.laIndex >= 0
          ) {
            map[stat.laIndex] = stat.id;
          }
        });
      }
      return map;
    })();

```

```
            var idx = parseInt(m[1], 10);
            var val = parseInt(m[2], 10);
            var statId = INDEX_MAP[idx];
            if (typeof statId === "string") {
              var elements = document.querySelectorAll('[data-stat-id="' + statId + '"]');

```

To fully implement the “single source of truth” suggestion, you should also:

1. Ensure there is a `stats` array defined earlier in `LaStats.astro` (or imported into it) that is used to render the stats UI.
2. Add a `laIndex` numeric field to each relevant `stats` entry corresponding to the 51LA index:
   - Example:
     - `la-online``laIndex: 0`
     - `la-today-uv``laIndex: 1`
     - `la-today-pv``laIndex: 2`
     - `la-yesterday-uv``laIndex: 3`
     - `la-yesterday-pv``laIndex: 4`
     - `la-month-pv``laIndex: 5`
     - `la-total-pv``laIndex: 6`
3. If some `stats` entries are not backed by 51LA, omit `laIndex` on those; the derived `INDEX_MAP` will only include entries where `laIndex` is set.
4. Remove any old hard-coded arrays or mappings that duplicate this index information elsewhere to keep `stats` as the single source of truth.
</issue_to_address>

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

Hey - I've found 2 issues, and left some high level feedback:

  • The parsing logic for quote.js relies on very specific string patterns (e.g. r.innerHTML = "..." and fixed <p><span> markup), which will break silently if 51LA changes their implementation; consider adding a small abstraction (with comments and a fallback UI state) to make this easier to update or detect when parsing fails.
  • Each swup:contentReplaced event triggers a new fetch to 51LA; adding a simple in-memory cache with a short TTL would reduce unnecessary network calls and avoid rate/quotas issues while still keeping the stats reasonably fresh.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The parsing logic for `quote.js` relies on very specific string patterns (e.g. `r.innerHTML = "..."` and fixed `<p><span>` markup), which will break silently if 51LA changes their implementation; consider adding a small abstraction (with comments and a fallback UI state) to make this easier to update or detect when parsing fails.
- Each `swup:contentReplaced` event triggers a new fetch to 51LA; adding a simple in-memory cache with a short TTL would reduce unnecessary network calls and avoid rate/quotas issues while still keeping the stats reasonably fresh.

## Individual Comments

### Comment 1
<location path="src/components/widget/LaStats.astro" line_range="102-103" />
<code_context>
+          return res.text();
+        })
+        .then(function(text) {
+          var match = text.match(/r\.innerHTML\s*=\s*"([^"]+)"/);
+          if (!match) return;
+
+          var html = match[1];
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Parsing `quote.js` via a very specific regex on `r.innerHTML` is brittle to upstream implementation changes.

It currently relies on 51LA’s script using `r.innerHTML="..."` with that exact variable name, double quotes, and assignment shape. Any variation (e.g. single quotes, different variable, or spacing change) will break extraction and silently stop updating stats. Please make the regex more tolerant to these variations, and/or log when `match` is falsy so failures are visible.

Suggested implementation:

```
        .then(function(text) {
          // Match any innerHTML assignment, allowing for different variable names,
          // single or double quotes, and flexible whitespace/newlines.
          var match = text.match(/innerHTML\s*=\s*(["'])([\s\S]*?)\1/);
          if (!match) {
            console.warn("51.LA stats: failed to extract innerHTML from quote.js", { url: url, textSample: text.slice(0, 200) });
            return;
          }

          var html = match[2];

```

The updated regex `/innerHTML\s*=\s*(["'])([\s\S]*?)\1/` now:
- Ignores the specific variable name (`r`) and focuses on any `innerHTML` assignment.
- Supports both single and double quotes.
- Handles arbitrary spacing and newlines between `=` and the assigned string.

The new `console.warn` ensures that if extraction fails, there is a visible log with the URL and a small sample of the response body to aid debugging. No other changes are required if this is the only place that parses `quote.js`.
</issue_to_address>

### Comment 2
<location path="src/components/widget/LaStats.astro" line_range="91-100" />
<code_context>
+    if (!la51Id || !/^[A-Za-z0-9]+$/.test(la51Id)) return;
+
+    // 索引到键名的映射,与 51LA quote.js 接口返回的 <p><span>索引</span><span>值</span></p> 格式对应
+    var INDEX_MAP = ["la-online", "la-today-uv", "la-today-pv", "la-yesterday-uv", "la-yesterday-pv", "la-month-pv", "la-total-pv"];
+
+    function updateLaStats() {
+      var url = "https://v6-widget.51.la/v6/" + la51Id + "/quote.js";
+
+      fetch(url)
+        .then(function(res) {
+          if (!res.ok) throw new Error("HTTP " + res.status);
+          return res.text();
+        })
+        .then(function(text) {
+          var match = text.match(/r\.innerHTML\s*=\s*"([^"]+)"/);
+          if (!match) return;
+
+          var html = match[1];
+          var pRegex = /<p><span>(\d+)<\/span><span>(\d+)<\/span><\/p>/g;
+          var m;
+
+          while ((m = pRegex.exec(html)) !== null) {
+            var idx = parseInt(m[1], 10);
+            var val = parseInt(m[2], 10);
+            if (idx >= 0 && idx < INDEX_MAP.length) {
</code_context>
<issue_to_address>
**suggestion (bug_risk):** The `stats` definition and `INDEX_MAP` need to stay manually in sync, which is easy to break over time.

Because the mapping lives partly in `stats` and partly in `INDEX_MAP`, any reordering or additions can silently desync labels and values. It would be safer to derive `INDEX_MAP` from a single source of truth (for example, store the 51LA index on each `stats` entry and generate the map from that).

Suggested implementation:

```
    // 通过 stats 配置派生出“51LA 索引 → statId”的映射,避免手动维护两份列表
    // 期望在上方有形如:
    // const stats = [
    //   { id: "la-online", /* ... */, laIndex: 0 },
    //   { id: "la-today-uv", /* ... */, laIndex: 1 },
    //   ...
    // ];
    var INDEX_MAP = (function() {
      var map = [];
      if (Array.isArray(stats)) {
        stats.forEach(function(stat) {
          if (
            stat &&
            typeof stat.laIndex === "number" &&
            stat.laIndex >= 0
          ) {
            map[stat.laIndex] = stat.id;
          }
        });
      }
      return map;
    })();

```

```
            var idx = parseInt(m[1], 10);
            var val = parseInt(m[2], 10);
            var statId = INDEX_MAP[idx];
            if (typeof statId === "string") {
              var elements = document.querySelectorAll('[data-stat-id="' + statId + '"]');

```

To fully implement the “single source of truth” suggestion, you should also:

1. Ensure there is a `stats` array defined earlier in `LaStats.astro` (or imported into it) that is used to render the stats UI.
2. Add a `laIndex` numeric field to each relevant `stats` entry corresponding to the 51LA index:
   - Example:
     - `la-online``laIndex: 0`
     - `la-today-uv``laIndex: 1`
     - `la-today-pv``laIndex: 2`
     - `la-yesterday-uv``laIndex: 3`
     - `la-yesterday-pv``laIndex: 4`
     - `la-month-pv``laIndex: 5`
     - `la-total-pv``laIndex: 6`
3. If some `stats` entries are not backed by 51LA, omit `laIndex` on those; the derived `INDEX_MAP` will only include entries where `laIndex` is set.
4. Remove any old hard-coded arrays or mappings that duplicate this index information elsewhere to keep `stats` as the single source of truth.
</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 +102 to +103
var match = text.match(/r\.innerHTML\s*=\s*"([^"]+)"/);
if (!match) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (bug_risk): 通过对 r.innerHTML 使用过于具体的正则来解析 quote.js,对上游实现变更非常脆弱。

当前实现依赖 51LA 的脚本使用形如 r.innerHTML="..." 的写法,并且假定变量名、双引号以及赋值结构都完全一致。任何变化(例如改用单引号、更换变量名,或只是空格格式的调整)都会导致提取失败,并在无提示的情况下停止更新统计数据。请让正则对这些变体更具容错能力,并且在 match 为 falsy 时输出日志,方便发现失败。

建议实现:

        .then(function(text) {
          // Match any innerHTML assignment, allowing for different variable names,
          // single or double quotes, and flexible whitespace/newlines.
          var match = text.match(/innerHTML\s*=\s*(["'])([\s\S]*?)\1/);
          if (!match) {
            console.warn("51.LA stats: failed to extract innerHTML from quote.js", { url: url, textSample: text.slice(0, 200) });
            return;
          }

          var html = match[2];

更新后的正则 /innerHTML\s*=\s*(["'])([\s\S]*?)\1/

  • 不再依赖特定变量名(如 r),而是聚焦任意 innerHTML 赋值语句;
  • 同时支持单引号和双引号;
  • 能处理 = 与被赋值字符串之间任意空白和换行。

新增的 console.warn 能确保在提取失败时,控制台会有可见日志,并附带 URL 和响应体前 200 个字符,方便调试。如果这里只在该处解析 quote.js,则无需做其他修改。

Original comment in English

suggestion (bug_risk): Parsing quote.js via a very specific regex on r.innerHTML is brittle to upstream implementation changes.

It currently relies on 51LA’s script using r.innerHTML="..." with that exact variable name, double quotes, and assignment shape. Any variation (e.g. single quotes, different variable, or spacing change) will break extraction and silently stop updating stats. Please make the regex more tolerant to these variations, and/or log when match is falsy so failures are visible.

Suggested implementation:

        .then(function(text) {
          // Match any innerHTML assignment, allowing for different variable names,
          // single or double quotes, and flexible whitespace/newlines.
          var match = text.match(/innerHTML\s*=\s*(["'])([\s\S]*?)\1/);
          if (!match) {
            console.warn("51.LA stats: failed to extract innerHTML from quote.js", { url: url, textSample: text.slice(0, 200) });
            return;
          }

          var html = match[2];

The updated regex /innerHTML\s*=\s*(["'])([\s\S]*?)\1/ now:

  • Ignores the specific variable name (r) and focuses on any innerHTML assignment.
  • Supports both single and double quotes.
  • Handles arbitrary spacing and newlines between = and the assigned string.

The new console.warn ensures that if extraction fails, there is a visible log with the URL and a small sample of the response body to aid debugging. No other changes are required if this is the only place that parses quote.js.

Comment thread src/components/widget/LaStats.astro Outdated
Comment on lines +91 to +100
var INDEX_MAP = ["la-online", "la-today-uv", "la-today-pv", "la-yesterday-uv", "la-yesterday-pv", "la-month-pv", "la-total-pv"];

function updateLaStats() {
var url = "https://v6-widget.51.la/v6/" + la51Id + "/quote.js";

fetch(url)
.then(function(res) {
if (!res.ok) throw new Error("HTTP " + res.status);
return res.text();
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (bug_risk): 目前的 stats 定义和 INDEX_MAP 需要手动保持同步,随着时间推移很容易被改坏。

由于映射关系一部分在 stats 中,一部分在 INDEX_MAP 中,任何重排或新增项都可能在没有明显迹象的情况下导致标签与数值错位。更安全的做法是从单一信息源派生出 INDEX_MAP(例如在每个 stats 条目上存 51LA 的索引,然后据此生成映射)。

建议实现:

    // 通过 stats 配置派生出“51LA 索引 → statId”的映射,避免手动维护两份列表
    // 期望在上方有形如:
    // const stats = [
    //   { id: "la-online", /* ... */, laIndex: 0 },
    //   { id: "la-today-uv", /* ... */, laIndex: 1 },
    //   ...
    // ];
    var INDEX_MAP = (function() {
      var map = [];
      if (Array.isArray(stats)) {
        stats.forEach(function(stat) {
          if (
            stat &&
            typeof stat.laIndex === "number" &&
            stat.laIndex >= 0
          ) {
            map[stat.laIndex] = stat.id;
          }
        });
      }
      return map;
    })();

            var idx = parseInt(m[1], 10);
            var val = parseInt(m[2], 10);
            var statId = INDEX_MAP[idx];
            if (typeof statId === "string") {
              var elements = document.querySelectorAll('[data-stat-id="' + statId + '"]');

要完整落地这个“单一信息源”的建议,还需要:

  1. 确保在 LaStats.astro 文件中更前面(或从外部导入)定义了一个 stats 数组,用于渲染统计信息的 UI;
  2. 在每个与 51LA 对应的 stats 条目上增加一个数值型字段 laIndex,与 51LA 的索引一一对应:
    • 示例:
      • la-onlinelaIndex: 0
      • la-today-uvlaIndex: 1
      • la-today-pvlaIndex: 2
      • la-yesterday-uvlaIndex: 3
      • la-yesterday-pvlaIndex: 4
      • la-month-pvlaIndex: 5
      • la-total-pvlaIndex: 6
  3. 如果某些 stats 项并非由 51LA 提供数据,则不要为其设置 laIndex;派生出的 INDEX_MAP 只会包含设了 laIndex 的条目。
  4. 删除其他地方任何硬编码的数组或重复的索引映射信息,确保 stats 是唯一的“单一信息源”。
Original comment in English

suggestion (bug_risk): The stats definition and INDEX_MAP need to stay manually in sync, which is easy to break over time.

Because the mapping lives partly in stats and partly in INDEX_MAP, any reordering or additions can silently desync labels and values. It would be safer to derive INDEX_MAP from a single source of truth (for example, store the 51LA index on each stats entry and generate the map from that).

Suggested implementation:

    // 通过 stats 配置派生出“51LA 索引 → statId”的映射,避免手动维护两份列表
    // 期望在上方有形如:
    // const stats = [
    //   { id: "la-online", /* ... */, laIndex: 0 },
    //   { id: "la-today-uv", /* ... */, laIndex: 1 },
    //   ...
    // ];
    var INDEX_MAP = (function() {
      var map = [];
      if (Array.isArray(stats)) {
        stats.forEach(function(stat) {
          if (
            stat &&
            typeof stat.laIndex === "number" &&
            stat.laIndex >= 0
          ) {
            map[stat.laIndex] = stat.id;
          }
        });
      }
      return map;
    })();

            var idx = parseInt(m[1], 10);
            var val = parseInt(m[2], 10);
            var statId = INDEX_MAP[idx];
            if (typeof statId === "string") {
              var elements = document.querySelectorAll('[data-stat-id="' + statId + '"]');

To fully implement the “single source of truth” suggestion, you should also:

  1. Ensure there is a stats array defined earlier in LaStats.astro (or imported into it) that is used to render the stats UI.
  2. Add a laIndex numeric field to each relevant stats entry corresponding to the 51LA index:
    • Example:
      • la-onlinelaIndex: 0
      • la-today-uvlaIndex: 1
      • la-today-pvlaIndex: 2
      • la-yesterday-uvlaIndex: 3
      • la-yesterday-pvlaIndex: 4
      • la-month-pvlaIndex: 5
      • la-total-pvlaIndex: 6
  3. If some stats entries are not backed by 51LA, omit laIndex on those; the derived INDEX_MAP will only include entries where laIndex is set.
  4. Remove any old hard-coded arrays or mappings that duplicate this index information elsewhere to keep stats as the single source of truth.

@CuteLeaf
CuteLeaf marked this pull request as draft June 27, 2026 12:14
feat(analytics/widget): 为51LA统计挂件添加可配置的数据源

This commit adds support for multiple data sources for the LA stats widget:
- Add `statsDataSource` and `statsApiUrl` fields to AnalyticsConfig type
- Set default config values for the new fields
- Rework the widget logic to support both quote.js built-in source and custom API source

该提交为51LA统计挂件添加了多数据源支持:
- 向AnalyticsConfig类型新增`statsDataSource`和`statsApiUrl`配置项
- 为新配置项设置默认值
- 重构挂件逻辑,同时支持官方quote.js数据源和自定义API数据源
…t sending duplicate requests and the compilation problem caused by adding Korean without adaptation

fix(组件/国际化):修复了51LA访客统计组件发送重复请求的问题,以及添加韩语未适配导致的编译问题

This commit adds full support for 51LA visitor statistics widget:
1. **Add Korean translations**: Add all required i18n keys for 51LA stats in `src/i18n/languages/ko.ts`
2. **Fix duplicate requests**: Add global initialization lock to prevent multiple widget instantiations
3. **Optimize fetch code**: Simplify the fetch URL inline writing

本次提交完整实现51LA访客统计功能:
1.  **添加韩语国际化支持**:在`src/i18n/languages/ko.ts`中新增所有51LA统计相关的多语言键值
2.  **修复重复请求问题**:添加全局初始化锁,避免多侧边栏实例重复加载组件
3.  **优化请求代码**:简化fetch请求的URL编写方式
@jacksen168sub
jacksen168sub marked this pull request as ready for review July 14, 2026 01:23

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - 我发现了 1 个问题,并且有一些整体性的反馈:

  • statsDataSource 设置为 apistatsApiUrl 为空时,建议不要只记录错误日志,可以考虑回退到 quotejs 的解析路径,这样在最小配置下组件仍然可以正常工作。
  • updateViaQuoteJs 的逻辑假设 quote.js 的结构是稳定不变的;在解析失败时,更安全的做法可能是显式清空/隐藏组件或展示一个兜底状态,而不是静默地保留占位内容。
给 AI 代理的提示词
Please address the comments from this code review:

## Overall Comments
-`statsDataSource` 设置为 `api``statsApiUrl` 为空时,建议不要只记录错误日志,可以考虑回退到 `quotejs` 的解析路径,这样在最小配置下组件仍然可以正常工作。
- `updateViaQuoteJs` 的逻辑假设 `quote.js` 的结构是稳定不变的;在解析失败时,更安全的做法可能是显式清空/隐藏组件或展示一个兜底状态,而不是静默地保留占位内容。

## Individual Comments

### Comment 1
<location path="src/components/widget/LaStats.astro" line_range="98-107" />
<code_context>
+    var STAT_IDS = ["la-online", "la-today-uv", "la-today-pv", "la-yesterday-uv", "la-yesterday-pv", "la-month-pv", "la-total-pv"];
</code_context>
<issue_to_address>
**suggestion:** 硬编码的 STAT_IDS 数组很容易与上面的统计定义失去同步。

由于 `stats``STAT_IDS` 必须保持相同的顺序和长度,任何对 `stats` 的修改(添加/删除/重新排序)都需要在这里手动更新,这很容易出错。可以考虑从 `stats` 推导出 `STAT_IDS`(例如通过 `define:vars` 传入 ID),或者从渲染后的 DOM 中推导,这样度量 ID 和排序就只有一个可信来源。

建议实现:

```
    function setStatValues(values) {
      // 从渲染后的 DOM 中推导 STAT_IDS,使其顺序和标记保持同步
      var allStatEls = document.querySelectorAll('[data-stat-id]');
      var statIds = [];

      allStatEls.forEach(function(el) {
        var id = el.getAttribute('data-stat-id');
        if (id && statIds.indexOf(id) === -1) {
          statIds.push(id);
        }
      });

      for (var i = 0; i < values.length && i < statIds.length; i++) {
        var els = document.querySelectorAll('[data-stat-id="' + statIds[i] + '"]');
        els.forEach(function(el) { el.textContent = values[i].toLocaleString(); });
      }
    }

```

如果统计项的顺序不仅仅由 DOM 顺序决定,可以考虑:
1. 通过 `define:vars`(例如 `la51StatIds`)从父级传入一个有序的统计 ID 列表,并用它来代替从 DOM 推导。
2. 在定义 `data-stat-id` 属性的标记附近添加一小段内联注释,说明它们在 DOM 中的顺序会决定此脚本中 stats 数组的映射关系。
</issue_to_address>

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

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

  • When statsDataSource is set to api but statsApiUrl is empty, consider falling back to the quotejs parsing path instead of just logging an error so the widget still works with a minimal configuration.
  • The updateViaQuoteJs logic assumes the quote.js structure stays stable; it might be safer to handle parse failures by explicitly clearing/hiding the widget or showing a fallback state instead of silently leaving placeholders.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- When `statsDataSource` is set to `api` but `statsApiUrl` is empty, consider falling back to the `quotejs` parsing path instead of just logging an error so the widget still works with a minimal configuration.
- The `updateViaQuoteJs` logic assumes the `quote.js` structure stays stable; it might be safer to handle parse failures by explicitly clearing/hiding the widget or showing a fallback state instead of silently leaving placeholders.

## Individual Comments

### Comment 1
<location path="src/components/widget/LaStats.astro" line_range="98-107" />
<code_context>
+    var STAT_IDS = ["la-online", "la-today-uv", "la-today-pv", "la-yesterday-uv", "la-yesterday-pv", "la-month-pv", "la-total-pv"];
</code_context>
<issue_to_address>
**suggestion:** The hardcoded STAT_IDS array can easily get out of sync with the stats definition above.

Because `stats` and `STAT_IDS` must stay the same order and length, any change to `stats` (add/remove/reorder) also requires a manual update here, which is error‑prone. Consider deriving `STAT_IDS` from `stats` (e.g., passing IDs via `define:vars`) or from the rendered DOM so there’s a single source of truth for metric IDs and ordering.

Suggested implementation:

```
    function setStatValues(values) {
      // Derive STAT_IDS from the rendered DOM so ordering stays in sync with the markup
      var allStatEls = document.querySelectorAll('[data-stat-id]');
      var statIds = [];

      allStatEls.forEach(function(el) {
        var id = el.getAttribute('data-stat-id');
        if (id && statIds.indexOf(id) === -1) {
          statIds.push(id);
        }
      });

      for (var i = 0; i < values.length && i < statIds.length; i++) {
        var els = document.querySelectorAll('[data-stat-id="' + statIds[i] + '"]');
        els.forEach(function(el) { el.textContent = values[i].toLocaleString(); });
      }
    }

```

If the stats ordering is not purely determined by DOM order, consider:
1. Passing an ordered list of stat IDs from the parent via `define:vars` (e.g. `la51StatIds`) and using that instead of deriving from the DOM.
2. Adding a small inline comment near the markup where `data-stat-id` attributes are defined, documenting that their DOM order determines the mapping to the stats array used by this script.
</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 +98 to +107
var STAT_IDS = ["la-online", "la-today-uv", "la-today-pv", "la-yesterday-uv", "la-yesterday-pv", "la-month-pv", "la-total-pv"];

function setStatValues(values) {
for (var i = 0; i < values.length && i < STAT_IDS.length; i++) {
var els = document.querySelectorAll('[data-stat-id="' + STAT_IDS[i] + '"]');
els.forEach(function(el) { el.textContent = values[i].toLocaleString(); });
}
}

function updateViaQuoteJs() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: 硬编码的 STAT_IDS 数组很容易与上面的统计定义失去同步。

由于 statsSTAT_IDS 必须保持相同的顺序和长度,任何对 stats 的修改(添加/删除/重新排序)都需要在这里手动更新,这很容易出错。可以考虑从 stats 推导出 STAT_IDS(例如通过 define:vars 传入 ID),或者从渲染后的 DOM 中推导,这样度量 ID 和排序就只有一个可信来源。

建议实现:

    function setStatValues(values) {
      // 从渲染后的 DOM 中推导 STAT_IDS,使其顺序和标记保持同步
      var allStatEls = document.querySelectorAll('[data-stat-id]');
      var statIds = [];

      allStatEls.forEach(function(el) {
        var id = el.getAttribute('data-stat-id');
        if (id && statIds.indexOf(id) === -1) {
          statIds.push(id);
        }
      });

      for (var i = 0; i < values.length && i < statIds.length; i++) {
        var els = document.querySelectorAll('[data-stat-id="' + statIds[i] + '"]');
        els.forEach(function(el) { el.textContent = values[i].toLocaleString(); });
      }
    }

如果统计项的顺序不仅仅由 DOM 顺序决定,可以考虑:

  1. 通过 define:vars(例如 la51StatIds)从父级传入一个有序的统计 ID 列表,并用它来代替从 DOM 推导。
  2. 在定义 data-stat-id 属性的标记附近添加一小段内联注释,说明它们在 DOM 中的顺序会决定此脚本中 stats 数组的映射关系。
Original comment in English

suggestion: The hardcoded STAT_IDS array can easily get out of sync with the stats definition above.

Because stats and STAT_IDS must stay the same order and length, any change to stats (add/remove/reorder) also requires a manual update here, which is error‑prone. Consider deriving STAT_IDS from stats (e.g., passing IDs via define:vars) or from the rendered DOM so there’s a single source of truth for metric IDs and ordering.

Suggested implementation:

    function setStatValues(values) {
      // Derive STAT_IDS from the rendered DOM so ordering stays in sync with the markup
      var allStatEls = document.querySelectorAll('[data-stat-id]');
      var statIds = [];

      allStatEls.forEach(function(el) {
        var id = el.getAttribute('data-stat-id');
        if (id && statIds.indexOf(id) === -1) {
          statIds.push(id);
        }
      });

      for (var i = 0; i < values.length && i < statIds.length; i++) {
        var els = document.querySelectorAll('[data-stat-id="' + statIds[i] + '"]');
        els.forEach(function(el) { el.textContent = values[i].toLocaleString(); });
      }
    }

If the stats ordering is not purely determined by DOM order, consider:

  1. Passing an ordered list of stat IDs from the parent via define:vars (e.g. la51StatIds) and using that instead of deriving from the DOM.
  2. Adding a small inline comment near the markup where data-stat-id attributes are defined, documenting that their DOM order determines the mapping to the stats array used by this script.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant