Skip to content

feat(frontend): add custom navigation items support with drag-and-drop reordering - #4082

Closed
RedwindA wants to merge 3 commits into
QuantumNous:mainfrom
RedwindA:feat/customNav
Closed

feat(frontend): add custom navigation items support with drag-and-drop reordering#4082
RedwindA wants to merge 3 commits into
QuantumNous:mainfrom
RedwindA:feat/customNav

Conversation

@RedwindA

@RedwindA RedwindA commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

📝 变更描述 / Description

实现顶部导航栏自定义功能:

  1. 新增自定义导航项管理界面,支持添加、编辑、删除导航项,最多可添加10个
  2. 支持配置导航项名称、链接地址,自动识别内部路径/外部链接类型
  3. 外部链接支持选择是否在新标签页打开
  4. 支持配置显示位置:通过拖拽自由调整导航项顺序
  5. 已同步完成所有7种语言的翻译覆盖

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix)
  • ✨ 新功能 (New feature)
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自撰写此描述,去除了 AI 原始输出的冗余。
  • 深度理解: 我已完全理解这些更改的工作原理及潜在影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过了测试或手动验证。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

  • 已在本地环境验证导航栏自定义功能正常工作,且所有语言翻译正确显示。
PixPin_2026-04-03_22-47-24.mp4

Summary by CodeRabbit

  • New Features

    • Redesigned header navigation settings with drag-and-drop reordering
    • Create, edit, and delete custom navigation items; max 10 custom items
    • Option to open external links in a new tab; external/internal link types supported
  • Improvements

    • External links now respect the "open in new tab" choice and set safe link behavior
    • Navigation config accepts an ordered items format and migrates legacy configs
    • Added UI translations for navigation management across multiple locales
  • Improvements

    • Pricing entry can require authentication via the navigation settings

@coderabbitai

coderabbitai Bot commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a42bd959-ea17-47a9-abce-7f7055064938

📥 Commits

Reviewing files that changed from the base of the PR and between 79b74c2 and fae973f.

📒 Files selected for processing (7)
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/vi.json
  • web/src/i18n/locales/zh-CN.json
  • web/src/i18n/locales/zh-TW.json
✅ Files skipped from review due to trivial changes (4)
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/zh-CN.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/zh-TW.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/vi.json

Walkthrough

Adds configurable, ordered header navigation with custom items, drag-and-drop reordering (dnd-kit), migration from legacy formats, UI to add/edit/delete items, hooks/app parsing updates for items shape, navigation target handling, and new i18n strings.

Changes

Cohort / File(s) Summary
Dependencies
web/package.json
Added runtime deps: @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities for drag-and-drop.
Migration Helper
web/src/helpers/navMigration.js
New migrateOldFormatToItems(modules) to normalize legacy headerNavModules into an ordered items array with deterministic insertion and ordering rules.
Hooks & App Integration
web/src/App.jsx, web/src/hooks/common/useHeaderBar.js, web/src/hooks/common/useNavigation.js
Support new headerNavModules.items shape; added migration fallback, resolveItems flow, and stricter pricing.requireAuth handling; navigation now built from resolved items.
Navigation Rendering
web/src/components/layout/headerbar/Navigation.jsx
External links compute target from openInNewTab (default '_blank'); rel="noopener noreferrer" set only when target === '_blank'.
Settings UI (Header Nav Management)
web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx
Replaced per-module map with ordered items array; add/edit/delete custom items, URL normalization and external detection, persist { items }, enforce max custom items, and add drag-and-drop reorder using dnd-kit.
Localization
web/src/i18n/locales/{en,fr,ja,ru,vi,zh-CN,zh-TW}.json
Added translation keys for navigation management UI: add/edit, open in new tab, internal vs external link labels, examples, drag-to-reorder hint, and max-limit/validation messages.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User as "User"
  participant UI as "SettingsHeaderNavModules\n(UI + DnD)"
  participant Migrator as "migrateOldFormatToItems\n(navMigration.js)"
  participant Hook as "useNavigation / useHeaderBar"
  participant Store as "StatusContext / Persistence"

  rect rgba(200,200,255,0.5)
  User->>UI: Open header nav settings
  UI->>Migrator: Load existing headerNavModules (migrate if legacy)
  Migrator-->>UI: Return normalized `items`
  User->>UI: Add/Edit/Delete/Drag items (dnd-kit)
  end

  rect rgba(200,255,200,0.5)
  UI->>Store: Save `{ items }` JSON
  Store-->>Hook: Broadcast new headerNavModules
  Hook->>Hook: resolveItems -> build navigation links (incl. pricing.requireAuth)
  Hook-->>UI: Updated nav state applied to header
  UI-->>User: Show updated header/navigation
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • seefs001
  • creamlike1024
  • Calcium-Ion

Poem

🐰 I hop through code and tidy each link,
I drag and I drop so the headers won't sink.
Old maps I migrate, new tabs I tuck,
A carrot for order — the menu's unstuck! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main feature: adding custom navigation items with drag-and-drop reordering capabilities.
Linked Issues check ✅ Passed The PR successfully implements all primary objectives from issue #4051: custom navigation items, internal/external URL support, new-tab option, reordering, configuration UI, and multilingual support.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the custom navigation feature and UI; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (3)
web/src/App.jsx (1)

69-94: Consider centralizing pricingRequireAuth parsing in a shared helper.

This logic now exists in both web/src/App.jsx and web/src/hooks/common/useHeaderBar.js; extracting one shared resolver will prevent future auth-behavior drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/App.jsx` around lines 69 - 94, Extract the parsing logic that
computes pricingRequireAuth into a single shared helper (e.g.,
parseHeaderNavModules or resolvePricingRequireAuth) and use it from both App.jsx
and web/src/hooks/common/useHeaderBar.js; specifically move the JSON.parse +
format-handling branches (the Array.isArray(modules.items) branch, the typeof
modules.pricing === 'boolean' branch, and the modules.pricing?.requireAuth ===
true branch) into that helper, ensure it returns a boolean and swallows/parses
errors the same way, then replace the inline useMemo logic in the
pricingRequireAuth computation with a call to the new helper that accepts
statusState?.status?.HeaderNavModules.
web/src/i18n/locales/zh-TW.json (1)

3001-3001: Consider reusing the existing “open in new tab” source key to avoid duplicate semantics.

Line 3001 adds "在新标签页打开", while the file already has "在新标签页中打开" (Line 731). Keeping one canonical Chinese source key reduces translation drift and future maintenance overhead.

As per coding guidelines, translation keys are Chinese source strings; consolidating semantically identical keys improves consistency in this keying model.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/i18n/locales/zh-TW.json` at line 3001, Remove the duplicate Chinese
source key "在新标签页打开" and reuse the existing canonical key "在新标签页中打开": delete the
newly added key from zh-TW.json, ensure the translation value "在新標籤頁打開" is
present as the value for "在新标签页中打开" (or merge the value if needed), and update
any code or template references that were changed to point to "在新标签页中打开" so only
the single canonical key remains.
web/src/i18n/locales/en.json (1)

3390-3390: Remove duplicated translation key "内置" to avoid key shadowing.

"内置" already exists earlier in this locale file, so re-declaring it here is redundant and can create parser/lint ambiguity.

Proposed cleanup
-    "拖拽可调整顺序": "Drag to reorder",
-    "内置": "Built-in"
+    "拖拽可调整顺序": "Drag to reorder"

As per coding guidelines, translation files should stay clean and lintable with the i18n workflow (bun run i18n:lint).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/i18n/locales/en.json` at line 3390, Remove the duplicated translation
entry for the key "内置" (the second occurrence shown in the diff) so there is
only a single "内置": "Built-in" mapping in the en.json locale; locate the
duplicate entry (key "内置") and delete the later re-declaration, then run the
i18n linter (bun run i18n:lint) to verify no key shadowing remains.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/helpers/navMigration.js`:
- Around line 23-50: The migration assumes objects for pricing and customItems
and can crash on null/non-object values; update the BUILT_IN_ORDER mapping to
check modules[key] != null && typeof modules[key] === 'object' before accessing
properties like enabled/requireAuth (use a safe fallback when not an object),
and when building customItems ensure modules.customItems is an array of objects
(e.g., const customItems = Array.isArray(modules.customItems) ?
modules.customItems.filter(ci => ci && typeof ci === 'object') : [];), then
safely read ci.position with a fallback (ci.position ?? 99) and destructure only
from the filtered object (const { position: _, ...rest } = ci) so null/primitive
entries are skipped and won't throw in the sorting/iteration using sorted,
cursors, and customItem creation.

In `@web/src/i18n/locales/fr.json`:
- Line 3346: Remove the duplicated translation key "内置" from the fr.json locale
so only a single "内置" entry remains; locate the second occurrence of the "内置"
key in the file and delete that duplicate (or merge if values differ), then
validate the JSON to ensure no duplicate keys remain and the file parses
correctly.

In `@web/src/i18n/locales/ja.json`:
- Line 3327: Remove the duplicate locale key "内置" from ja.json (the second
occurrence shown in the diff) so it no longer shadowed the earlier definition;
delete the redundant JSON property and then run the i18n CLI steps (bun run
i18n:extract, bun run i18n:sync, bun run i18n:lint) to validate and sync
translations.

In `@web/src/i18n/locales/ru.json`:
- Line 3360: Remove the duplicate translation entry for the key "内置" in ru.json
(the later occurrence around the shown diff) so only the original mapping
remains; locate the second "内置": "Встроенный" entry and delete it to avoid
silent overrides and i18n lint errors, leaving the earlier definition (the one
around Line ~643) intact.

In `@web/src/i18n/locales/vi.json`:
- Line 3895: The vi.json contains a duplicate key "内置" (one value is "Tích hợp
sẵn", another is "Tích hợp"), causing the later definition to override the
earlier; remove or consolidate the duplicate so the single "内置" entry uses the
intended translation "Tích hợp sẵn" (or pick the correct phrasing) and keep only
that definition; verify usages in SettingsHeaderNavModules.jsx and
UserBindingManagementModal.jsx still render correctly after the change.

In `@web/src/i18n/locales/zh-CN.json`:
- Around line 2983-2994: Add the missing Chinese translation key used in
SettingsHeaderNavModules.jsx by inserting an entry for the exact source string
"控制顶栏导航项的显示和排序,全局生效" into the zh-CN locale JSON alongside the other navigation
keys (e.g., near "添加导航项"/"编辑导航项"), then run the i18n workflow (bun run
i18n:extract, bun run i18n:sync, bun run i18n:lint) to sync and validate; update
the file web/src/i18n/locales/zh-CN.json so t('控制顶栏导航项的显示和排序,全局生效') resolves to
a proper localized value instead of falling back to the raw key.

In `@web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx`:
- Around line 340-345: openEditModal currently sets form state with
setFormOpenInNewTab(item.openInNewTab) which leaves undefined and causes the
modal switch to be unchecked while the rendered default treats undefined as
"open in new tab"; change it to normalize missing values to the true default
(e.g. setFormOpenInNewTab(item.openInNewTab ?? true) or
setFormOpenInNewTab(item.openInNewTab !== false)) so the modal switch matches
Navigation.jsx behavior; apply the same normalization in the corresponding
add/edit handlers in the nearby block (the code around lines 361-381) so both
openEditModal and the add-modal initializer use the same normalized default.
- Around line 429-441: The effect handling HeaderNavModules only updates state
when props.options.HeaderNavModules exists, leaving stale items when that key is
removed; update the useEffect that reads props.options and HeaderNavModules so
that if props.options exists but HeaderNavModules is missing, null, or an empty
string you call setItems(DEFAULT_ITEMS) (and keep the existing try/catch parsing
logic otherwise), i.e. ensure the branch for props.options &&
!props.options.HeaderNavModules calls setItems(DEFAULT_ITEMS) to reset local
state; reference useEffect, props.options, HeaderNavModules, setItems,
DEFAULT_ITEMS, and migrateOldFormatToItems when making the change.

---

Nitpick comments:
In `@web/src/App.jsx`:
- Around line 69-94: Extract the parsing logic that computes pricingRequireAuth
into a single shared helper (e.g., parseHeaderNavModules or
resolvePricingRequireAuth) and use it from both App.jsx and
web/src/hooks/common/useHeaderBar.js; specifically move the JSON.parse +
format-handling branches (the Array.isArray(modules.items) branch, the typeof
modules.pricing === 'boolean' branch, and the modules.pricing?.requireAuth ===
true branch) into that helper, ensure it returns a boolean and swallows/parses
errors the same way, then replace the inline useMemo logic in the
pricingRequireAuth computation with a call to the new helper that accepts
statusState?.status?.HeaderNavModules.

In `@web/src/i18n/locales/en.json`:
- Line 3390: Remove the duplicated translation entry for the key "内置" (the
second occurrence shown in the diff) so there is only a single "内置": "Built-in"
mapping in the en.json locale; locate the duplicate entry (key "内置") and delete
the later re-declaration, then run the i18n linter (bun run i18n:lint) to verify
no key shadowing remains.

In `@web/src/i18n/locales/zh-TW.json`:
- Line 3001: Remove the duplicate Chinese source key "在新标签页打开" and reuse the
existing canonical key "在新标签页中打开": delete the newly added key from zh-TW.json,
ensure the translation value "在新標籤頁打開" is present as the value for "在新标签页中打开"
(or merge the value if needed), and update any code or template references that
were changed to point to "在新标签页中打开" so only the single canonical key remains.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ab8d3600-fd28-41fa-8748-6693f789b8b9

📥 Commits

Reviewing files that changed from the base of the PR and between bb5b9ea and 6dc1f89.

⛔ Files ignored due to path filters (1)
  • web/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • web/package.json
  • web/src/App.jsx
  • web/src/components/layout/headerbar/Navigation.jsx
  • web/src/helpers/navMigration.js
  • web/src/hooks/common/useHeaderBar.js
  • web/src/hooks/common/useNavigation.js
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/vi.json
  • web/src/i18n/locales/zh-CN.json
  • web/src/i18n/locales/zh-TW.json
  • web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx

Comment on lines +23 to +50
const items = BUILT_IN_ORDER.map((key) => {
if (key === 'pricing') {
const val = modules[key];
if (typeof val === 'object') {
return {
key,
enabled: val.enabled !== false,
requireAuth: val.requireAuth || false,
};
}
return { key, enabled: val !== false, requireAuth: false };
}
return { key, enabled: modules[key] !== false };
});

const customItems = Array.isArray(modules.customItems)
? modules.customItems
: [];
const sorted = [...customItems].sort(
(a, b) => (a.position ?? 99) - (b.position ?? 99),
);

const cursors = new Map();
for (const ci of sorted) {
const pos = ci.position ?? 99;
const { position: _, ...rest } = ci;
const customItem = { ...rest };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Harden migration against malformed persisted config.

At Line 26 and Line 48, null/non-object values can throw (pricing: null, customItems: [null]) and break header rendering. Please guard object assumptions before property access/destructuring.

🔧 Proposed defensive fix
 export function migrateOldFormatToItems(modules) {
+  const safeModules =
+    modules && typeof modules === 'object' && !Array.isArray(modules)
+      ? modules
+      : {};
+
   const items = BUILT_IN_ORDER.map((key) => {
     if (key === 'pricing') {
-      const val = modules[key];
-      if (typeof val === 'object') {
+      const val = safeModules[key];
+      if (val && typeof val === 'object' && !Array.isArray(val)) {
         return {
           key,
           enabled: val.enabled !== false,
-          requireAuth: val.requireAuth || false,
+          requireAuth: val.requireAuth === true,
         };
       }
       return { key, enabled: val !== false, requireAuth: false };
     }
-    return { key, enabled: modules[key] !== false };
+    return { key, enabled: safeModules[key] !== false };
   });
 
-  const customItems = Array.isArray(modules.customItems)
-    ? modules.customItems
+  const customItems = Array.isArray(safeModules.customItems)
+    ? safeModules.customItems.filter(
+        (ci) => ci && typeof ci === 'object' && !Array.isArray(ci),
+      )
     : [];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/helpers/navMigration.js` around lines 23 - 50, The migration assumes
objects for pricing and customItems and can crash on null/non-object values;
update the BUILT_IN_ORDER mapping to check modules[key] != null && typeof
modules[key] === 'object' before accessing properties like enabled/requireAuth
(use a safe fallback when not an object), and when building customItems ensure
modules.customItems is an array of objects (e.g., const customItems =
Array.isArray(modules.customItems) ? modules.customItems.filter(ci => ci &&
typeof ci === 'object') : [];), then safely read ci.position with a fallback
(ci.position ?? 99) and destructure only from the filtered object (const {
position: _, ...rest } = ci) so null/primitive entries are skipped and won't
throw in the sorting/iteration using sorted, cursors, and customItem creation.

Comment thread web/src/i18n/locales/fr.json Outdated
Comment thread web/src/i18n/locales/ja.json Outdated
Comment thread web/src/i18n/locales/ru.json Outdated
Comment thread web/src/i18n/locales/vi.json Outdated
Comment thread web/src/i18n/locales/zh-CN.json Outdated
Comment on lines +2983 to +2994
"无法读取剪贴板": "无法读取剪贴板",
"在新标签页打开": "在新标签页打开",
"添加导航项": "添加导航项",
"编辑导航项": "编辑导航项",
"链接地址": "链接地址",
"内部路径": "内部路径",
"外部链接": "外部链接",
"最多添加 {{max}} 个自定义导航项": "最多添加 {{max}} 个自定义导航项",
"请填写完整信息": "请填写完整信息",
"链接示例: https://example.com 或 /path": "链接示例: https://example.com 或 /path",
"拖拽可调整顺序": "拖拽可调整顺序",
"内置": "内置"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add the new section help text to the locale bundle.

web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx, Line 448 now calls t('控制顶栏导航项的显示和排序,全局生效'), but that source string was not added alongside the rest of the navigation-management keys here. Until it is synced, localized builds will fall back to the raw Chinese key.

➕ Suggested locale addition
     "拖拽可调整顺序": "拖拽可调整顺序",
-    "内置": "内置"
+    "内置": "内置",
+    "控制顶栏导航项的显示和排序,全局生效": "控制顶栏导航项的显示和排序,全局生效"
As per coding guidelines, "Frontend translation files must use `i18next` + `react-i18next` + `i18next-browser-languagedetector` libraries with translation files at `web/src/i18n/locales/{lang}.json`" and "Use CLI tools: `bun run i18n:extract`, `bun run i18n:sync`, `bun run i18n:lint`."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/i18n/locales/zh-CN.json` around lines 2983 - 2994, Add the missing
Chinese translation key used in SettingsHeaderNavModules.jsx by inserting an
entry for the exact source string "控制顶栏导航项的显示和排序,全局生效" into the zh-CN locale
JSON alongside the other navigation keys (e.g., near "添加导航项"/"编辑导航项"), then run
the i18n workflow (bun run i18n:extract, bun run i18n:sync, bun run i18n:lint)
to sync and validate; update the file web/src/i18n/locales/zh-CN.json so
t('控制顶栏导航项的显示和排序,全局生效') resolves to a proper localized value instead of falling
back to the raw key.

Comment thread web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx
Comment thread web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx
RedwindA added 2 commits April 7, 2026 10:37
…p reordering

Add configurable custom navigation items to the sidebar/header with full
CRUD operations, drag-and-drop reordering, URL normalization, localized
link examples, accessibility labels, and strict requireAuth checks.
Remove duplicate "内置" translation keys from en/fr/ja/ru/vi locale files
that were shadowing earlier definitions. Normalize openInNewTab default
to true when editing migrated custom nav items. Reset nav items to
defaults when HeaderNavModules is removed from options.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
web/src/helpers/navMigration.js (1)

25-26: ⚠️ Potential issue | 🟠 Major

Guard null and non-object legacy values before reading them.

typeof null === 'object', so pricing: null still throws at Line 29, and [null] / primitive entries in customItems still blow up at Lines 42-48. useNavigation resolves legacy configs during render, so one bad persisted value can take down the whole header instead of falling back.

🛠️ Defensive guard
 export function migrateOldFormatToItems(modules) {
+  const safeModules =
+    modules && typeof modules === 'object' && !Array.isArray(modules)
+      ? modules
+      : {};
+
   const items = BUILT_IN_ORDER.map((key) => {
     if (key === 'pricing') {
-      const val = modules[key];
-      if (typeof val === 'object') {
+      const val = safeModules[key];
+      if (val && typeof val === 'object' && !Array.isArray(val)) {
         return {
           key,
           enabled: val.enabled !== false,
-          requireAuth: val.requireAuth || false,
+          requireAuth: val.requireAuth === true,
         };
       }
       return { key, enabled: val !== false, requireAuth: false };
     }
-    return { key, enabled: modules[key] !== false };
+    return { key, enabled: safeModules[key] !== false };
   });
 
-  const customItems = Array.isArray(modules.customItems)
-    ? modules.customItems
+  const customItems = Array.isArray(safeModules.customItems)
+    ? safeModules.customItems.filter(
+        (ci) => ci && typeof ci === 'object' && !Array.isArray(ci),
+      )
     : [];

Also applies to: 38-48

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/helpers/navMigration.js` around lines 25 - 26, The code assumes
module entries and customItems are objects but `typeof null === 'object'` and
primitives can appear; update the guards in the migration logic (where `const
val = modules[key]` is checked and where `customItems` entries are iterated) to
first ensure values are non-null objects (e.g., `val !== null && typeof val ===
'object'`) and likewise filter/map `customItems` by `item !== null && typeof
item === 'object'` before reading properties; also add a safe fallback branch
for non-object entries to skip or replace them with a default so useNavigation
won't throw when encountering legacy null/primitives.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/src/i18n/locales/ja.json`:
- Around line 3316-3326: Add the missing source key "控制顶栏导航项的显示和排序,全局生效" to the
Japanese locale by adding a Japanese translation entry in
web/src/i18n/locales/ja.json matching that exact Chinese key; locate where
SettingsHeaderNavModules.jsx calls t('控制顶栏导航项的显示和排序,全局生效') and add the
corresponding Japanese string (e.g., a concise translation) under that key, then
run the i18n CLI steps (bun run i18n:extract, bun run i18n:sync, bun run
i18n:lint) to propagate and validate the change across locales.

In `@web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx`:
- Around line 189-212: Custom nav items lack an enabled flag and a toggle UI:
update the SettingsHeaderNavModules component so non-built-in rows render a
Switch (reuse checked={item.enabled}
onChange={(checked)=>onToggle(getItemId(item), checked)}) alongside edit/delete
(replace the current Button-only branch used in the JSX for custom items and the
similar block around the other occurrence), ensure new custom item creation
logic sets item.enabled = true by default, and update useNavigation to filter
out items where enabled === false so disabled custom items are ignored when
building the navigation.
- Around line 124-137: The drag handle activator is currently a non-focusable
div so keyboard users cannot reach the KeyboardSensor; update the activator in
SettingsHeaderNavModules.jsx to be a focusable element (e.g., change the outer
div that spreads {...attributes} and {...listeners} to a <button type="button">
or add tabIndex={0} plus role="button" and proper onKeyDown handling) so it can
receive keyboard focus and activation; keep spreading {...attributes} and
{...listeners}, preserve the inline style and the IconHandle reference, and add
an accessible label (aria-label) so keyboard and screen-reader users can
discover and start drag with Enter/Space.

---

Duplicate comments:
In `@web/src/helpers/navMigration.js`:
- Around line 25-26: The code assumes module entries and customItems are objects
but `typeof null === 'object'` and primitives can appear; update the guards in
the migration logic (where `const val = modules[key]` is checked and where
`customItems` entries are iterated) to first ensure values are non-null objects
(e.g., `val !== null && typeof val === 'object'`) and likewise filter/map
`customItems` by `item !== null && typeof item === 'object'` before reading
properties; also add a safe fallback branch for non-object entries to skip or
replace them with a default so useNavigation won't throw when encountering
legacy null/primitives.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3ac515df-e1e3-49e5-8edb-f2c684e5e403

📥 Commits

Reviewing files that changed from the base of the PR and between 5776436 and 79b74c2.

⛔ Files ignored due to path filters (1)
  • web/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • web/package.json
  • web/src/App.jsx
  • web/src/components/layout/headerbar/Navigation.jsx
  • web/src/helpers/navMigration.js
  • web/src/hooks/common/useHeaderBar.js
  • web/src/hooks/common/useNavigation.js
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/fr.json
  • web/src/i18n/locales/ja.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/vi.json
  • web/src/i18n/locales/zh-CN.json
  • web/src/i18n/locales/zh-TW.json
  • web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx
✅ Files skipped from review due to trivial changes (7)
  • web/package.json
  • web/src/i18n/locales/zh-CN.json
  • web/src/i18n/locales/zh-TW.json
  • web/src/i18n/locales/en.json
  • web/src/i18n/locales/ru.json
  • web/src/i18n/locales/fr.json
  • web/src/App.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/i18n/locales/vi.json

Comment thread web/src/i18n/locales/ja.json Outdated
Comment on lines +3316 to +3326
"无法读取剪贴板": "クリップボードを読み取れません",
"在新标签页打开": "新しいタブで開く",
"添加导航项": "ナビゲーション項目を追加",
"编辑导航项": "ナビゲーション項目を編集",
"链接地址": "リンクアドレス",
"内部路径": "内部パス",
"外部链接": "外部リンク",
"最多添加 {{max}} 个自定义导航项": "カスタムナビゲーション項目は最大{{max}}個まで",
"请填写完整信息": "情報を完全に入力してください",
"链接示例: https://example.com 或 /path": "例: https://example.com または /path",
"拖拽可调整顺序": "ドラッグして並べ替え"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add the new settings helper-text key to this locale.

web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx, Line 450 now uses t('控制顶栏导航项的显示和排序,全局生效'), but this locale update never adds that Chinese source key. Japanese users will still see raw Chinese for that description until it is added here and synced across locales.

As per coding guidelines web/src/i18n/**/*.json: “Use CLI tools: bun run i18n:extract, bun run i18n:sync, bun run i18n:lint.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/i18n/locales/ja.json` around lines 3316 - 3326, Add the missing
source key "控制顶栏导航项的显示和排序,全局生效" to the Japanese locale by adding a Japanese
translation entry in web/src/i18n/locales/ja.json matching that exact Chinese
key; locate where SettingsHeaderNavModules.jsx calls t('控制顶栏导航项的显示和排序,全局生效') and
add the corresponding Japanese string (e.g., a concise translation) under that
key, then run the i18n CLI steps (bun run i18n:extract, bun run i18n:sync, bun
run i18n:lint) to propagate and validate the change across locales.

Comment on lines +124 to +137
<div
{...attributes}
{...listeners}
style={{
cursor: 'grab',
touchAction: 'none',
color: 'var(--semi-color-text-2)',
display: 'flex',
alignItems: 'center',
flexShrink: 0,
}}
>
<IconHandle size='large' />
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's explore the file structure to understand the context
find web -type f -name "SettingsHeaderNavModules.jsx" 2>/dev/null | head -20

Repository: QuantumNous/new-api

Length of output: 123


🏁 Script executed:

# Get the file content, focusing on the relevant sections
cat -n web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx | head -160

Repository: QuantumNous/new-api

Length of output: 5349


🏁 Script executed:

# Check for KeyboardSensor import and usage
rg "KeyboardSensor" web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx -A 2 -B 2

Repository: QuantumNous/new-api

Length of output: 305


🏁 Script executed:

# Check the i18n locale files for the translation key
fd -e json web/src/i18n/locales/ -type f -exec rg "拖拽可调整顺序" {} +

Repository: QuantumNous/new-api

Length of output: 233


🏁 Script executed:

# Find i18n locale files
find web/src/i18n -name "*.json" -type f 2>/dev/null | head -20

Repository: QuantumNous/new-api

Length of output: 271


🏁 Script executed:

# Search for the translation key in JSON files
rg "拖拽可调整顺序" web/src/i18n/ 2>/dev/null || echo "Key not found"

Repository: QuantumNous/new-api

Length of output: 512


🏁 Script executed:

# Check what translation keys exist related to drag/order
rg "拖拽|调整顺序" web/src/i18n/ 2>/dev/null | head -20

Repository: QuantumNous/new-api

Length of output: 1199


Make the drag handle keyboard-focusable.

KeyboardSensor is registered, but the activator is a plain div, so tab focus never reaches it. Reordering is mouse/touch-only right now.

♿ Minimal fix
-        <div
+        <button
+          type='button'
           {...attributes}
           {...listeners}
+          aria-label={t('拖拽可调整顺序')}
           style={{
             cursor: 'grab',
             touchAction: 'none',
             color: 'var(--semi-color-text-2)',
             display: 'flex',
             alignItems: 'center',
             flexShrink: 0,
+            background: 'transparent',
+            border: 0,
+            padding: 0,
           }}
         >
           <IconHandle size='large' />
-        </div>
+        </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
{...attributes}
{...listeners}
style={{
cursor: 'grab',
touchAction: 'none',
color: 'var(--semi-color-text-2)',
display: 'flex',
alignItems: 'center',
flexShrink: 0,
}}
>
<IconHandle size='large' />
</div>
<button
type='button'
{...attributes}
{...listeners}
aria-label={t('拖拽可调整顺序')}
style={{
cursor: 'grab',
touchAction: 'none',
color: 'var(--semi-color-text-2)',
display: 'flex',
alignItems: 'center',
flexShrink: 0,
background: 'transparent',
border: 0,
padding: 0,
}}
>
<IconHandle size='large' />
</button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx` around lines
124 - 137, The drag handle activator is currently a non-focusable div so
keyboard users cannot reach the KeyboardSensor; update the activator in
SettingsHeaderNavModules.jsx to be a focusable element (e.g., change the outer
div that spreads {...attributes} and {...listeners} to a <button type="button">
or add tabIndex={0} plus role="button" and proper onKeyDown handling) so it can
receive keyboard focus and activation; keep spreading {...attributes} and
{...listeners}, preserve the inline style and the IconHandle reference, and add
an accessible label (aria-label) so keyboard and screen-reader users can
discover and start drag with Enter/Space.

Comment on lines +189 to +212
{isBuiltIn ? (
<Switch
checked={item.enabled}
onChange={(checked) => onToggle(getItemId(item), checked)}
size='default'
/>
) : (
<>
<Button
icon={<IconEdit />}
size='small'
type='tertiary'
aria-label={t('编辑') + ' ' + (item.label || '')}
onClick={() => onEdit(item)}
/>
<Button
icon={<IconDelete />}
size='small'
type='danger'
aria-label={t('删除') + ' ' + (item.label || '')}
onClick={() => onDelete(item.id)}
/>
</>
)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Custom nav items still have no disable path.

Built-ins get a Switch, but custom rows only expose edit/delete, and new items are saved without an enabled flag. That makes deletion the only way to hide a custom link and misses the linked enable/disable requirement; useNavigation also needs to skip custom items when enabled === false.

Also applies to: 375-383

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx` around lines
189 - 212, Custom nav items lack an enabled flag and a toggle UI: update the
SettingsHeaderNavModules component so non-built-in rows render a Switch (reuse
checked={item.enabled} onChange={(checked)=>onToggle(getItemId(item), checked)})
alongside edit/delete (replace the current Button-only branch used in the JSX
for custom items and the similar block around the other occurrence), ensure new
custom item creation logic sets item.enabled = true by default, and update
useNavigation to filter out items where enabled === false so disabled custom
items are ignored when building the navigation.

Keep both custom navigation keys (feat/customNav) and error boundary/payment keys (main) in all 7 locale files.
@RedwindA RedwindA closed this Apr 9, 2026
@RedwindA
RedwindA deleted the feat/customNav branch July 23, 2026 07:03
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.

[Feature Request] Support custom navigation menu items in header

1 participant