From af747849311b330fd696a2ae6bd12865ef73e824 Mon Sep 17 00:00:00 2001 From: Erich Gubler Date: Tue, 16 Apr 2024 16:02:23 -0400 Subject: [PATCH 1/2] feat: add `=` omnibox handler, wire it up to reopening in container --- src/_locales/dict.common.ts | 7 ++++ src/bg/background.ts | 3 ++ src/manifest.json | 3 ++ src/services/omnibox.ts | 71 +++++++++++++++++++++++++++++++++++++ src/sidebar/sidebar.ts | 1 + src/types/ipc.ts | 1 + 6 files changed, 86 insertions(+) create mode 100644 src/services/omnibox.ts diff --git a/src/_locales/dict.common.ts b/src/_locales/dict.common.ts index db0284dee..cb1d71cac 100644 --- a/src/_locales/dict.common.ts +++ b/src/_locales/dict.common.ts @@ -2780,6 +2780,13 @@ export const commonTranslations: Translations = { zh_CN: '扩展没有在隐私窗口中运行的权限', zh_TW: '擴充套件沒有於隱私視窗中執行的權限', }, + + // --- + // -- Omnibox + // - + 'omnibox.container_switch.prompt': { + en: 'Type the name of the container you want for this tab…', + }, } if (!window.translations) window.translations = commonTranslations diff --git a/src/bg/background.ts b/src/bg/background.ts index 93e02d3fb..8ef28fc8f 100644 --- a/src/bg/background.ts +++ b/src/bg/background.ts @@ -17,6 +17,7 @@ import { Menu } from 'src/services/menu' import { WebReq } from 'src/services/web-req' import { Sync } from 'src/services/_services' import { Styles } from 'src/services/styles' +import * as omnibox from 'src/services/omnibox' void (async function main() { Info.setInstanceType(InstanceType.bg) @@ -119,6 +120,8 @@ void (async function main() { if (newVersion <= currentVersion) browser.runtime.reload() }) + omnibox.setupListeners() + Logs.info(`Init end: ${performance.now() - ts}ms`) })() diff --git a/src/manifest.json b/src/manifest.json index deca3f557..ddc6263cd 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -501,5 +501,8 @@ }, "background": { "page": "bg/background.html" + }, + "omnibox": { + "keyword": "=" } } diff --git a/src/services/omnibox.ts b/src/services/omnibox.ts new file mode 100644 index 000000000..e70c4871c --- /dev/null +++ b/src/services/omnibox.ts @@ -0,0 +1,71 @@ +import { Containers } from 'src/services/containers' +import { translate } from 'src/dict' +import * as IPC from 'src/services/ipc' +import * as Logs from 'src/services/logs' +import { Tabs } from 'src/services/tabs.bg' +import { Windows } from 'src/services/windows' +import { Container, InstanceType } from 'src/types' + +function setupListeners() { + browser.omnibox.setDefaultSuggestion({ + description: translate('omnibox.container_switch.prompt'), + }) + + function matchContainers(input: string): Container[] { + // TODO: order by score of some sort? + return Object.values(Containers.reactive.byId).filter(container => + container.name.toLowerCase().includes(input.toLowerCase()) + ) + } + + browser.omnibox.onInputChanged.addListener(async (input, suggest) => { + const suggestions = matchContainers(input).map(ctx => ({ + content: ctx.name, + description: ctx.name, + deletable: false, + })) + suggest(suggestions) + }) + + browser.omnibox.onInputEntered.addListener(async (input, _disposition) => { + // NOTE: We're semantically _re-opening_ tabs, which conflicts with a disposition. Ignore it. + + if (!Windows.lastFocusedWinId) { + Logs.err('omnibox: no last focused window ID found') + return + } + + const matchingContainers = matchContainers(input) + if (matchingContainers.length <= 0) { + Logs.warn('omnibox: no matching containers found') + return + } + const firstMatchingContainer = matchingContainers[0] + + const sidebarTabs = await Tabs.getSidebarTabs(Windows.lastFocusedWinId) + if (!sidebarTabs) { + Logs.err('omnibox: no sidebar tabs found for last focused window ID') + return + } + + const con = IPC.getConnection(InstanceType.sidebar, Windows.lastFocusedWinId) + if ((con?.localPort && con.localPort.error) || (con?.remotePort && con.remotePort.error)) { + Logs.err('need to fall back to creating tabs by hand') + return + } + + const activeTabs = sidebarTabs.filter(tab => tab.active) + try { + await IPC.sidebar( + Windows.lastFocusedWinId, + 'reopenInContainer', + activeTabs.map(tab => tab.id), + firstMatchingContainer.id + ) + } catch { + Logs.warn('failed to re-open tabs', activeTabs, 'in container', firstMatchingContainer) + } + }) +} + +export { setupListeners } diff --git a/src/sidebar/sidebar.ts b/src/sidebar/sidebar.ts index d6cd33e6a..1f6938f6d 100644 --- a/src/sidebar/sidebar.ts +++ b/src/sidebar/sidebar.ts @@ -42,6 +42,7 @@ async function main(): Promise { getTabsTreeData: Tabs.getTabsTreeData, moveTabsToThisWin: Tabs.moveToThisWin, openTabs: Tabs.open, + reopenInContainer: Tabs.reopenInContainer, handleReopening: Tabs.handleReopening, getActivePanelConfig: Sidebar.getActivePanelConfig, stopDrag: DnD.onExternalStop, diff --git a/src/types/ipc.ts b/src/types/ipc.ts index 3d11465ac..c449965c2 100644 --- a/src/types/ipc.ts +++ b/src/types/ipc.ts @@ -114,6 +114,7 @@ export type SidebarActions = { moveTabsToThisWin: (tabs: Tab[], dst?: DstPlaceInfo) => Promise openTabs: (items: ItemInfo[], dst: DstPlaceInfo) => Promise + reopenInContainer: (ids: ID[], containerId: string) => Promise notify: (config: Notification, timeout?: number) => void notifyAboutNewSnapshot: () => void From c6a794bb64090be8633b154f98243564e4395cd0 Mon Sep 17 00:00:00 2001 From: Erich Gubler Date: Mon, 24 Nov 2025 09:07:41 -0500 Subject: [PATCH 2/2] FIXUP: minimum 3 characters before trying to match --- src/services/omnibox.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/services/omnibox.ts b/src/services/omnibox.ts index e70c4871c..096f56a21 100644 --- a/src/services/omnibox.ts +++ b/src/services/omnibox.ts @@ -19,11 +19,14 @@ function setupListeners() { } browser.omnibox.onInputChanged.addListener(async (input, suggest) => { - const suggestions = matchContainers(input).map(ctx => ({ - content: ctx.name, - description: ctx.name, - deletable: false, - })) + const suggestions = + input.length >= 3 + ? matchContainers(input).map(ctx => ({ + content: ctx.name, + description: ctx.name, + deletable: false, + })) + : [] suggest(suggestions) })