refactor(tabs): enhance URL handling and default title logic

- Updated `AppShell` and `TabRouter` components to improve URL synchronization with tab states, ensuring full URLs (including search/hash) are correctly managed.
- Enhanced `getDefaultRouteTitle` utility to handle full URLs for better title mapping, improving internationalization support.
- Refactored `useTabs` to retain complete URLs in tab management, optimizing tab state tracking.

These changes enhance the application's navigation and tab management, providing a more consistent user experience.
This commit is contained in:
MyPrototypeWhat 2025-12-12 15:04:01 +08:00
parent 4f7a14b044
commit bcbd5ed717
6 changed files with 31 additions and 17 deletions

View File

@ -23,7 +23,7 @@ const WebviewContainer = ({ url, isActive }: { url: string; isActive: boolean })
export const AppShell = () => {
const { tabs, activeTabId, setActiveTab, closeTab, updateTab, addTab } = useTabs()
// Sync internal navigation back to tab state with default title
// Sync internal navigation back to tab state with default title (url may include search/hash)
const handleUrlChange = (tabId: string, url: string) => {
updateTab(tabId, { url, title: getDefaultRouteTitle(url) })
}

View File

@ -24,20 +24,20 @@ export const TabRouter = ({ tab, isActive, onUrlChange }: TabRouterProps) => {
return createRouter({ routeTree, history })
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tab.id])
// Sync internal navigation back to tab state
useEffect(() => {
return router.subscribe('onResolved', ({ toLocation }) => {
if (toLocation.pathname !== tab.url) {
onUrlChange(toLocation.pathname)
const nextHref = toLocation.href
if (nextHref !== tab.url) {
onUrlChange(nextHref)
}
})
}, [router, tab.url, onUrlChange])
// Navigate when tab.url changes externally (e.g., from Sidebar)
useEffect(() => {
const currentPath = router.state.location.pathname
if (tab.url !== currentPath) {
const currentHref = router.state.location.href
if (tab.url !== currentHref) {
router.navigate({ to: tab.url })
}
}, [router, tab.url])

View File

@ -179,7 +179,7 @@ export function useTabs() {
return
}
// 添加 LRU 字段
// 添加 LRU 字段,保留完整 URL含 search/hash
const newTab: Tab = {
...tab,
lastAccessTime: Date.now(),
@ -255,7 +255,7 @@ export function useTabs() {
const newTab: Tab = {
id: id || uuid(),
type,
url,
url, // full URL including search/hash
title: title || getDefaultRouteTitle(url),
lastAccessTime: Date.now(),
isDormant: false

View File

@ -94,8 +94,8 @@ const ProviderList: FC = () => {
// 清除 id 参数
navigate({
to: location.pathname,
search: (prev) => {
const { id: _, ...rest } = prev as Record<string, unknown>
search: ({ id, ...rest }) => {
void id
return rest
},
replace: true

View File

@ -1,5 +1,11 @@
import NavigationHandler from '@renderer/handler/NavigationHandler'
import { createRootRoute, Outlet } from '@tanstack/react-router'
export const Route = createRootRoute({
component: () => <Outlet />
component: () => (
<>
<NavigationHandler />
<Outlet />
</>
)
})

View File

@ -1,5 +1,8 @@
import i18n from '@renderer/i18n'
/** Base URL for parsing relative route paths */
const BASE_URL = 'https://www.cherry-ai.com/'
/**
* Route to i18n key mapping for default tab titles
*/
@ -29,30 +32,35 @@ const routeTitleKeys: Record<string, string> = {
* getDefaultRouteTitle('/unknown') // 'unknown'
*/
export function getDefaultRouteTitle(url: string): string {
const sanitizedUrl = new URL(url, BASE_URL).pathname
// Try exact match first
const exactKey = routeTitleKeys[url]
const exactKey = routeTitleKeys[sanitizedUrl]
if (exactKey) {
return i18n.t(exactKey)
}
// Try matching base path (e.g., '/chat/123' -> '/chat')
const basePath = '/' + url.split('/').filter(Boolean)[0]
const basePath = '/' + sanitizedUrl.split('/').filter(Boolean)[0]
const baseKey = routeTitleKeys[basePath]
if (baseKey) {
return i18n.t(baseKey)
}
// Fallback to URL path
return url.split('/').pop() || url
// Fallback to last segment of pathname
const segments = sanitizedUrl.split('/').filter(Boolean)
return segments.pop() || sanitizedUrl
}
/**
* Get the i18n key for a route (without translating)
*/
export function getRouteTitleKey(url: string): string | undefined {
const exactKey = routeTitleKeys[url]
const sanitizedUrl = new URL(url, BASE_URL).pathname
const exactKey = routeTitleKeys[sanitizedUrl]
if (exactKey) return exactKey
const basePath = '/' + url.split('/').filter(Boolean)[0]
const basePath = '/' + sanitizedUrl.split('/').filter(Boolean)[0]
return routeTitleKeys[basePath]
}