diff --git a/package-lock.json b/package-lock.json index 93d8411..02e2ab3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5602,9 +5602,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "funding": [ { "type": "github", @@ -6412,9 +6412,9 @@ } }, "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", "dev": true, "license": "MIT", "engines": { @@ -7776,9 +7776,9 @@ } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", "dev": true, "funding": [ { diff --git a/src/renderer/main.ts b/src/renderer/main.ts index c018480..6b4b001 100644 --- a/src/renderer/main.ts +++ b/src/renderer/main.ts @@ -129,6 +129,9 @@ let knownDownloadIds = new Set(); // Per-tab internal route – each tab keeps its own orb:// page state const tabInternalRoutes = new Map(); +// Route to bind to the next newly-activated tab (set before createTab(), consumed in applyState) +let pendingInternalRoute: 'history' | 'bookmarks' | 'downloads' | null = null; + let unsubscribeOpenUrl: (() => void) | null = null; let unsubscribeTabsState: (() => void) | null = null; let unsubscribeBookmarks: (() => void) | null = null; @@ -439,6 +442,12 @@ function updateTabCompactMode(): void { tabsContainer.classList.toggle('compact', perTabPx < 72); } +const INTERNAL_ROUTE_TITLES: Record<'history' | 'bookmarks' | 'downloads', string> = { + history: 'History', + bookmarks: 'Bookmarks', + downloads: 'Downloads', +}; + function renderTabs(): void { tabsContainer.innerHTML = ''; @@ -447,7 +456,13 @@ function renderTabs(): void { tabElement.className = `tab${tab.id === state.activeTabId ? ' active' : ''}`; tabElement.dataset.id = String(tab.id); - const faviconUrl = tab.url ? getSiteFaviconUrl(tab.url) : ''; + // Internal-page tabs get a friendly title instead of the BrowserView title + const internalRoute = tabInternalRoutes.get(tab.id); + const displayTitle = internalRoute + ? INTERNAL_ROUTE_TITLES[internalRoute] + : (tab.title || 'New Tab'); + + const faviconUrl = !internalRoute && tab.url ? getSiteFaviconUrl(tab.url) : ''; const iconContent = faviconUrl ? `` : ``; @@ -457,7 +472,7 @@ function renderTabs(): void { ${iconContent} - ${escapeHtml(tab.title || 'New Tab')} + ${escapeHtml(displayTitle)} `; @@ -748,6 +763,12 @@ function applyState(nextState: TabsStateSnapshot): void { state.tabs = nextState.tabs; state.activeTabId = nextState.activeTabId; + // Consume a pending internal route — bound to the newly active tab after createTab() + if (pendingInternalRoute !== null && nextState.activeTabId !== null) { + tabInternalRoutes.set(nextState.activeTabId, pendingInternalRoute); + pendingInternalRoute = null; + } + // Restore the internal page that belongs to the newly active tab state.fullPageView = nextState.activeTabId !== null @@ -870,10 +891,34 @@ function setFullPageView(view: 'history' | 'bookmarks' | 'downloads' | null): vo syncBrowserBounds(); } +// Opens an internal page in a dedicated new tab — Chrome-style. +// If a tab with this route already exists, focus it instead of opening a duplicate. +function openInternalPageInNewTab(view: 'history' | 'bookmarks' | 'downloads'): void { + // Check for an existing tab with this internal route — focus it (Chrome-like) + for (const [tabId, route] of tabInternalRoutes.entries()) { + if (route === view) { + activateTab(tabId); + return; + } + } + + // No existing tab — create one and bind the route when it becomes active + pendingInternalRoute = view; + void window.orb.createTab(); +} + +function closeInternalPageTab(): void { + if (state.activeTabId !== null) { + tabInternalRoutes.delete(state.activeTabId); + requestTabClose(window.orb, state.activeTabId); + } +} + function navigate(input: string): void { const internalRoute = resolveInternalRoute(input); if (internalRoute) { - setFullPageView(internalRoute); + // Navigating to an orb:// URL opens it in a new tab (Chrome-style) + openInternalPageInNewTab(internalRoute); newTabSearch.value = ''; return; } @@ -934,7 +979,7 @@ function handleMenuAction(action: MenuAction): void { toggleHistorySidebar(); break; case MENU_ACTIONS.OPEN_DOWNLOADS: - setFullPageView('downloads'); + openInternalPageInNewTab('downloads'); break; case MENU_ACTIONS.TOGGLE_BOOKMARK_BAR: toggleBookmarkBar(); @@ -989,15 +1034,20 @@ btnHistoryClose.addEventListener('click', () => { }); btnBookmarksDetailed.addEventListener('click', () => { - setFullPageView('bookmarks'); + openInternalPageInNewTab('bookmarks'); }); btnHistoryDetailed.addEventListener('click', () => { - setFullPageView('history'); + openInternalPageInNewTab('history'); }); btnFullPageClose.addEventListener('click', () => { - setFullPageView(null); + // Close the dedicated internal-page tab entirely (Chrome-style) + if (state.fullPageView !== null) { + closeInternalPageTab(); + } else { + setFullPageView(null); + } }); btnFullPageHistoryClear.addEventListener('click', () => { @@ -1337,7 +1387,8 @@ document.addEventListener('keydown', event => { if (event.key === 'Escape' && state.fullPageView) { event.preventDefault(); - setFullPageView(null); + // Close the dedicated internal-page tab entirely (Chrome-style) + closeInternalPageTab(); return; } @@ -1382,7 +1433,7 @@ document.addEventListener('keydown', event => { if (mod && event.key.toLowerCase() === 'j') { event.preventDefault(); - setFullPageView('downloads'); + openInternalPageInNewTab('downloads'); return; }