diff --git a/packages/extension/public/_locales/de/messages.json b/packages/extension/public/_locales/de/messages.json index 2cb260fe..468a18f2 100644 --- a/packages/extension/public/_locales/de/messages.json +++ b/packages/extension/public/_locales/de/messages.json @@ -212,6 +212,18 @@ "Option_popupAutoCloseDelay_placeholder": { "message": "0 (sofort schließen)" }, + "Option_sidePanelAutoHide": { + "message": "Seitenbereich automatisch ausblenden" + }, + "Option_sidePanelAutoHide_desc": { + "message": "Blendet den Seitenbereich automatisch aus, wenn in den Hauptbereich geklickt wird, während der Seitenbereich über einen Befehl geöffnet ist." + }, + "Option_sidePanelAutoHide_link": { + "message": "Seitenbereich automatisch ausblenden" + }, + "Option_sidePanelAutoHide_link_desc": { + "message": "Blendet den Seitenbereich automatisch aus, wenn in den Hauptbereich geklickt wird, während die Linkvorschau angezeigt wird." + }, "Option_inherit": { "message": "Erben" }, @@ -365,6 +377,9 @@ "Option_openMode_previewWindow": { "message": "Fenster" }, + "Option_openMode_previewSidePanel": { + "message": "Seitenbereich" + }, "Option_openModeSecondary": { "message": " ┗ Strg + Klick" }, diff --git a/packages/extension/public/_locales/en/messages.json b/packages/extension/public/_locales/en/messages.json index 2a6a37fd..98085c63 100644 --- a/packages/extension/public/_locales/en/messages.json +++ b/packages/extension/public/_locales/en/messages.json @@ -212,6 +212,18 @@ "Option_popupAutoCloseDelay_placeholder": { "message": "0 (close immediately)" }, + "Option_sidePanelAutoHide": { + "message": "Side Panel Auto-Hide" + }, + "Option_sidePanelAutoHide_desc": { + "message": "Automatically hide the side panel when the main panel is clicked while the side panel is open via a command." + }, + "Option_sidePanelAutoHide_link": { + "message": "Side Panel Auto-Hide" + }, + "Option_sidePanelAutoHide_link_desc": { + "message": "Automatically hide the side panel when the main panel is clicked while displaying the link preview." + }, "Option_inherit": { "message": "Inherit" }, @@ -365,6 +377,9 @@ "Option_openMode_previewWindow": { "message": "Window" }, + "Option_openMode_previewSidePanel": { + "message": "Side Panel" + }, "Option_openModeSecondary": { "message": " ┗ Ctrl + Click" }, diff --git a/packages/extension/public/_locales/es/messages.json b/packages/extension/public/_locales/es/messages.json index 7ddb0d23..dbe48076 100644 --- a/packages/extension/public/_locales/es/messages.json +++ b/packages/extension/public/_locales/es/messages.json @@ -212,6 +212,18 @@ "Option_popupAutoCloseDelay_placeholder": { "message": "0 (cerrar inmediatamente)" }, + "Option_sidePanelAutoHide": { + "message": "Ocultar panel lateral automáticamente" + }, + "Option_sidePanelAutoHide_desc": { + "message": "Oculta automáticamente el panel lateral cuando se hace clic en el panel principal mientras el panel lateral está abierto a través de un comando." + }, + "Option_sidePanelAutoHide_link": { + "message": "Ocultar panel lateral automáticamente" + }, + "Option_sidePanelAutoHide_link_desc": { + "message": "Oculta automáticamente el panel lateral cuando se hace clic en el panel principal mientras se muestra la vista previa del enlace." + }, "Option_inherit": { "message": "Heredar" }, @@ -365,6 +377,9 @@ "Option_openMode_previewWindow": { "message": "Ventana" }, + "Option_openMode_previewSidePanel": { + "message": "Panel lateral" + }, "Option_openModeSecondary": { "message": " ┗ Ctrl + Clic" }, diff --git a/packages/extension/public/_locales/fr/messages.json b/packages/extension/public/_locales/fr/messages.json index f2f29ed6..118e2cd8 100644 --- a/packages/extension/public/_locales/fr/messages.json +++ b/packages/extension/public/_locales/fr/messages.json @@ -212,6 +212,18 @@ "Option_popupAutoCloseDelay_placeholder": { "message": "0 (fermer immédiatement)" }, + "Option_sidePanelAutoHide": { + "message": "Masquage automatique du panneau latéral" + }, + "Option_sidePanelAutoHide_desc": { + "message": "Masque automatiquement le panneau latéral lorsque le panneau principal est cliqué pendant que le panneau latéral est ouvert via une commande." + }, + "Option_sidePanelAutoHide_link": { + "message": "Masquage automatique du panneau latéral" + }, + "Option_sidePanelAutoHide_link_desc": { + "message": "Masque automatiquement le panneau latéral lorsque le panneau principal est cliqué pendant l'affichage de l'aperçu du lien." + }, "Option_inherit": { "message": "Hériter" }, @@ -365,6 +377,9 @@ "Option_openMode_previewWindow": { "message": "Fenêtre" }, + "Option_openMode_previewSidePanel": { + "message": "Panneau latéral" + }, "Option_openModeSecondary": { "message": " ┗ Ctrl + Clic" }, diff --git a/packages/extension/public/_locales/hi/messages.json b/packages/extension/public/_locales/hi/messages.json index 700f4f31..d87aed3f 100644 --- a/packages/extension/public/_locales/hi/messages.json +++ b/packages/extension/public/_locales/hi/messages.json @@ -212,6 +212,18 @@ "Option_popupAutoCloseDelay_placeholder": { "message": "0 (तुरंत बंद करें)" }, + "Option_sidePanelAutoHide": { + "message": "साइड पैनल स्वतः छिपाएं" + }, + "Option_sidePanelAutoHide_desc": { + "message": "कमांड के माध्यम से साइड पैनल खुले होने पर मुख्य पैनल पर क्लिक करने पर साइड पैनल को स्वतः छिपा देता है।" + }, + "Option_sidePanelAutoHide_link": { + "message": "साइड पैनल स्वतः छिपाएं" + }, + "Option_sidePanelAutoHide_link_desc": { + "message": "लिंक पूर्वावलोकन प्रदर्शित करते समय मुख्य पैनल पर क्लिक करने पर साइड पैनल को स्वतः छिपा देता है।" + }, "Option_inherit": { "message": "प्राप्त करें" }, @@ -365,6 +377,9 @@ "Option_openMode_previewWindow": { "message": "विंडो" }, + "Option_openMode_previewSidePanel": { + "message": "साइड पैनल" + }, "Option_openModeSecondary": { "message": " ┗ Ctrl + क्लिक" }, diff --git a/packages/extension/public/_locales/id/messages.json b/packages/extension/public/_locales/id/messages.json index b1d44abc..c9dfae34 100644 --- a/packages/extension/public/_locales/id/messages.json +++ b/packages/extension/public/_locales/id/messages.json @@ -212,6 +212,18 @@ "Option_popupAutoCloseDelay_placeholder": { "message": "0 (tutup segera)" }, + "Option_sidePanelAutoHide": { + "message": "Sembunyikan Panel Samping Otomatis" + }, + "Option_sidePanelAutoHide_desc": { + "message": "Secara otomatis menyembunyikan panel samping saat panel utama diklik saat panel samping terbuka melalui perintah." + }, + "Option_sidePanelAutoHide_link": { + "message": "Sembunyikan Panel Samping Otomatis" + }, + "Option_sidePanelAutoHide_link_desc": { + "message": "Secara otomatis menyembunyikan panel samping saat panel utama diklik saat menampilkan pratinjau tautan." + }, "Option_inherit": { "message": "Warisi" }, @@ -365,6 +377,9 @@ "Option_openMode_previewWindow": { "message": "Jendela" }, + "Option_openMode_previewSidePanel": { + "message": "Panel Samping" + }, "Option_openModeSecondary": { "message": " ┗ Ctrl + Klik" }, diff --git a/packages/extension/public/_locales/it/messages.json b/packages/extension/public/_locales/it/messages.json index 7381941f..6465bcea 100644 --- a/packages/extension/public/_locales/it/messages.json +++ b/packages/extension/public/_locales/it/messages.json @@ -212,6 +212,18 @@ "Option_popupAutoCloseDelay_placeholder": { "message": "0 (chiudi immediatamente)" }, + "Option_sidePanelAutoHide": { + "message": "Nascondi automaticamente il pannello laterale" + }, + "Option_sidePanelAutoHide_desc": { + "message": "Nasconde automaticamente il pannello laterale quando si fa clic sul pannello principale mentre il pannello laterale è aperto tramite un comando." + }, + "Option_sidePanelAutoHide_link": { + "message": "Nascondi automaticamente il pannello laterale" + }, + "Option_sidePanelAutoHide_link_desc": { + "message": "Nasconde automaticamente il pannello laterale quando si fa clic sul pannello principale durante la visualizzazione dell'anteprima del collegamento." + }, "Option_inherit": { "message": "Eredita" }, @@ -365,6 +377,9 @@ "Option_openMode_previewWindow": { "message": "Finestra" }, + "Option_openMode_previewSidePanel": { + "message": "Pannello laterale" + }, "Option_openModeSecondary": { "message": " ┗ Ctrl + clic" }, diff --git a/packages/extension/public/_locales/ja/messages.json b/packages/extension/public/_locales/ja/messages.json index 4d8fcad8..d2cfb9a2 100644 --- a/packages/extension/public/_locales/ja/messages.json +++ b/packages/extension/public/_locales/ja/messages.json @@ -212,6 +212,18 @@ "Option_popupAutoCloseDelay_placeholder": { "message": "0 (即座に閉じる)" }, + "Option_sidePanelAutoHide": { + "message": "サイドパネル自動非表示" + }, + "Option_sidePanelAutoHide_desc": { + "message": "コマンドによりサイドパネルを表示している時、メインのパネルをクリックしたときに、サイドパネルを自動的に非表示にします。" + }, + "Option_sidePanelAutoHide_link": { + "message": "サイドパネル自動非表示" + }, + "Option_sidePanelAutoHide_link_desc": { + "message": "リンクプレビューの表示中、メインのパネルをクリックしたときに、サイドパネルを自動的に非表示にします。" + }, "Option_inherit": { "message": "継承" }, @@ -360,10 +372,13 @@ "message": "実験的" }, "Option_openMode_previewPopup": { - "message": "Popup" + "message": "ポップアップ" }, "Option_openMode_previewWindow": { - "message": "Window" + "message": "ウィンドウ" + }, + "Option_openMode_previewSidePanel": { + "message": "サイドパネル" }, "Option_openModeSecondary": { "message": " ┗ Ctrl + クリック" diff --git a/packages/extension/public/_locales/ko/messages.json b/packages/extension/public/_locales/ko/messages.json index e2cfa418..860308f0 100644 --- a/packages/extension/public/_locales/ko/messages.json +++ b/packages/extension/public/_locales/ko/messages.json @@ -212,6 +212,18 @@ "Option_popupAutoCloseDelay_placeholder": { "message": "0 (즉시 닫기)" }, + "Option_sidePanelAutoHide": { + "message": "사이드 패널 자동 숨기기" + }, + "Option_sidePanelAutoHide_desc": { + "message": "명령을 통해 사이드 패널이 열려 있는 동안 메인 패널을 클릭하면 사이드 패널이 자동으로 숨겨집니다." + }, + "Option_sidePanelAutoHide_link": { + "message": "사이드 패널 자동 숨기기" + }, + "Option_sidePanelAutoHide_link_desc": { + "message": "링크 미리보기 표시 중 메인 패널을 클릭하면 사이드 패널이 자동으로 숨겨집니다." + }, "Option_inherit": { "message": "상속" }, @@ -365,6 +377,9 @@ "Option_openMode_previewWindow": { "message": "창" }, + "Option_openMode_previewSidePanel": { + "message": "사이드 패널" + }, "Option_openModeSecondary": { "message": " ┗ Ctrl + 클릭" }, diff --git a/packages/extension/public/_locales/ms/messages.json b/packages/extension/public/_locales/ms/messages.json index 7bbbbbb9..d96eb7d1 100644 --- a/packages/extension/public/_locales/ms/messages.json +++ b/packages/extension/public/_locales/ms/messages.json @@ -212,6 +212,18 @@ "Option_popupAutoCloseDelay_placeholder": { "message": "0 (tutup segera)" }, + "Option_sidePanelAutoHide": { + "message": "Sembunyikan Panel Sisi Secara Automatik" + }, + "Option_sidePanelAutoHide_desc": { + "message": "Menyembunyikan panel sisi secara automatik apabila panel utama diklik semasa panel sisi dibuka melalui perintah." + }, + "Option_sidePanelAutoHide_link": { + "message": "Sembunyikan Panel Sisi Secara Automatik" + }, + "Option_sidePanelAutoHide_link_desc": { + "message": "Menyembunyikan panel sisi secara automatik apabila panel utama diklik semasa memaparkan pratonton pautan." + }, "Option_inherit": { "message": "Warisi" }, @@ -365,6 +377,9 @@ "Option_openMode_previewWindow": { "message": "Tetingkap" }, + "Option_openMode_previewSidePanel": { + "message": "Panel Sisi" + }, "Option_openModeSecondary": { "message": " ┗ Ctrl + Klik" }, diff --git a/packages/extension/public/_locales/pt_BR/messages.json b/packages/extension/public/_locales/pt_BR/messages.json index f1682e77..62f53f54 100644 --- a/packages/extension/public/_locales/pt_BR/messages.json +++ b/packages/extension/public/_locales/pt_BR/messages.json @@ -212,6 +212,18 @@ "Option_popupAutoCloseDelay_placeholder": { "message": "0 (fechar imediatamente)" }, + "Option_sidePanelAutoHide": { + "message": "Ocultar painel lateral automaticamente" + }, + "Option_sidePanelAutoHide_desc": { + "message": "Oculta automaticamente o painel lateral quando o painel principal é clicado enquanto o painel lateral está aberto via um comando." + }, + "Option_sidePanelAutoHide_link": { + "message": "Ocultar painel lateral automaticamente" + }, + "Option_sidePanelAutoHide_link_desc": { + "message": "Oculta automaticamente o painel lateral quando o painel principal é clicado durante a exibição da pré-visualização do link." + }, "Option_inherit": { "message": "Herdar" }, @@ -365,6 +377,9 @@ "Option_openMode_previewWindow": { "message": "Janela" }, + "Option_openMode_previewSidePanel": { + "message": "Painel lateral" + }, "Option_openModeSecondary": { "message": " ┗ Ctrl + Clique" }, diff --git a/packages/extension/public/_locales/pt_PT/messages.json b/packages/extension/public/_locales/pt_PT/messages.json index f4f49e35..9e2042dd 100644 --- a/packages/extension/public/_locales/pt_PT/messages.json +++ b/packages/extension/public/_locales/pt_PT/messages.json @@ -212,6 +212,18 @@ "Option_popupAutoCloseDelay_placeholder": { "message": "0 (fechar imediatamente)" }, + "Option_sidePanelAutoHide": { + "message": "Ocultar painel lateral automaticamente" + }, + "Option_sidePanelAutoHide_desc": { + "message": "Oculta automaticamente o painel lateral quando o painel principal é clicado enquanto o painel lateral está aberto via um comando." + }, + "Option_sidePanelAutoHide_link": { + "message": "Ocultar painel lateral automaticamente" + }, + "Option_sidePanelAutoHide_link_desc": { + "message": "Oculta automaticamente o painel lateral quando o painel principal é clicado durante a visualização da pré-visualização do link." + }, "Option_inherit": { "message": "Herdar" }, @@ -365,6 +377,9 @@ "Option_openMode_previewWindow": { "message": "Janela" }, + "Option_openMode_previewSidePanel": { + "message": "Painel lateral" + }, "Option_openModeSecondary": { "message": " ┗ Ctrl + Clique" }, diff --git a/packages/extension/public/_locales/ru/messages.json b/packages/extension/public/_locales/ru/messages.json index 039ec78e..d4c5587c 100644 --- a/packages/extension/public/_locales/ru/messages.json +++ b/packages/extension/public/_locales/ru/messages.json @@ -212,6 +212,18 @@ "Option_popupAutoCloseDelay_placeholder": { "message": "0 (закрыть немедленно)" }, + "Option_sidePanelAutoHide": { + "message": "Автоматически скрывать боковую панель" + }, + "Option_sidePanelAutoHide_desc": { + "message": "Автоматически скрывает боковую панель при клике на основную панель, пока боковая панель открыта через команду." + }, + "Option_sidePanelAutoHide_link": { + "message": "Автоматически скрывать боковую панель" + }, + "Option_sidePanelAutoHide_link_desc": { + "message": "Автоматически скрывает боковую панель при клике на основную панель во время отображения предварительного просмотра ссылки." + }, "Option_inherit": { "message": "Наследовать" }, @@ -365,6 +377,9 @@ "Option_openMode_previewWindow": { "message": "Окно" }, + "Option_openMode_previewSidePanel": { + "message": "Боковая панель" + }, "Option_openModeSecondary": { "message": " ┗ Ctrl + клик" }, diff --git a/packages/extension/public/_locales/zh_CN/messages.json b/packages/extension/public/_locales/zh_CN/messages.json index 9a43837c..f87db698 100644 --- a/packages/extension/public/_locales/zh_CN/messages.json +++ b/packages/extension/public/_locales/zh_CN/messages.json @@ -212,6 +212,18 @@ "Option_popupAutoCloseDelay_placeholder": { "message": "0(立即关闭)" }, + "Option_sidePanelAutoHide": { + "message": "侧边栏自动隐藏" + }, + "Option_sidePanelAutoHide_desc": { + "message": "通过命令打开侧边栏时,点击主面板自动隐藏侧边栏。" + }, + "Option_sidePanelAutoHide_link": { + "message": "侧边栏自动隐藏" + }, + "Option_sidePanelAutoHide_link_desc": { + "message": "显示链接预览时,点击主面板自动隐藏侧边栏。" + }, "Option_inherit": { "message": "获取" }, @@ -365,6 +377,9 @@ "Option_openMode_previewWindow": { "message": "窗口" }, + "Option_openMode_previewSidePanel": { + "message": "侧边栏" + }, "Option_openModeSecondary": { "message": " ┗ Ctrl + 点击" }, diff --git a/packages/extension/src/action/helper.test.ts b/packages/extension/src/action/helper.test.ts index c045166a..97cfa530 100644 --- a/packages/extension/src/action/helper.test.ts +++ b/packages/extension/src/action/helper.test.ts @@ -35,7 +35,7 @@ describe("helper", () => { const sender = {} as any vi.mocked(BgData.get).mockReturnValue({ - sidePanelTabs: [123], + sidePanelTabs: [{ tabId: 123, isLinkCommand: false }], } as any) const result = navigateSidePanel(param, sender) @@ -48,7 +48,7 @@ describe("helper", () => { const sender = {} as any vi.mocked(BgData.get).mockReturnValue({ - sidePanelTabs: [123], + sidePanelTabs: [{ tabId: 123, isLinkCommand: false }], } as any) const result = navigateSidePanel(param, sender) @@ -61,7 +61,7 @@ describe("helper", () => { const sender = {} as any vi.mocked(BgData.get).mockReturnValue({ - sidePanelTabs: [123], + sidePanelTabs: [{ tabId: 123, isLinkCommand: false }], } as any) const result = navigateSidePanel(param, sender) @@ -74,7 +74,10 @@ describe("helper", () => { const sender = {} as any vi.mocked(BgData.get).mockReturnValue({ - sidePanelTabs: [456, 789], // Different tab IDs + sidePanelTabs: [ + { tabId: 456, isLinkCommand: false }, + { tabId: 789, isLinkCommand: false }, + ], // Different tab IDs } as any) const result = navigateSidePanel(param, sender) @@ -89,7 +92,7 @@ describe("helper", () => { const sender = {} as any vi.mocked(BgData.get).mockReturnValue({ - sidePanelTabs: [tabId], + sidePanelTabs: [{ tabId, isLinkCommand: false }], sidePanelUrls: {}, } as any) @@ -113,7 +116,7 @@ describe("helper", () => { const sender = {} as any vi.mocked(BgData.get).mockReturnValue({ - sidePanelTabs: [tabId], + sidePanelTabs: [{ tabId, isLinkCommand: false }], sidePanelUrls: {}, } as any) @@ -147,7 +150,7 @@ describe("helper", () => { const sender = {} as any const mockData = { - sidePanelTabs: [tabId], + sidePanelTabs: [{ tabId, isLinkCommand: false }], sidePanelUrls: {}, } as any diff --git a/packages/extension/src/action/helper.ts b/packages/extension/src/action/helper.ts index 7044af4f..5ac005ab 100644 --- a/packages/extension/src/action/helper.ts +++ b/packages/extension/src/action/helper.ts @@ -123,10 +123,11 @@ export const openSidePanel = ( .then(() => { // Register the tab ID for tracking if (tabId) { + const newEntry = { tabId, isLinkCommand: param.isLinkCommand ?? false } return BgData.update((data) => ({ - sidePanelTabs: data.sidePanelTabs.includes(tabId) - ? data.sidePanelTabs - : [...data.sidePanelTabs, tabId], + sidePanelTabs: data.sidePanelTabs.some((t) => t.tabId === tabId) + ? data.sidePanelTabs.map((t) => (t.tabId === tabId ? newEntry : t)) + : [...data.sidePanelTabs, newEntry], })) } }) @@ -150,12 +151,14 @@ export const closeSidePanel = ( if (tabId == null) { return false } - enhancedSettings.get().then(async (settings) => { - const sidePanelAutoHide = settings.windowOption.sidePanelAutoHide - if (sidePanelAutoHide) { - const bgData = BgData.get() - if (bgData.sidePanelTabs.includes(tabId)) { + const bgData = BgData.get() + const tab = bgData.sidePanelTabs.find((t) => t.tabId === tabId) + if (tab) { + const autoHideEnabled = tab.isLinkCommand + ? settings.linkCommand.sidePanelAutoHide + : settings.windowOption.sidePanelAutoHide + if (autoHideEnabled) { await _closeSidePanel(tabId) } } @@ -165,6 +168,27 @@ export const closeSidePanel = ( return true } +/** + * Handle side panel closed event for a tab + * @param {number} tabId - The ID of the tab whose side panel was closed + * @return {Promise} A promise that resolves when the side panel closed event is handled + * This function is called when a side panel is closed, either by user action or programmatically. + */ +export const sidePanelClosed = async (tabId?: number): Promise => { + if (!tabId) return + try { + BgData.update((data) => { + const { [tabId]: _, ...rest } = data.sidePanelUrls + return { + sidePanelTabs: data.sidePanelTabs.filter((t) => t.tabId !== tabId), + sidePanelUrls: rest, + } + }) + } catch (e) { + console.warn("Failed to cleanup side panel:", e) + } +} + export const navigateSidePanel = ( param: NavigateSidePanelProps, _sender: Sender, @@ -191,7 +215,7 @@ export const navigateSidePanel = ( // Check if tab is in sidePanelTabs const bgData = BgData.get() - if (!bgData.sidePanelTabs.includes(tabId)) { + if (!bgData.sidePanelTabs.some((t) => t.tabId === tabId)) { console.warn("[navigateSidePanel] Tab is not in sidePanelTabs:", tabId) return false } diff --git a/packages/extension/src/action/linkPreview.ts b/packages/extension/src/action/linkPreview.ts index f55f24d1..709dec44 100644 --- a/packages/extension/src/action/linkPreview.ts +++ b/packages/extension/src/action/linkPreview.ts @@ -8,6 +8,7 @@ import { getScreenSize } from "@/services/screen" import { DRAG_OPEN_MODE, POPUP_TYPE } from "@/const" import { isEmpty } from "@/lib/utils" import type { ExecuteCommandParams } from "@/types" +import type { OpenSidePanelProps } from "@/services/chrome" export const LinkPreview = { async execute({ command, position, target }: ExecuteCommandParams) { @@ -15,6 +16,16 @@ export const LinkPreview = { const elm = findAnchorElementFromParent(target) as HTMLAnchorElement const href = elm?.href + if (command.openMode === DRAG_OPEN_MODE.PREVIEW_SIDE_PANEL) { + if (!isEmpty(href)) { + Ipc.send(BgCommand.openSidePanel, { + url: href, + isLinkCommand: true, + }) + } + return + } + const type = command.openMode === DRAG_OPEN_MODE.PREVIEW_POPUP ? POPUP_TYPE.POPUP diff --git a/packages/extension/src/background_script.ts b/packages/extension/src/background_script.ts index d6eb43eb..a652bb17 100644 --- a/packages/extension/src/background_script.ts +++ b/packages/extension/src/background_script.ts @@ -618,15 +618,10 @@ chrome.commands.onCommand.addListener(async (commandName) => { // SidePanel auto-hide functionality // Track tabs with active side panels -chrome.tabs.onRemoved.addListener((tabId) => { - BgData.update((data) => { - const { [tabId]: _, ...rest } = data.sidePanelUrls - return { - sidePanelTabs: data.sidePanelTabs.filter((id) => id !== tabId), - sidePanelUrls: rest, - } - }) -}) +chrome.tabs.onRemoved.addListener(ActionHelper.sidePanelClosed) +chrome.sidePanel.onClosed.addListener(({ tabId }) => + ActionHelper.sidePanelClosed(tabId), +) // Export functions for testing export const testExports = { diff --git a/packages/extension/src/components/Popup.tsx b/packages/extension/src/components/Popup.tsx index 4926b9c4..67f61e95 100644 --- a/packages/extension/src/components/Popup.tsx +++ b/packages/extension/src/components/Popup.tsx @@ -5,6 +5,7 @@ import { useUserSettings } from "@/hooks/useSettings" import { useDetectStartup } from "@/hooks/useDetectStartup" import { useTabCommandReceiver } from "@/hooks/useTabCommandReceiver" import { useSidePanelNavigation } from "@/hooks/useSidePanelNavigation" +import { useSidePanelAutoClose } from "@/hooks/useSidePanelAutoClose" import { hexToHsl, isMac, onHover, cn } from "@/lib/utils" import { t } from "@/services/i18n" import { STYLE_VARIABLE, EXIT_DURATION, SIDE, ALIGN } from "@/const" @@ -29,6 +30,7 @@ export const Popup = forwardRef( (props: PopupProps, ref) => { useTabCommandReceiver() useSidePanelNavigation() + useSidePanelAutoClose() const { userSettings } = useUserSettings() const [inTransition, setInTransition] = useState(false) diff --git a/packages/extension/src/components/option/SettingForm.tsx b/packages/extension/src/components/option/SettingForm.tsx index 20b1ae0c..72b4517e 100644 --- a/packages/extension/src/components/option/SettingForm.tsx +++ b/packages/extension/src/components/option/SettingForm.tsx @@ -100,6 +100,7 @@ const formSchema = z .refine((v) => v !== LINK_COMMAND_ENABLED.INHERIT), openMode: z.nativeEnum(DRAG_OPEN_MODE), showIndicator: z.boolean(), + sidePanelAutoHide: z.boolean(), startupMethod: z .object({ method: z.nativeEnum(LINK_COMMAND_STARTUP_METHOD), @@ -164,6 +165,12 @@ export function SettingForm({ className }: { className?: string }) { defaultValue: LINK_COMMAND_STARTUP_METHOD.KEYBOARD, }) + const linkCommandOpenMode = useWatch({ + control: form.control, + name: "linkCommand.openMode", + defaultValue: DRAG_OPEN_MODE.PREVIEW_POPUP, + }) + // Common function to load and transform settings data const loadSettingsData = async () => { const settings = await enhancedSettings.get({ excludeOptions: true }) @@ -586,6 +593,14 @@ export function SettingForm({ className }: { className?: string }) { formLabel={t("showIndicator")} description={t("showIndicator_desc")} /> + {linkCommandOpenMode === DRAG_OPEN_MODE.PREVIEW_SIDE_PANEL && ( + + )}
@@ -595,6 +610,7 @@ export function SettingForm({ className }: { className?: string }) { {t("windowSettings")}

{t("windowSettings_desc")}

+ + +
diff --git a/packages/extension/src/components/option/field/SwitchField.tsx b/packages/extension/src/components/option/field/SwitchField.tsx index e906d12f..4f39aeab 100644 --- a/packages/extension/src/components/option/field/SwitchField.tsx +++ b/packages/extension/src/components/option/field/SwitchField.tsx @@ -1,3 +1,4 @@ +import { useRef } from "react" import { Switch } from "@/components/ui/switch" import { @@ -8,12 +9,16 @@ import { FormLabel, FormMessage, } from "@/components/ui/form" +import { Info } from "lucide-react" +import { Tooltip } from "@/components/Tooltip" +import { cn } from "@/lib/utils" type SwitchFieldType = { control: any name: string formLabel: string description?: string + tooltip?: string } export const SwitchField = ({ @@ -21,7 +26,10 @@ export const SwitchField = ({ name, formLabel, description, + tooltip, }: SwitchFieldType) => { + const span = useRef(null) + return ( (
- {formLabel} + + {formLabel} + {tooltip && ( + + + + )} + {description && {description}} + {tooltip && ( + + )}
diff --git a/packages/extension/src/const.ts b/packages/extension/src/const.ts index 847d8546..5964c642 100644 --- a/packages/extension/src/const.ts +++ b/packages/extension/src/const.ts @@ -139,6 +139,7 @@ export enum ExecState { export enum DRAG_OPEN_MODE { PREVIEW_POPUP = "previewPopup", PREVIEW_WINDOW = "previewWindow", + PREVIEW_SIDE_PANEL = "previewSidePanel", } export enum SIDE { diff --git a/packages/extension/src/hooks/useSettings.test.tsx b/packages/extension/src/hooks/useSettings.test.tsx index c5a0330c..74b297be 100644 --- a/packages/extension/src/hooks/useSettings.test.tsx +++ b/packages/extension/src/hooks/useSettings.test.tsx @@ -63,6 +63,7 @@ const createMockUserSettings = ( enabled: LINK_COMMAND_ENABLED.ENABLE, openMode: DRAG_OPEN_MODE.PREVIEW_POPUP, showIndicator: true, + sidePanelAutoHide: false, startupMethod: { method: LINK_COMMAND_STARTUP_METHOD.KEYBOARD, keyboardParam: KEYBOARD.SHIFT, diff --git a/packages/extension/src/hooks/useSidePanelAutoClose.ts b/packages/extension/src/hooks/useSidePanelAutoClose.ts new file mode 100644 index 00000000..ea7b03fa --- /dev/null +++ b/packages/extension/src/hooks/useSidePanelAutoClose.ts @@ -0,0 +1,50 @@ +import { useEffect, useState } from "react" +import { Ipc, BgCommand } from "@/services/ipc" +import { BgData } from "@/services/backgroundData" +import { useTabContext } from "./useTabContext" +import { useUserSettings } from "./useSettings" + +export function useSidePanelAutoClose() { + const { tabId } = useTabContext() + const { userSettings } = useUserSettings() + const [sidePanelVisible, setSidePanelVisible] = useState(false) + const [isLinkCommand, setIsLinkCommand] = useState(false) + + useEffect(() => { + const update = (data: BgData) => { + if (tabId == null) { + setSidePanelVisible(false) + setIsLinkCommand(false) + return + } + const tab = data.sidePanelTabs.find((t) => t.tabId === tabId) + setSidePanelVisible(tab != null) + setIsLinkCommand(tab?.isLinkCommand ?? false) + } + + const initialData = BgData.get() + update(initialData) + + return BgData.watch((newVal) => update(newVal)) + }, [tabId]) + + useEffect(() => { + if (tabId == null) return + if (!sidePanelVisible) return + + // Determine which setting to use based on whether this is a link command + const autoHideEnabled = isLinkCommand + ? userSettings?.linkCommand?.sidePanelAutoHide + : userSettings?.windowOption?.sidePanelAutoHide + + // Only enable auto-close if the setting is enabled + if (!autoHideEnabled) return + + const close = () => Ipc.send(BgCommand.closeSidePanel) + + window.addEventListener("click", close) + return () => { + window.removeEventListener("click", close) + } + }, [tabId, sidePanelVisible, userSettings, isLinkCommand]) +} diff --git a/packages/extension/src/services/backgroundData.ts b/packages/extension/src/services/backgroundData.ts index d614480e..70d04d4f 100644 --- a/packages/extension/src/services/backgroundData.ts +++ b/packages/extension/src/services/backgroundData.ts @@ -6,6 +6,11 @@ type updaterPartial = (val: BgData) => Partial type watchCallback = (newVal: BgData, oldVal: BgData) => void +export type SidePanelTab = { + tabId: number + isLinkCommand: boolean +} + export class BgData { private static instance: BgData @@ -14,7 +19,7 @@ export class BgData { public pageActionStop: boolean public activeScreenId: string | null public connectedTabs: number[] - public sidePanelTabs: number[] + public sidePanelTabs: SidePanelTab[] public sidePanelUrls: Record private constructor(val: BgData | undefined) { diff --git a/packages/extension/src/services/chrome.ts b/packages/extension/src/services/chrome.ts index 38f94409..088bcc3a 100644 --- a/packages/extension/src/services/chrome.ts +++ b/packages/extension/src/services/chrome.ts @@ -332,11 +332,11 @@ const readClipboardContent = async ( ): Promise => { try { const result = await new Promise((resolve) => { - chrome.runtime.onConnect.addListener(function (port) { + chrome.runtime.onConnect.addListener(function(port) { if (port.sender?.tab?.id !== tabId) { return } - port.onMessage.addListener(function (msg) { + port.onMessage.addListener(function(msg) { if (msg.command === BgCommand.setClipboard) { resolve(msg.data) } @@ -621,6 +621,7 @@ export async function closeWindow( export type OpenSidePanelProps = { url: string | UrlParam tabId?: number + isLinkCommand?: boolean } export type UpdateSidePanelUrlProps = { @@ -673,23 +674,6 @@ export const closeSidePanel = async (tabId: number): Promise => { } catch (e) { console.warn("Failed to close side panel:", e) } - - // Cleanup regardless of whether close succeeded - try { - await BgData.update((data) => { - const { [tabId]: _, ...rest } = data.sidePanelUrls - return { - sidePanelTabs: data.sidePanelTabs.filter((id) => id !== tabId), - sidePanelUrls: rest, - } - }) - await chrome.sidePanel.setOptions({ - tabId: tabId, - enabled: false, - }) - } catch (e) { - console.warn("Failed to cleanup side panel:", e) - } } /** diff --git a/packages/extension/src/services/option/defaultSettings.ts b/packages/extension/src/services/option/defaultSettings.ts index 1f8533c1..a0e3f5b6 100644 --- a/packages/extension/src/services/option/defaultSettings.ts +++ b/packages/extension/src/services/option/defaultSettings.ts @@ -40,6 +40,7 @@ export const emptySettings: SettingsType = { enabled: LINK_COMMAND_ENABLED.ENABLE, openMode: DRAG_OPEN_MODE.PREVIEW_POPUP, showIndicator: true, + sidePanelAutoHide: false, startupMethod: { method: LINK_COMMAND_STARTUP_METHOD.KEYBOARD, keyboardParam: KEYBOARD.SHIFT, @@ -71,6 +72,7 @@ export default { enabled: LINK_COMMAND_ENABLED.ENABLE, openMode: DRAG_OPEN_MODE.PREVIEW_POPUP, showIndicator: true, + sidePanelAutoHide: false, startupMethod: { method: LINK_COMMAND_STARTUP_METHOD.KEYBOARD, keyboardParam: KEYBOARD.SHIFT, diff --git a/packages/extension/src/services/settings/settings.ts b/packages/extension/src/services/settings/settings.ts index fc2e130a..dc8736ac 100644 --- a/packages/extension/src/services/settings/settings.ts +++ b/packages/extension/src/services/settings/settings.ts @@ -243,6 +243,10 @@ export const migrate = async (data: SettingsType): Promise => { data.settingVersion = VERSION as Version data = migrate0_14_3(data) } + if (versionDiff(data.settingVersion, "0.15.0") === VersionDiff.Old) { + data.settingVersion = VERSION as Version + data = migrate0_15_0(data) + } return data } @@ -352,3 +356,13 @@ const migrate0_14_3 = (data: SettingsType): SettingsType => { } return data } + +const migrate0_15_0 = (data: SettingsType): SettingsType => { + // Add linkCommand.sidePanelAutoHide if not exists + if (data.linkCommand != null && data.linkCommand.sidePanelAutoHide == null) { + data.linkCommand.sidePanelAutoHide = + DefaultSettings.linkCommand.sidePanelAutoHide + console.debug("migrate 0.15.0: added linkCommand.sidePanelAutoHide") + } + return data +} diff --git a/packages/extension/src/services/sidePanelDetector.test.ts b/packages/extension/src/services/sidePanelDetector.test.ts index a337a947..c3301ba1 100644 --- a/packages/extension/src/services/sidePanelDetector.test.ts +++ b/packages/extension/src/services/sidePanelDetector.test.ts @@ -25,7 +25,7 @@ describe("sidePanelDetector", () => { it("SPD-01-a: Should continue checking when tabId is undefined (treated same as null)", () => { vi.mocked(BgData.get).mockReturnValue({ - sidePanelTabs: [789], // activeTabId not in list + sidePanelTabs: [{ tabId: 789, isLinkCommand: false }], } as any) const result = isSidePanel(undefined, 789) @@ -41,7 +41,7 @@ describe("sidePanelDetector", () => { it("SPD-03: Should return false when activeTabId is not in sidePanelTabs", () => { vi.mocked(BgData.get).mockReturnValue({ - sidePanelTabs: [789], // activeTabId not in list + sidePanelTabs: [{ tabId: 789, isLinkCommand: false }], // activeTabId not in list } as any) const result = isSidePanel(null, 456) @@ -52,7 +52,7 @@ describe("sidePanelDetector", () => { it("SPD-04: Should return true when all conditions are met", () => { vi.mocked(BgData.get).mockReturnValue({ - sidePanelTabs: [456], + sidePanelTabs: [{ tabId: 456, isLinkCommand: false }], } as any) const result = isSidePanel(null, 456) diff --git a/packages/extension/src/services/sidePanelDetector.ts b/packages/extension/src/services/sidePanelDetector.ts index 4b021229..606a4b06 100644 --- a/packages/extension/src/services/sidePanelDetector.ts +++ b/packages/extension/src/services/sidePanelDetector.ts @@ -17,7 +17,7 @@ export const isSidePanel = ( // Check if tab is in sidePanelTabs const bgData = BgData.get() - if (!bgData.sidePanelTabs.includes(activeTabId)) return false + if (!bgData.sidePanelTabs.some((t) => t.tabId === activeTabId)) return false return true } diff --git a/packages/extension/src/test/setup.ts b/packages/extension/src/test/setup.ts index 2a49bb42..ed6089d7 100644 --- a/packages/extension/src/test/setup.ts +++ b/packages/extension/src/test/setup.ts @@ -341,6 +341,12 @@ global.chrome = { addListener: vi.fn(), }, }, + sidePanel: { + onClosed: { + addListener: vi.fn(), + removeListener: vi.fn(), + }, + }, } as any // Mock window.matchMedia diff --git a/packages/extension/src/types/index.ts b/packages/extension/src/types/index.ts index aa2bcf69..ada419d3 100644 --- a/packages/extension/src/types/index.ts +++ b/packages/extension/src/types/index.ts @@ -96,6 +96,7 @@ type LinkCommandSettings = { openMode: DRAG_OPEN_MODE showIndicator: boolean startupMethod: LinkCommandStartupMethod + sidePanelAutoHide: boolean } export type CommandFolder = {