Skip to content

docs: New version reminder notification modal#53777

Closed
Wxh16144 wants to merge 24 commits intoant-design:masterfrom
Wxh16144-forks:wuxh/changelog-modal
Closed

docs: New version reminder notification modal#53777
Wxh16144 wants to merge 24 commits intoant-design:masterfrom
Wxh16144-forks:wuxh/changelog-modal

Conversation

@Wxh16144
Copy link
Member

@Wxh16144 Wxh16144 commented May 8, 2025

中文版模板 / Chinese template

🤔 This is a ...

  • 🆕 New feature
  • 🐞 Bug fix
  • 📝 Site / documentation improvement
  • 📽️ Demo improvement
  • 💄 Component style improvement
  • 🤖 TypeScript definition improvement
  • 📦 Bundle size optimization
  • ⚡️ Performance optimization
  • ⭐️ Feature enhancement
  • 🌐 Internationalization
  • 🛠 Refactoring
  • 🎨 Code style optimization
  • ✅ Test Case
  • 🔀 Branch merge
  • ⏩ Workflow
  • ⌨️ Accessibility improvement
  • ❓ Other (about what?)

🔗 Related Issues

需要等 dumi 支持后才能生效:umijs/dumi#2281

background: #53759

image

  • Describe the source of related requirements, such as links to relevant issue discussions.
  • For example: close #xxxx, fix #xxxx

💡 Background and Solution

内容过长时,添加一个遮罩 (如下图
image

内容较少时
image

  • The specific problem to be addressed.
  • List the final API implementation and usage if needed.
  • If there are UI/interaction changes, consider providing screenshots or GIFs.

📝 Change Log

Language Changelog
🇺🇸 English --
🇨🇳 Chinese --

@bolt-new-by-stackblitz
Copy link

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 8, 2025

📝 Walkthrough

Summary by CodeRabbit

  • 新功能

    • 新增版本更新弹窗,自动检测并展示当前版本的更新日志,支持中英文切换与溢出渐隐效果。
    • 弹窗内支持跳转至完整更新日志页面,且在中文用户访问非中国大陆镜像站时,提供一键跳转镜像站按钮。
  • 优化

    • 移除旧的镜像推荐弹窗脚本,改为集成在新版本弹窗中,提升用户体验。
    • 增加对 Markdown 文件作为 React 组件的类型支持,便于直接导入与渲染。
  • 构建/脚本

    • 新增自动提取当前版本更新日志的脚本,优化 changelog 生成流程。
    • .gitignore 增加对最新 changelog 相关文件的忽略规则。
    • 升级 dumi 依赖至新版本。

Summary by CodeRabbit

  • 新功能
    • 新增版本更新弹窗,自动检测新版本并展示中英文本地化的更新日志,支持溢出渐变提示、镜像站跳转及完整日志页面访问。
  • 杂项
    • 更新 .gitignore,忽略最新 changelog 相关文件。
    • 新增 changelog 提取脚本,自动生成当前版本的中英文 changelog 摘要文件。
    • 优化 changelog 检查脚本,增加最新 changelog 生成步骤。
    • 升级 dumi 依赖为指定 URL 版本。
  • 移除
    • 删除旧版中国镜像推荐弹窗脚本,相关功能迁移至新版弹窗组件。
  • 配置
    • 配置中新增 antd 版本号 meta 标签,移除旧版镜像弹窗脚本加载配置。

Walkthrough

本次变更新增了一个用于发布新版本公告的 React 组件 ChangeModal,并将其集成到全局布局中。新增脚本实现了自动提取当前版本变更日志的功能,同时更新了 .gitignorepackage.json 以支持相关流程和文件管理。旧的中国镜像推荐弹窗脚本被移除,其功能已整合到新弹窗中。

Changes

文件/路径 变更摘要
.dumi/theme/common/ChangeModal.tsx 新增 ChangeModal 组件,负责检测新版本并弹窗展示多语言变更日志,支持本地化、溢出检测、镜像跳转等功能。
.dumi/theme/layouts/GlobalLayout.tsx 引入并渲染 ChangeModal 组件,使其随全局布局加载。
scripts/generate-latest-changelog.ts 新增脚本,自动提取当前版本的中英文变更日志片段,生成至 .dumi/preset/ 目录下。
.gitignore 新增忽略规则,排除 .dumi/preset/latest-changelog* 文件。
package.json lint:changelog 脚本新增执行 generate-latest-changelog.ts,实现多脚本串行;升级 dumi 依赖版本。
.dumi/scripts/mirror-modal.js 删除旧的中国镜像推荐弹窗脚本,相关功能迁移至新版 ChangeModal 组件中。
.dumirc.ts 移除旧镜像弹窗脚本配置,新增 meta 标签注入当前版本号。
typings/custom-typings.d.ts 新增 .md 文件模块声明,支持将 Markdown 文件作为 React 组件导入。

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Browser
    participant ChangeModal
    participant localStorage
    participant MarkdownFiles

    Browser->>ChangeModal: 页面加载后渲染
    ChangeModal->>localStorage: 读取 lastVisitedVersion
    ChangeModal->>ChangeModal: 比较当前版本与上次访问版本
    alt 新版本
        ChangeModal->>MarkdownFiles: 加载对应语言 latest-changelog.md
        ChangeModal->>User: 弹窗展示变更日志
        User->>ChangeModal: 关闭弹窗
        ChangeModal->>localStorage: 更新 lastVisitedVersion
    else 不是新版本
        ChangeModal-->>User: 不显示弹窗
    end
Loading

Suggested reviewers

  • thinkasany

Poem

🐇
新版本上线,弹窗闪亮,
变更日志中英双语齐放。
一键跳转中国镜像,
渐变溢出美如画。
代码脚本齐上阵,
小兔子为你把关卡!

Tip

⚡️ Faster reviews with caching
  • CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.

Enjoy the performance boost—your workflow just got faster.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92c872d and ba4fd78.

📒 Files selected for processing (1)
  • scripts/generate-latest-changelog.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/generate-latest-changelog.ts
⏰ Context from checks skipped due to timeout of 90000ms (11)
  • GitHub Check: test-react-latest (dom, 2/2)
  • GitHub Check: test-react-latest (dom, 1/2)
  • GitHub Check: build
  • GitHub Check: test-react-legacy (17, 2/2)
  • GitHub Check: test-react-legacy (17, 1/2)
  • GitHub Check: test-react-legacy (16, 2/2)
  • GitHub Check: test-react-legacy (16, 1/2)
  • GitHub Check: visual-diff snapshot (2/2)
  • GitHub Check: build preview
  • GitHub Check: visual-diff snapshot (1/2)
  • GitHub Check: build
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link
Contributor

github-actions bot commented May 8, 2025

Preview is ready

@github-actions
Copy link
Contributor

github-actions bot commented May 8, 2025

👁 Visual Regression Report for PR #53777 Passed ✅

🎯 Target branch: master (a07d6f1)
📖 View Full Report ↗︎
📖 Alternative Report ↗︎

🎊 Congrats! No visual-regression diff found.

@petercat-assistant
Copy link

Walkthrough

This pull request introduces a new version reminder notification modal to the documentation site. It includes a modal that informs users about the latest version release and provides links to the full changelog and a Chinese mirror. The modal is integrated into the global layout and is triggered based on version comparison logic.

Changes

Files Summary
.dumi/theme/common/ChangeModal.tsx Added a new component for displaying a version reminder modal with overflow detection and language support.
.dumi/theme/layouts/GlobalLayout.tsx Integrated the ChangeModal component into the global layout.
.gitignore Updated to ignore new changelog files.
package.json Modified lint script to include latest changelog generation.
scripts/generate-latest-changelog.ts Added a script to generate the latest changelog files for English and Chinese.
🪧 Tips For further assistance, please describe your question in the comments and @petercat-assistant to start a conversation with me.

@socket-security
Copy link

socket-security bot commented May 8, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License

View full report

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (3)
scripts/generate-latest-changelog.ts (1)

1-56: 建议添加输出目录存在性检查

脚本没有检查输出目录 .dumi/preset 是否存在,如果目录不存在,写入文件时会失败。

建议在写入文件前添加以下代码确保输出目录存在:

 const main = async () => {
+  // 确保输出目录存在
+  await fse.ensureDir(OUTPUT_DIR);
+
   const lastENChangelog = getChangelog(originENChangelog);
   const lastCNChangelog = getChangelog(originCNChangelog);
.dumi/theme/common/ChangeModal.tsx (2)

89-97: 溢出检测中的可读性与鲁棒性问题

  1. containerRef.current?.scrollHeight! 与可选链同时出现,可读性差且在 current 为空时仍会得到 undefined > undefined 的比较,虽然返回 false,但语义不清晰。
  2. debounce 未指定 wait,默认 0,实质上并未节流;浏览器 resize 频率较高时可能造成多次调用。
-const hasOverflow =
-  containerRef.current?.scrollHeight! > containerRef.current?.clientHeight!;
+const el = containerRef.current;
+const hasOverflow = !!el && el.scrollHeight > el.clientHeight;

可同时在 debounce 中加入 100~200 ms 的等待时间来减少高频触发。


149-163: hasNewVersion 依赖未参与 showModal 逻辑

useEffect 监听了 hasNewVersion,但在内部 showModal 没有检查。若首次渲染 hasNewVersionfalse,而后又变成 true,仍会注册 load 事件;建议在 showModaluseEffect 外层提前判空:

React.useEffect(() => {
-  let timer: NodeJS.Timeout | null = null;
+  if (!hasNewVersion) return;
+
+  let timer: NodeJS.Timeout | null = null;
   function showModal() {

这样能避免不必要的事件监听。

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d354ce0 and 6c5d03f.

📒 Files selected for processing (5)
  • .dumi/theme/common/ChangeModal.tsx (1 hunks)
  • .dumi/theme/layouts/GlobalLayout.tsx (2 hunks)
  • .gitignore (1 hunks)
  • package.json (1 hunks)
  • scripts/generate-latest-changelog.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
scripts/generate-latest-changelog.ts (1)
typings/custom-typings.d.ts (1)
  • version (19-19)
⏰ Context from checks skipped due to timeout of 90000ms (16)
  • GitHub Check: test-react-legacy (17, 2/2)
  • GitHub Check: test-react-legacy (17, 1/2)
  • GitHub Check: test-react-legacy (16, 2/2)
  • GitHub Check: test-react-latest (dom, 2/2)
  • GitHub Check: test-react-legacy (16, 1/2)
  • GitHub Check: test-react-latest (dom, 1/2)
  • GitHub Check: test lib/es module (es, 2/2)
  • GitHub Check: test lib/es module (es, 1/2)
  • GitHub Check: build
  • GitHub Check: visual-diff snapshot (2/2)
  • GitHub Check: visual-diff snapshot (1/2)
  • GitHub Check: build preview
  • GitHub Check: lint
  • GitHub Check: size
  • GitHub Check: build
  • GitHub Check: WIP
🔇 Additional comments (8)
.gitignore (1)

62-62: 添加新的 gitignore 规则以忽略生成的更新日志文件

这条规则正确地忽略了由新脚本 scripts/generate-latest-changelog.ts 生成的更新日志文件。这些文件是在构建时生成的,不应该被 Git 跟踪。

package.json (1)

65-65: 更新 lint:changelog 脚本以包含新的更新日志生成脚本

脚本现在包含两个命令:原有的组件更新日志生成脚本和新增的最新版本更新日志提取脚本。这种集成方式正确,确保了在相关的生命周期中同时执行两个脚本。

.dumi/theme/layouts/GlobalLayout.tsx (2)

28-28: 导入新的版本提醒模态框组件

正确导入了 ChangeModal 组件以在全局布局中使用。


216-219: 将版本提醒模态框集成到全局布局中

App 组件内添加 ChangeModal 组件的位置正确,确保它在整个应用中都可用。

scripts/generate-latest-changelog.ts (4)

1-6: 正确导入必要的依赖和版本信息

导入了所需的模块和从 package.json 中获取当前版本号,这是正确的做法。


7-11: 设置路径常量并读取更新日志文件

代码正确设置了根路径和输出目录,并读取了英文和中文更新日志文件。


13-33: 实现提取特定版本更新日志的函数

函数实现逻辑清晰,能够正确提取当前版本的更新日志内容。注释有助于理解代码功能,正则表达式的使用也很恰当。


46-56: 适当的错误处理和日志输出

主函数执行和错误处理实现得很好,包括成功日志和失败时的进程退出。

@pkg-pr-new
Copy link

pkg-pr-new bot commented May 8, 2025

@codecov
Copy link

codecov bot commented May 8, 2025

Bundle Report

Bundle size has no change ✅

@codecov
Copy link

codecov bot commented May 8, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 100.00%. Comparing base (a07d6f1) to head (dfcc5f8).
Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff            @@
##            master    #53777   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          774       774           
  Lines        14012     14012           
  Branches      3711      3711           
=========================================
  Hits         14012     14012           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Co-authored-by: codefactor-io[bot] <47775046+codefactor-io[bot]@users.noreply.github.com>
Signed-off-by: 𝑾𝒖𝒙𝒉 <wxh16144@qq.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
.dumi/theme/common/ChangeModal.tsx (2)

20-27: 避免 SSR 崩溃:在访问 localStorage 之前判断运行环境

该段代码仍直接在函数顶层访问 localStorage,若在 dumi 的静态构建或任何 SSR 场景中执行,会因为 window/localStorage 不存在而抛出 ReferenceError。请参考之前的保护写法,先判断 typeof window !== 'undefined' && window.localStorage 后再读取或写入。


125-129: navigator / window 直接引用缺少环境判断

组件初始化阶段直接读取 navigator.languageswindow.location.hostname,SSR 时会导致错误。与之前评论相同,建议包裹 typeof window !== 'undefined' 判断或延迟到 useEffect 中执行。

🧹 Nitpick comments (2)
.dumi/theme/common/ChangeModal.tsx (2)

141-147: 按静态分析建议去掉可推断类型并使用 const

lastVersion 的类型可由初始化值推断,无需显式注解;lastVisitedVersion 从未重新赋值,应声明为 const 以表达不可变语义。

-    let lastVersion: string = '1.0.0';
-    let lastVisitedVersion = getLastVisitedVersion();
+    let lastVersion = '1.0.0'; // 推断为 string
+    const lastVisitedVersion = getLastVisitedVersion();
🧰 Tools
🪛 Biome (1.9.4)

[error] 143-143: This type annotation is trivially inferred from its initialization.

Safe fix: Remove the type annotation.

(lint/style/noInferrableTypes)


[error] 144-145: This let declares a variable that is only assigned once.

'lastVisitedVersion' is never reassigned.

Safe fix: Use const instead.

(lint/style/useConst)

🪛 GitHub Check: CodeFactor

[warning] 143-143: .dumi/theme/common/ChangeModal.tsx#L143
'lastVisitedVersion' is never reassigned. Use 'const' instead. (prefer-const)


93-94: 溢出判定缺乏空值保护,可能触发运行时异常

当前写法依赖非空断言 !,在 containerRef.current 为空时表达式会变成 undefined > undefined,虽不会抛错但逻辑含糊。可以在取值前先判断引用是否存在,让表达式更清晰健壮。

-        const hasOverflow =
-          containerRef.current?.scrollHeight! > containerRef.current?.clientHeight!;
+        const node = containerRef.current;
+        const hasOverflow = node ? node.scrollHeight > node.clientHeight : false;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c5d03f and 8f2f2a1.

📒 Files selected for processing (1)
  • .dumi/theme/common/ChangeModal.tsx (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
.dumi/theme/common/ChangeModal.tsx

[error] 143-143: This type annotation is trivially inferred from its initialization.

Safe fix: Remove the type annotation.

(lint/style/noInferrableTypes)


[error] 144-145: This let declares a variable that is only assigned once.

'lastVisitedVersion' is never reassigned.

Safe fix: Use const instead.

(lint/style/useConst)

🪛 GitHub Check: CodeFactor
.dumi/theme/common/ChangeModal.tsx

[warning] 143-143: .dumi/theme/common/ChangeModal.tsx#L143
'lastVisitedVersion' is never reassigned. Use 'const' instead. (prefer-const)

⏰ Context from checks skipped due to timeout of 90000ms (12)
  • GitHub Check: test-react-legacy (17, 2/2)
  • GitHub Check: test-react-legacy (17, 1/2)
  • GitHub Check: test-react-legacy (16, 2/2)
  • GitHub Check: test-react-legacy (16, 1/2)
  • GitHub Check: test-react-latest (dom, 2/2)
  • GitHub Check: test-react-latest (dom, 1/2)
  • GitHub Check: test lib/es module (es, 1/2)
  • GitHub Check: visual-diff snapshot (2/2)
  • GitHub Check: build
  • GitHub Check: visual-diff snapshot (1/2)
  • GitHub Check: build
  • GitHub Check: WIP

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
.dumi/theme/common/ChangeModal.tsx (3)

125-129: ⚠️ Potential issue

添加环境检测以避免在 SSR/构建阶段使用 navigator 和 window 引发错误

hasChinesePreferenceisChineseMirror 在组件渲染时顶层直接引用 navigatorwindow,同样会在 SSR 时导致崩溃。请改为带保护的写法,例如:

- const hasChinesePreference = navigator.languages.some((lang) => lang.startsWith('zh'));
- const isChineseMirror = ['ant-design.gitee.io', 'ant-design.antgroup.com'].includes(
-   window.location.hostname,
- );
+ const hasChinesePreference =
+   typeof navigator !== 'undefined' &&
+   navigator.languages?.some((lang) => lang.startsWith('zh'));
+ const isChineseMirror =
+   typeof window !== 'undefined' &&
+   ['ant-design.gitee.io', 'ant-design.antgroup.com'].includes(window.location.hostname);

100-112: 🛠️ Refactor suggestion

补充取消防抖以避免潜在内存泄漏

checkOverflow 使用了 lodash/debounce,在组件卸载时仅断开了 ResizeObserver,但未调用 checkOverflow.cancel(),会导致定时器仍在运行且持有引用。建议在清理函数中增加取消调用:

 React.useEffect(() => {
   const container = containerRef.current;
   if (!container) return;

   checkOverflow();

   const resizeObserver = new ResizeObserver(checkOverflow);
   resizeObserver.observe(container);

   return () => {
-    resizeObserver.disconnect();
+    resizeObserver.disconnect();
+    // 取消防抖定时器,防止组件卸载后仍保留引用
+    checkOverflow.cancel();
   };
 }, []);

20-28: ⚠️ Potential issue

修复 SSR 环境下直接访问 localStorage 导致的构建错误

当前 getLastVisitedVersion 在顶层直接调用 localStorage,会在 Dumi 的静态构建或任何 SSR 场景下因 window/localStorage 不存在抛出 ReferenceError。请在访问前添加运行时检测:

 function getLastVisitedVersion() {
-  const fallbackVersion = '1.0.0';
-  const lastVisitedVersion = localStorage.getItem(STORAGE_KEY);
+  /* SSR 安全:仅在浏览器环境下访问 localStorage */
+  if (typeof window === 'undefined' || !window.localStorage) {
+    return '0.0.0';
+  }
+  const fallbackVersion = '1.0.0';
+  const lastVisitedVersion = window.localStorage.getItem(STORAGE_KEY);
   if (lastVisitedVersion && VER_REGEX.test(lastVisitedVersion)) {
     return lastVisitedVersion;
   }
-  localStorage.setItem(STORAGE_KEY, fallbackVersion);
+  window.localStorage.setItem(STORAGE_KEY, fallbackVersion);
   return fallbackVersion;
 }
🧹 Nitpick comments (2)
.dumi/theme/common/ChangeModal.tsx (2)

142-143: 移除可推断类型的冗余注解

let lastVersion: string = '1.0.0'; 中的类型注解可被 TS 推断,建议简化:

-    let lastVersion: string = '1.0.0';
+    let lastVersion = '1.0.0';
🧰 Tools
🪛 Biome (1.9.4)

[error] 143-143: This type annotation is trivially inferred from its initialization.

Safe fix: Remove the type annotation.

(lint/style/noInferrableTypes)


10-13: 优化 Markdown 模块导入的类型声明,避免使用 @ts-ignore

当前为导入 .md?type=dumi-component 添加了 // @ts-ignore,建议新增全局类型声明(例如在 typings.d.ts):

declare module '*.md?type=dumi-component' {
  const Component: React.ComponentType;
  export default Component;
}

然后移除文件内的 @ts-ignore 注释,以保持类型安全和代码可维护性。

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f2f2a1 and 4aacc89.

📒 Files selected for processing (1)
  • .dumi/theme/common/ChangeModal.tsx (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
.dumi/theme/common/ChangeModal.tsx

[error] 143-143: This type annotation is trivially inferred from its initialization.

Safe fix: Remove the type annotation.

(lint/style/noInferrableTypes)

⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: test-react-latest (dom, 2/2)
  • GitHub Check: test-react-latest (dom, 1/2)
  • GitHub Check: test-react-legacy (17, 2/2)
  • GitHub Check: test-react-legacy (17, 1/2)
  • GitHub Check: test-react-legacy (16, 2/2)
  • GitHub Check: test-react-legacy (16, 1/2)
  • GitHub Check: visual-diff snapshot (2/2)
  • GitHub Check: visual-diff snapshot (1/2)
  • GitHub Check: WIP

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (5)
.dumi/theme/common/ChangeModal.tsx (5)

21-29: 修复 SSR 兼容性问题

直接访问 localStorage 在服务器端渲染(SSR)环境中会导致错误,因为 localStorage 对象在服务器端不存在。


92-99: 防止内存泄漏,需要清理防抖函数

debounce 创建的函数在组件卸载时需要被取消,否则可能导致内存泄漏和组件卸载后的无效状态更新。

  return () => {
    resizeObserver.disconnect();
+   checkOverflow.cancel();
  };

Also applies to: 111-112


128-131: 修复 SSR 兼容性问题

直接访问 navigatorwindow 对象在服务器端渲染(SSR)环境中会导致错误。需要添加环境检查。

-const hasChinesePreference = navigator.languages.some((lang) => lang.startsWith('zh')) || utils.isZhCN(pathname);;
-const isChineseMirror = ['ant-design.gitee.io', 'ant-design.antgroup.com'].includes(
-  window.location.hostname,
-);
+const hasChinesePreference = 
+  (typeof navigator !== 'undefined' && 
+   navigator.languages?.some((lang) => lang.startsWith('zh'))) || 
+   utils.isZhCN(pathname);
+const isChineseMirror = 
+  typeof window !== 'undefined' && 
+  ['ant-design.gitee.io', 'ant-design.antgroup.com'].includes(
+    window.location.hostname,
+  );

135-137: 修复 SSR 兼容性问题

直接访问 localStorage 在服务器端渲染(SSR)环境中会导致错误。

-  function handleClose() {
-    localStorage.setItem(STORAGE_KEY, currentVersion);
-    updateOpen(false);
-  }
+  function handleClose() {
+    if (typeof window !== 'undefined') {
+      localStorage.setItem(STORAGE_KEY, currentVersion);
+    }
+    updateOpen(false);
+  }

202-209: 修正 Modal 样式属性使用

Ant Design 的 Modal 组件不支持 styles 属性,应使用官方提供的 maskStylebodyStyle

-      styles={{
-        mask: {
-          backdropFilter: 'blur(2px)',
-        },
-        body: {
-          padding: 0,
-        },
-      }}
+      maskStyle={{ backdropFilter: 'blur(2px)' }}
+      bodyStyle={{ padding: 0 }}
🧹 Nitpick comments (3)
.dumi/theme/common/ChangeModal.tsx (3)

128-128: 修复代码中的多余分号

这行代码末尾有两个分号。

-const hasChinesePreference = navigator.languages.some((lang) => lang.startsWith('zh')) || utils.isZhCN(pathname);;
+const hasChinesePreference = navigator.languages.some((lang) => lang.startsWith('zh')) || utils.isZhCN(pathname);

144-146: 移除不必要的类型注解

这里的类型注解是多余的,因为它可以从初始化值推断出来。

-  const checkVersion = useEvent(async () => {
-    let lastVersion: string = '1.0.0';
+  const checkVersion = useEvent(async () => {
+    let lastVersion = '1.0.0';
🧰 Tools
🪛 Biome (1.9.4)

[error] 146-146: This type annotation is trivially inferred from its initialization.

Safe fix: Remove the type annotation.

(lint/style/noInferrableTypes)


219-223: 考虑使用更灵活的镜像切换逻辑

当前代码硬编码了国内镜像地址。考虑使用配置项或环境变量来定义镜像URL,以便将来更改时更加灵活。

-                  window.location.href = window.location.href.replace(
-                    window.location.host,
-                    'ant-design.antgroup.com',
-                  );
+                  const CHINA_MIRROR = 'ant-design.antgroup.com';
+                  window.location.href = window.location.href.replace(
+                    window.location.host,
+                    CHINA_MIRROR,
+                  );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4aacc89 and 5239ddf.

📒 Files selected for processing (1)
  • .dumi/theme/common/ChangeModal.tsx (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
.dumi/theme/common/ChangeModal.tsx

[error] 146-146: This type annotation is trivially inferred from its initialization.

Safe fix: Remove the type annotation.

(lint/style/noInferrableTypes)

⏰ Context from checks skipped due to timeout of 90000ms (16)
  • GitHub Check: test-react-latest (dom, 2/2)
  • GitHub Check: test-react-latest (dom, 1/2)
  • GitHub Check: test lib/es module (es, 2/2)
  • GitHub Check: test lib/es module (es, 1/2)
  • GitHub Check: test-react-legacy (17, 2/2)
  • GitHub Check: test-react-legacy (17, 1/2)
  • GitHub Check: build
  • GitHub Check: test-react-legacy (16, 2/2)
  • GitHub Check: test-react-legacy (16, 1/2)
  • GitHub Check: lint
  • GitHub Check: visual-diff snapshot (2/2)
  • GitHub Check: visual-diff snapshot (1/2)
  • GitHub Check: build preview
  • GitHub Check: size
  • GitHub Check: build
  • GitHub Check: WIP

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (4)
.dumi/theme/common/ChangeModal.tsx (4)

205-212: Modal 并不支持 styles 属性,仍会导致样式无法生效

该问题在上一轮已提及,仍未修复。请改用 maskStylebodyStyle

-      styles={{
-        mask: {
-          backdropFilter: 'blur(2px)',
-        },
-        body: {
-          padding: 0,
-        },
-      }}
+      maskStyle={{ backdropFilter: 'blur(2px)' }}
+      bodyStyle={{ padding: 0 }}

21-29: 仍然缺少对 localStorage 的环境检测

虽然当前调用路径大概率只在浏览器端触发,但公共方法最好自带防护,避免未来复用时踩坑。建议参考此前评论加入 typeof window !== 'undefined' 判断。


182-195: 🛠️ Refactor suggestion

useEffect 中缺少 checkVersion 依赖且仍未做 SSR 保护

  1. 依赖数组为空会造成闭包引用旧的 checkVersion
  2. window 在 SSR 不存在,仍可能导致构建失败。
-  React.useEffect(() => {
+  React.useEffect(() => {
     let timer: NodeJS.Timeout | null = null;
     function showModal() {
       timer = setTimeout(() => {
         checkVersion().then(updateOpen);
       }, 1000);
     }
 
-    window.addEventListener('load', showModal);
+    if (typeof window !== 'undefined') {
+      window.addEventListener('load', showModal);
+    }
     return function cleanup() {
-      window.removeEventListener('load', showModal);
+      if (typeof window !== 'undefined') {
+        window.removeEventListener('load', showModal);
+      }
       timer && clearTimeout(timer);
     };
-  }, []);
+  }, [checkVersion]);

这样既能保证最新闭包,又避免 SSR 崩溃。


131-136: ⚠️ Potential issue

SSR 环境下直接访问 navigator / window,渲染阶段会抛 ReferenceError

组件首次渲染(包括静态构建/SSR 阶段)就会执行这段代码,navigatorwindow 在服务端均不存在,页面将直接构建失败。建议在运行时加环境判断,或推迟到 useEffect 中运行。

-  const hasChinesePreference = navigator.languages.some((lang) => lang.startsWith('zh')) || utils.isZhCN(pathname);;
-  const isChineseMirror = ['ant-design.gitee.io', 'ant-design.antgroup.com'].includes(
-    window.location.hostname,
-  );
+  const hasChinesePreference =
+    (typeof navigator !== 'undefined' &&
+      navigator.languages?.some((lang) => lang.startsWith('zh'))) ||
+    utils.isZhCN(pathname);
+
+  const isChineseMirror =
+    typeof window !== 'undefined' &&
+    ['ant-design.gitee.io', 'ant-design.antgroup.com'].includes(window.location.hostname);

如需进一步优化,可包裹在 React.useMemo 中避免重复计算。

🧹 Nitpick comments (1)
.dumi/theme/common/ChangeModal.tsx (1)

148-149: 无必要的类型注解,可移除以简化代码

lastVersion 的类型可由初始化值推断,静态分析已给出提示。

-    let lastVersion: string = '1.0.0';
+    let lastVersion = '1.0.0';
🧰 Tools
🪛 Biome (1.9.4)

[error] 149-149: This type annotation is trivially inferred from its initialization.

Safe fix: Remove the type annotation.

(lint/style/noInferrableTypes)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5239ddf and 846e83d.

📒 Files selected for processing (2)
  • .dumi/theme/common/ChangeModal.tsx (1 hunks)
  • scripts/generate-latest-changelog.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/generate-latest-changelog.ts
🧰 Additional context used
🪛 Biome (1.9.4)
.dumi/theme/common/ChangeModal.tsx

[error] 149-149: This type annotation is trivially inferred from its initialization.

Safe fix: Remove the type annotation.

(lint/style/noInferrableTypes)

⏰ Context from checks skipped due to timeout of 90000ms (16)
  • GitHub Check: test-react-legacy (17, 2/2)
  • GitHub Check: test lib/es module (es, 2/2)
  • GitHub Check: test-react-legacy (17, 1/2)
  • GitHub Check: test-react-legacy (16, 2/2)
  • GitHub Check: test lib/es module (es, 1/2)
  • GitHub Check: test-react-legacy (16, 1/2)
  • GitHub Check: build
  • GitHub Check: lint
  • GitHub Check: test-react-latest (dom, 2/2)
  • GitHub Check: test-react-latest (dom, 1/2)
  • GitHub Check: visual-diff snapshot (2/2)
  • GitHub Check: visual-diff snapshot (1/2)
  • GitHub Check: build preview
  • GitHub Check: size
  • GitHub Check: build
  • GitHub Check: WIP

@coderabbitai coderabbitai bot requested a review from afc163 May 9, 2025 05:14
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
.dumi/theme/common/ChangeModal.tsx (2)

21-29: 确保 localStorage 在 SSR 环境下的安全访问

直接使用 localStorage 在服务器端渲染(SSR)环境中会导致运行时错误,因为服务器端不存在 window 对象。尽管已有关于此问题的评论,但代码仍未修复。

建议添加运行时检测,如下所示:

function getLastVisitedVersion() {
+  /* istanbul ignore next */
+  if (typeof window === 'undefined' || !window.localStorage) {
+    // SSR / 构建阶段:返回一个固定版本,避免崩溃
+    return '0.0.0';
+  }
+
  const fallbackVersion = '1.0.0';
-  const lastVisitedVersion = localStorage.getItem(STORAGE_KEY);
+  const lastVisitedVersion = window.localStorage.getItem(STORAGE_KEY);
  if (lastVisitedVersion && VER_REGEX.test(lastVisitedVersion)) {
    return lastVisitedVersion;
  }
-  localStorage.setItem(STORAGE_KEY, fallbackVersion);
+  window.localStorage.setItem(STORAGE_KEY, fallbackVersion);
  return fallbackVersion;
}

211-218: 修正 Modal 样式属性使用,避免无效的 styles 配置

Ant Design Modal 不支持 styles 属性,应使用官方提供的 maskStylebodyStyle

-      styles={{
-        mask: {
-          backdropFilter: 'blur(2px)',
-        },
-        body: {
-          padding: 0,
-        },
-      }}
+      maskStyle={{ backdropFilter: 'blur(2px)' }}
+      bodyStyle={{ padding: 0 }}
🧹 Nitpick comments (4)
.dumi/theme/common/ChangeModal.tsx (4)

153-153: 移除冗余类型注解

此处的类型注解可以由初始化值推断出来,建议移除以减少代码冗余。

-    let lastVersion: string = '1.0.0';
+    let lastVersion = '1.0.0';
🧰 Tools
🪛 Biome (1.9.4)

[error] 153-153: This type annotation is trivially inferred from its initialization.

Safe fix: Remove the type annotation.

(lint/style/noInferrableTypes)


201-201: 添加依赖数组中缺少的 checkVersion

虽然 checkVersion 使用 useEvent 包装保证了引用稳定性,但为了代码的清晰性和一致性,建议将其添加到依赖数组中。

-  }, []);
+  }, [checkVersion]);

115-115: 添加 checkOverflow 的依赖项

useEffect 的依赖数组中应包含 checkOverflow,以避免潜在的闭包问题。

-  }, []);
+  }, [checkOverflow]);

99-99: 优化 debounce 的 deps 数组

useMemo 创建的 debounce 函数依赖数组为空,这通常没问题,但明确添加 setIsOverflowing 作为依赖可以使代码更加清晰。

-    [],
+    [setIsOverflowing],
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c54095a and 8f67a8a.

📒 Files selected for processing (2)
  • .dumi/theme/common/ChangeModal.tsx (1 hunks)
  • package.json (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json
🧰 Additional context used
🪛 Biome (1.9.4)
.dumi/theme/common/ChangeModal.tsx

[error] 153-153: This type annotation is trivially inferred from its initialization.

Safe fix: Remove the type annotation.

(lint/style/noInferrableTypes)

🪛 ESLint
.dumi/theme/common/ChangeModal.tsx

[error] 108-108: ResizeObserver is not supported in op_mini all, KaiOS 2.5

(compat/compat)


[error] 155-159: fetch is not supported in op_mini all

(compat/compat)

⏰ Context from checks skipped due to timeout of 90000ms (12)
  • GitHub Check: test-react-legacy (17, 2/2)
  • GitHub Check: test-react-legacy (17, 1/2)
  • GitHub Check: test-react-legacy (16, 2/2)
  • GitHub Check: test-react-legacy (16, 1/2)
  • GitHub Check: test-react-latest (dom, 2/2)
  • GitHub Check: test-react-latest (dom, 1/2)
  • GitHub Check: build
  • GitHub Check: visual-diff snapshot (2/2)
  • GitHub Check: build preview
  • GitHub Check: visual-diff snapshot (1/2)
  • GitHub Check: build
  • GitHub Check: WIP

@coderabbitai coderabbitai bot requested a review from thinkasany May 9, 2025 05:41
@Wxh16144
Copy link
Member Author

Wxh16144 commented May 9, 2025

image

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (4)
.dumi/theme/common/ChangeModal.tsx (4)

141-144: ⚠️ Potential issue

在 handleClose 中添加 localStorage 环境检查

getLastVisitedVersion 函数类似,handleClose 中也应先检查 localStorage 是否可用。

function handleClose() {
+  if (typeof window === 'undefined' || !window.localStorage) {
+    updateOpen(false);
+    return;
+  }
-  localStorage.setItem(STORAGE_KEY, currentVersion);
+  window.localStorage.setItem(STORAGE_KEY, currentVersion);
  updateOpen(false);
}

21-29: ⚠️ Potential issue

注意避免直接访问 localStorage,增加 SSR 环境判断

该函数直接访问 localStorage,会在服务端渲染 (SSR) 环境下导致错误。需要先检查运行环境。

function getLastVisitedVersion() {
+  if (typeof window === 'undefined' || !window.localStorage) {
+    return '0.0.0'; // SSR 环境下返回默认值
+  }
  const fallbackVersion = '1.0.0';
-  const lastVisitedVersion = localStorage.getItem(STORAGE_KEY);
+  const lastVisitedVersion = window.localStorage.getItem(STORAGE_KEY);
  if (lastVisitedVersion && VER_REGEX.test(lastVisitedVersion)) {
    return lastVisitedVersion;
  }
-  localStorage.setItem(STORAGE_KEY, fallbackVersion);
+  window.localStorage.setItem(STORAGE_KEY, fallbackVersion);
  return fallbackVersion;
}

190-211: ⚠️ Potential issue

优化 useEffect 的依赖和事件监听

该 useEffect 中使用了 checkVersion 函数但未将其添加到依赖数组中,可能导致闭包问题。

  React.useEffect(() => {
    let timer: NodeJS.Timeout | null = null;
    let isLoadListenerAttached = false;
    function showModal() {
      timer = setTimeout(() => {
        checkVersion().then(updateOpen);
      }, 1000);
    }

    if (typeof window !== 'undefined') {
      if (document.readyState === 'complete') {
        showModal();
      } else {
        isLoadListenerAttached = true;
        window.addEventListener('load', showModal);
      }
    }
    return function cleanup() {
      isLoadListenerAttached && window.removeEventListener('load', showModal);
      timer && clearTimeout(timer);
    };
-  }, []);
+  }, [checkVersion]);

221-228: ⚠️ Potential issue

修正 Modal 样式属性,避免使用不支持的 styles 配置

Ant Design Modal 组件不支持 styles 属性,应使用官方提供的 maskStylebodyStyle

-      styles={{
-        mask: {
-          backdropFilter: 'blur(2px)',
-        },
-        body: {
-          padding: 0,
-        },
-      }}
+      maskStyle={{ backdropFilter: 'blur(2px)' }}
+      bodyStyle={{ padding: 0 }}
🧹 Nitpick comments (2)
.dumi/theme/common/ChangeModal.tsx (2)

90-98: 考虑添加 useCallback 并处理依赖关系

debounce 创建的函数在每次渲染时都会重新创建,应使用 useCallback 包装并添加正确的依赖项。

  const checkOverflow = React.useMemo(
    () =>
      debounce(() => {
        const hasOverflow =
          containerRef.current?.scrollHeight! > containerRef.current?.clientHeight!;
        setIsOverflowing(hasOverflow);
-      }),
+      }, 200),
    [],
  );

此外,建议考虑使用 useCallback 替代 useMemo 来创建函数,更符合 React hooks 最佳实践。

🧰 Tools
🪛 GitHub Check: CodeFactor

[notice] 94-94: .dumi/theme/common/ChangeModal.tsx#L94
Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong. (typescript-eslint/no-non-null-asserted-optional-chain)


[notice] 94-94: .dumi/theme/common/ChangeModal.tsx#L94
Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong. (typescript-eslint/no-non-null-asserted-optional-chain)


238-243: 优化中国镜像跳转逻辑

当前跳转方式会导致页面完全刷新,建议考虑使用 React Router 的方式导航,保持应用状态。

-                  window.location.href = window.location.href.replace(
-                    window.location.host,
-                    'ant-design.antgroup.com',
-                  );
+                  // 使用正则替换域名部分,保留路径和查询参数
+                  const newUrl = window.location.href.replace(
+                    /^(https?:\/\/)([^\/]+)(\/.*)?$/,
+                    '$1ant-design.antgroup.com$3'
+                  );
+                  window.location.href = newUrl;

如果确实需要保持站点状态,则可以考虑:

// 如果使用 react-router,可以尝试:
const { pathname, search } = window.location;
navigate(`https://ant-design.antgroup.com${pathname}${search}`);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 50d60b2 and c3a054a.

📒 Files selected for processing (1)
  • .dumi/theme/common/ChangeModal.tsx (1 hunks)
🧰 Additional context used
🪛 GitHub Check: CodeFactor
.dumi/theme/common/ChangeModal.tsx

[notice] 94-94: .dumi/theme/common/ChangeModal.tsx#L94
Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong. (typescript-eslint/no-non-null-asserted-optional-chain)


[notice] 94-94: .dumi/theme/common/ChangeModal.tsx#L94
Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong. (typescript-eslint/no-non-null-asserted-optional-chain)

⏰ Context from checks skipped due to timeout of 90000ms (10)
  • GitHub Check: test-react-legacy (17, 2/2)
  • GitHub Check: test-react-legacy (17, 1/2)
  • GitHub Check: test-react-legacy (16, 2/2)
  • GitHub Check: test-react-legacy (16, 1/2)
  • GitHub Check: test-react-latest (dom, 2/2)
  • GitHub Check: test-react-latest (dom, 1/2)
  • GitHub Check: visual-diff snapshot (2/2)
  • GitHub Check: visual-diff snapshot (1/2)
  • GitHub Check: build preview
  • GitHub Check: WIP

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (7)
.dumi/theme/common/ChangeModal.tsx (7)

159-180: 添加 fetch API 的兼容性处理

fetch API 在某些旧版浏览器中不被支持,应考虑添加 polyfill 或回退到 XMLHttpRequest。

try {
  // eslint-disable-next-line compat/compat
-  await fetch(
-    isChineseMirror
-      ? 'https://registry.npmmirror.com/antd/latest'
-      : 'https://registry.npmjs.org/antd/latest',
-  )
+  const fetchUrl = isChineseMirror
+    ? 'https://registry.npmmirror.com/antd/latest'
+    : 'https://registry.npmjs.org/antd/latest';
+  
+  let response;
+  // 检查是否支持 fetch
+  if (typeof fetch !== 'undefined') {
+    response = await fetch(fetchUrl);
+    if (response.ok) {
+      const data = await response.json();
+      if (typeof data?.version === 'string' && VER_REGEX.test(data.version)) {
+        lastVersion = data.version;
+      }
+    }
+  } else {
+    // 回退到 XMLHttpRequest
+    const data = await new Promise((resolve, reject) => {
+      const xhr = new XMLHttpRequest();
+      xhr.open('GET', fetchUrl);
+      xhr.onload = () => {
+        if (xhr.status >= 200 && xhr.status < 300) {
+          try {
+            resolve(JSON.parse(xhr.responseText));
+          } catch (e) {
+            reject(e);
+          }
+        } else {
+          reject(new Error(`请求失败,状态码: ${xhr.status}`));
+        }
+      };
+      xhr.onerror = () => reject(new Error('网络请求失败'));
+      xhr.send();
+    });
+    
+    if (typeof data?.version === 'string' && VER_REGEX.test(data.version)) {
+      lastVersion = data.version;
+    }
+  }
-    .then((res) => {
-      if (res.ok) return res.json();
-      throw new Error('Network response was not ok');
-    })
-    .then((data) => {
-      if (typeof data?.version === 'string' && VER_REGEX.test(data.version)) {
-        lastVersion = data.version;
-      }
-    });
} catch {
  lastVersion = '1.0.0';
}

19-27: ⚠️ Potential issue

⚠️ localStorage 访问需要 SSR 兼容处理

getLastVisitedVersion 函数直接访问 localStorage,但在服务端渲染 (SSR) 环境中,localStorage 不存在,会导致运行时错误。

建议添加环境检测:

function getLastVisitedVersion() {
+  if (typeof window === 'undefined' || !window.localStorage) {
+    return '0.0.0'; // SSR 环境下返回一个安全的默认值
+  }
  const fallbackVersion = '1.0.0';
-  const lastVisitedVersion = localStorage.getItem(STORAGE_KEY);
+  const lastVisitedVersion = window.localStorage.getItem(STORAGE_KEY);
  if (lastVisitedVersion && VER_REGEX.test(lastVisitedVersion)) {
    return lastVisitedVersion;
  }
-  localStorage.setItem(STORAGE_KEY, fallbackVersion);
+  window.localStorage.setItem(STORAGE_KEY, fallbackVersion);
  return fallbackVersion;
}

91-94: ⚠️ Potential issue

⚠️ 修正非空断言在可选链后的使用

在可选链 (?.) 后使用非空断言 (!) 是不安全的,因为可选链可能返回 undefined

const checkOverflow = React.useMemo(
  () =>
    debounce(() => {
-      const hasOverflow =
-        containerRef.current?.scrollHeight! > containerRef.current?.clientHeight!;
+      const container = containerRef.current;
+      const hasOverflow = container
+        ? container.scrollHeight > container.clientHeight
+        : false;
      setIsOverflowing(hasOverflow);
    }),
  [],
);
🧰 Tools
🪛 GitHub Check: CodeFactor

[notice] 94-94: .dumi/theme/common/ChangeModal.tsx#L94
Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong. (typescript-eslint/no-non-null-asserted-optional-chain)


[notice] 94-94: .dumi/theme/common/ChangeModal.tsx#L94
Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong. (typescript-eslint/no-non-null-asserted-optional-chain)


99-113: 🛠️ Refactor suggestion

useEffect 依赖缺失和 ResizeObserver 兼容性

这个 useEffect 有两个问题:

  1. 依赖了 checkOverflow 但未将其添加到依赖数组
  2. 直接使用 ResizeObserver 没有兼容性处理
// 检测溢出状态
React.useEffect(() => {
  const container = containerRef.current;
  if (!container) return;

  checkOverflow();

  // eslint-disable-next-line compat/compat
-  const resizeObserver = new ResizeObserver(checkOverflow);
-  resizeObserver.observe(container);
+  let resizeObserver: ResizeObserver | null = null;
+  
+  // 安全地使用 ResizeObserver
+  const initObserver = async () => {
+    try {
+      // 如果浏览器不支持,尝试加载 polyfill
+      const ResizeObserverConstructor = window.ResizeObserver || 
+        (await import('resize-observer-polyfill')).default;
+      
+      resizeObserver = new ResizeObserverConstructor(checkOverflow);
+      resizeObserver.observe(container);
+    } catch (e) {
+      console.error('ResizeObserver 初始化失败', e);
+      // 回退方案:使用窗口 resize 事件
+      window.addEventListener('resize', checkOverflow);
+    }
+  };
+  
+  if (typeof window !== 'undefined') {
+    initObserver();
+  }

  return function cleanup() {
-    resizeObserver.disconnect();
+    if (resizeObserver) {
+      resizeObserver.disconnect();
+    } else if (typeof window !== 'undefined') {
+      window.removeEventListener('resize', checkOverflow);
+    }
    checkOverflow.cancel();
  };
-}, []);
+}, [checkOverflow]);

141-144: ⚠️ Potential issue

handleClose 函数中的 localStorage 访问也需要 SSR 保护

getLastVisitedVersion 函数类似,handleClose 函数中的 localStorage 访问也需要添加环境检测。

function handleClose() {
+  if (typeof window === 'undefined' || !window.localStorage) {
+    updateOpen(false);
+    return;
+  }
-  localStorage.setItem(STORAGE_KEY, currentVersion);
+  window.localStorage.setItem(STORAGE_KEY, currentVersion);
  updateOpen(false);
}

195-216: 🛠️ Refactor suggestion

useEffect 依赖项缺失和事件处理优化

useEffect 依赖了 checkVersion 但未将其添加到依赖数组,并且可以改进事件监听逻辑:

React.useEffect(() => {
  let timer: NodeJS.Timeout | null = null;
  let isLoadListenerAttached = false;
  function showModal() {
    timer = setTimeout(() => {
      checkVersion().then(updateOpen);
    }, 1000);
  }

  if (typeof window !== 'undefined') {
    if (document.readyState === 'complete') {
      showModal();
    } else {
      isLoadListenerAttached = true;
      window.addEventListener('load', showModal);
    }
  }
  return function cleanup() {
    isLoadListenerAttached && window.removeEventListener('load', showModal);
    timer && clearTimeout(timer);
  };
-}, []);
+}, [checkVersion]);

226-233: ⚠️ Potential issue

修正 Modal 样式属性使用

Ant Design 的 Modal 组件不支持 styles 属性,应使用官方提供的 maskStylebodyStyle

  <Modal
    title={[locale.title, ' 🎉']}
    open={open}
    width={`min(90vw, 800px)`}
    centered
    onCancel={handleClose}
    onOk={gettingStarted}
-   styles={{
-     mask: {
-       backdropFilter: 'blur(2px)',
-     },
-     body: {
-       padding: 0,
-     },
-   }}
+   maskStyle={{ backdropFilter: 'blur(2px)' }}
+   bodyStyle={{ padding: 0 }}
    okText={locale.gettingStarted}
    footer={(_, { OkBtn }) => (
🧹 Nitpick comments (3)
.dumi/theme/common/ChangeModal.tsx (3)

96-96: debounce 函数缺少延迟参数

debounce 函数没有指定延迟时间,会使用默认的 0ms。这实际上只是将执行推迟到下一个事件循环,而不是真正的防抖动。

const checkOverflow = React.useMemo(
  () =>
-    debounce(() => {
+    debounce(() => {
      // ...检测逻辑
-    }),
+    }, 100), // 添加合理的延迟时间,例如 100ms
  [],
);

244-247: 优化导航处理方式

当前代码直接修改 window.location.href 进行导航,这会导致整个页面重新加载。由于组件已经有 navigate 函数,建议使用 React Router 的方式进行导航:

onClick={() => {
-  window.location.href = window.location.href.replace(
-    window.location.host,
-    'ant-design.antgroup.com',
-  );
+  const newHost = 'ant-design.antgroup.com';
+  const newPathname = window.location.pathname;
+  const newSearch = window.location.search;
+  
+  // 如果是在同一应用内导航,优先使用 navigate
+  if (typeof navigate === 'function') {
+    // 保存目标站点到会话存储
+    sessionStorage.setItem('redirect_to_mirror', 'true');
+    // 使用 navigate 重定向到中转页面
+    navigate('/mirror-redirect');
+  } else {
+    // 回退方案:直接修改 location
+    window.location.href = `${window.location.protocol}//${newHost}${newPathname}${newSearch}`;
+  }
}}

然后创建一个中转页面组件 MirrorRedirect,在 useEffect 中处理实际跳转。


1-270: 总体评价:功能完整但需优化兼容性

此组件实现了版本更新通知的功能,并集成了国内镜像站点推荐,设计上考虑周全。但在代码实现上,主要存在以下几类问题需要解决:

  1. SSR 兼容性:多处直接访问浏览器 API(localStorage、window、navigator 等)需要增加环境检测
  2. 浏览器兼容性:ResizeObserver 和 fetch API 在旧浏览器中不支持,需要添加 polyfill
  3. TypeScript 安全性:修复可选链后的非空断言
  4. React 最佳实践:完善 useEffect 依赖、优化 Modal 属性使用

建议添加单元测试,并考虑将核心逻辑(如版本比较、数据获取)提取到单独的 hooks 中以提高可测试性和可维护性。

🧰 Tools
🪛 GitHub Check: CodeFactor

[notice] 94-94: .dumi/theme/common/ChangeModal.tsx#L94
Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong. (typescript-eslint/no-non-null-asserted-optional-chain)


[notice] 94-94: .dumi/theme/common/ChangeModal.tsx#L94
Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong. (typescript-eslint/no-non-null-asserted-optional-chain)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c7d2ae and 92c872d.

📒 Files selected for processing (3)
  • .dumi/theme/common/ChangeModal.tsx (1 hunks)
  • package.json (2 hunks)
  • typings/custom-typings.d.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json
🧰 Additional context used
🪛 GitHub Check: CodeFactor
.dumi/theme/common/ChangeModal.tsx

[notice] 94-94: .dumi/theme/common/ChangeModal.tsx#L94
Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong. (typescript-eslint/no-non-null-asserted-optional-chain)


[notice] 94-94: .dumi/theme/common/ChangeModal.tsx#L94
Optional chain expressions can return undefined by design: using a non-null assertion is unsafe and wrong. (typescript-eslint/no-non-null-asserted-optional-chain)

⏰ Context from checks skipped due to timeout of 90000ms (11)
  • GitHub Check: test-react-latest (dom, 2/2)
  • GitHub Check: test-react-latest (dom, 1/2)
  • GitHub Check: test-react-legacy (17, 2/2)
  • GitHub Check: build
  • GitHub Check: test-react-legacy (17, 1/2)
  • GitHub Check: test-react-legacy (16, 2/2)
  • GitHub Check: visual-diff snapshot (2/2)
  • GitHub Check: test-react-legacy (16, 1/2)
  • GitHub Check: visual-diff snapshot (1/2)
  • GitHub Check: build preview
  • GitHub Check: WIP
🔇 Additional comments (1)
typings/custom-typings.d.ts (1)

29-33: 新增 markdown 文件类型声明

这个类型声明允许在 TypeScript 代码中直接导入 Markdown 文件作为 React 组件使用,配合 dumi 的功能支持。这很好地为 ChangeModal 组件提供了类型安全的方式来导入和渲染变更日志内容。

注意:此声明依赖于 dumi 的 PR(https://github.com/umijs/dumi/pull/2281)合并后才能正常工作。

@Wxh16144 Wxh16144 changed the title WIP docs: New version reminder notification modal docs: New version reminder notification modal May 13, 2025
Comment on lines +10 to +11
import EN from '../../preset/latest-changelog.en-US.md';
import CN from '../../preset/latest-changelog.zh-CN.md';
Copy link
Member

@afc163 afc163 May 14, 2025

Choose a reason for hiding this comment

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

这个是否会大大增加打包体积?Modal 里放个版本提示和直达链接就好,我建议不放 markdown 内容。

Copy link
Member Author

Choose a reason for hiding this comment

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

是否会大大增加打包体积?

不会大大, dumi 处理成 html 塞进去的。 就一些标签文本体积

@Wxh16144
Copy link
Member Author

image

当前 PR 存档吧,新起一个实现~

@Wxh16144 Wxh16144 closed this May 14, 2025
@Wxh16144 Wxh16144 mentioned this pull request May 19, 2025
17 tasks
Wxh16144 added a commit to Wxh16144-forks/ant-design that referenced this pull request Nov 22, 2025
@Wxh16144 Wxh16144 mentioned this pull request Nov 22, 2025
17 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm This PR has been approved by a maintainer skip-verify-files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants