feat(frontend): add custom navigation items support with drag-and-drop reordering - #4082
feat(frontend): add custom navigation items support with drag-and-drop reordering#4082RedwindA wants to merge 3 commits into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
✅ Files skipped from review due to trivial changes (4)
🚧 Files skipped from review as they are similar to previous changes (2)
WalkthroughAdds 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 Changes
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (3)
web/src/App.jsx (1)
69-94: Consider centralizingpricingRequireAuthparsing in a shared helper.This logic now exists in both
web/src/App.jsxandweb/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
⛔ Files ignored due to path filters (1)
web/bun.lockis excluded by!**/*.lock
📒 Files selected for processing (14)
web/package.jsonweb/src/App.jsxweb/src/components/layout/headerbar/Navigation.jsxweb/src/helpers/navMigration.jsweb/src/hooks/common/useHeaderBar.jsweb/src/hooks/common/useNavigation.jsweb/src/i18n/locales/en.jsonweb/src/i18n/locales/fr.jsonweb/src/i18n/locales/ja.jsonweb/src/i18n/locales/ru.jsonweb/src/i18n/locales/vi.jsonweb/src/i18n/locales/zh-CN.jsonweb/src/i18n/locales/zh-TW.jsonweb/src/pages/Setting/Operation/SettingsHeaderNavModules.jsx
| 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 }; | ||
|
|
There was a problem hiding this comment.
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.
| "无法读取剪贴板": "无法读取剪贴板", | ||
| "在新标签页打开": "在新标签页打开", | ||
| "添加导航项": "添加导航项", | ||
| "编辑导航项": "编辑导航项", | ||
| "链接地址": "链接地址", | ||
| "内部路径": "内部路径", | ||
| "外部链接": "外部链接", | ||
| "最多添加 {{max}} 个自定义导航项": "最多添加 {{max}} 个自定义导航项", | ||
| "请填写完整信息": "请填写完整信息", | ||
| "链接示例: https://example.com 或 /path": "链接示例: https://example.com 或 /path", | ||
| "拖拽可调整顺序": "拖拽可调整顺序", | ||
| "内置": "内置" |
There was a problem hiding this comment.
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
"拖拽可调整顺序": "拖拽可调整顺序",
- "内置": "内置"
+ "内置": "内置",
+ "控制顶栏导航项的显示和排序,全局生效": "控制顶栏导航项的显示和排序,全局生效"🤖 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.
…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.
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (1)
web/src/helpers/navMigration.js (1)
25-26:⚠️ Potential issue | 🟠 MajorGuard
nulland non-object legacy values before reading them.
typeof null === 'object', sopricing: nullstill throws at Line 29, and[null]/ primitive entries incustomItemsstill blow up at Lines 42-48.useNavigationresolves 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
⛔ Files ignored due to path filters (1)
web/bun.lockis excluded by!**/*.lock
📒 Files selected for processing (14)
web/package.jsonweb/src/App.jsxweb/src/components/layout/headerbar/Navigation.jsxweb/src/helpers/navMigration.jsweb/src/hooks/common/useHeaderBar.jsweb/src/hooks/common/useNavigation.jsweb/src/i18n/locales/en.jsonweb/src/i18n/locales/fr.jsonweb/src/i18n/locales/ja.jsonweb/src/i18n/locales/ru.jsonweb/src/i18n/locales/vi.jsonweb/src/i18n/locales/zh-CN.jsonweb/src/i18n/locales/zh-TW.jsonweb/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
| "无法读取剪贴板": "クリップボードを読み取れません", | ||
| "在新标签页打开": "新しいタブで開く", | ||
| "添加导航项": "ナビゲーション項目を追加", | ||
| "编辑导航项": "ナビゲーション項目を編集", | ||
| "链接地址": "リンクアドレス", | ||
| "内部路径": "内部パス", | ||
| "外部链接": "外部リンク", | ||
| "最多添加 {{max}} 个自定义导航项": "カスタムナビゲーション項目は最大{{max}}個まで", | ||
| "请填写完整信息": "情報を完全に入力してください", | ||
| "链接示例: https://example.com 或 /path": "例: https://example.com または /path", | ||
| "拖拽可调整顺序": "ドラッグして並べ替え" |
There was a problem hiding this comment.
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.
| <div | ||
| {...attributes} | ||
| {...listeners} | ||
| style={{ | ||
| cursor: 'grab', | ||
| touchAction: 'none', | ||
| color: 'var(--semi-color-text-2)', | ||
| display: 'flex', | ||
| alignItems: 'center', | ||
| flexShrink: 0, | ||
| }} | ||
| > | ||
| <IconHandle size='large' /> | ||
| </div> |
There was a problem hiding this comment.
🧩 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 -20Repository: 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 -160Repository: 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 2Repository: 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 -20Repository: 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 -20Repository: 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.
| <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.
| {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)} | ||
| /> | ||
| </> | ||
| )} |
There was a problem hiding this comment.
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.
📝 变更描述 / Description
实现顶部导航栏自定义功能:
🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
✅ 提交前检查项 / Checklist
📸 运行证明 / Proof of Work
PixPin_2026-04-03_22-47-24.mp4
Summary by CodeRabbit
New Features
Improvements
Improvements