diff --git a/src-tauri/src/common/document.rs b/src-tauri/src/common/document.rs index 875aa630..724b82bd 100644 --- a/src-tauri/src/common/document.rs +++ b/src-tauri/src/common/document.rs @@ -291,8 +291,9 @@ pub(crate) async fn open( to_value(permission).unwrap(), to_value(ui).unwrap(), ]; + use crate::common::MAIN_WINDOW_LABEL; tauri_app_handle - .emit("open_view_extension", view_extension_opened) + .emit_to(MAIN_WINDOW_LABEL, "open_view_extension", view_extension_opened) .unwrap(); } } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ac36c44e..1f7edba6 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -407,6 +407,13 @@ async fn show_view_extension( height: Option, ) { log::debug!("view extension menu item was clicked"); + if query + .as_ref() + .map(|q| !(q.contains("manual=1") && q.contains("ext="))) + .unwrap_or(true) + { + return; + } let window_label = label.unwrap_or_else(|| VIEW_EXTENSION_WINDOW_LABEL.to_string()); if let Some(window) = app_handle.get_webview_window(&window_label) { diff --git a/src/components/Search/ViewExtension.tsx b/src/components/Search/ViewExtension.tsx index 530e9c9a..e86284fa 100644 --- a/src/components/Search/ViewExtension.tsx +++ b/src/components/Search/ViewExtension.tsx @@ -19,6 +19,7 @@ type ControlsProps = { showFocus?: boolean; forceResizable?: boolean; initialViewExtensionOpened?: ViewExtensionOpened | null; + isStandalone?: boolean; }; const ViewExtensionContent: React.FC = ({ @@ -26,9 +27,10 @@ const ViewExtensionContent: React.FC = ({ showFocus = true, forceResizable = false, initialViewExtensionOpened = null, + isStandalone = false, }) => { const storeView = useExtensionStore((state) => - state.viewExtensions.length > 0 ? state.viewExtensions[state.viewExtensions.length - 1] : undefined + initialViewExtensionOpened ? undefined : (state.viewExtensions.length > 0 ? state.viewExtensions[state.viewExtensions.length - 1] : undefined) ); const viewExtensionOpened = initialViewExtensionOpened ?? storeView; @@ -189,7 +191,7 @@ const ViewExtensionContent: React.FC = ({ iframeRef, focusIframe, setBaseSize, - } = useViewExtensionWindow({ forceResizable }); + } = useViewExtensionWindow({ forceResizable, viewExtension: viewExtensionOpened, isStandalone }); const [iframeReady, setIframeReady] = useState(false); @@ -214,11 +216,11 @@ const ViewExtensionContent: React.FC = ({ const ext = viewExtensionOpened!; const name = ext[0] || "extension"; const safe = name.toLowerCase().replace(/[^a-z0-9]+/g, "-"); - const label = `view_extension_${safe}_${Date.now()}`; + const label = `view_extension_${safe}`; const payload = btoa(encodeURIComponent(JSON.stringify(ext))); platformAdapter.invokeBackend("show_view_extension", { label, - query: `?ext=${payload}`, + query: `?manual=1&ext=${payload}`, width: baseWidth, height: baseHeight, }); @@ -245,10 +247,12 @@ const ViewExtensionContent: React.FC = ({ }; const ViewExtension: React.FC = (props) => { - const viewExtensionOpened = useExtensionStore( - (state) => state.viewExtensions.length > 0 ? state.viewExtensions[state.viewExtensions.length - 1] : undefined + const storeView = useExtensionStore( + (state) => props.initialViewExtensionOpened ? undefined : (state.viewExtensions.length > 0 ? state.viewExtensions[state.viewExtensions.length - 1] : undefined) ); + const viewExtensionOpened = props.initialViewExtensionOpened ?? storeView; + if (viewExtensionOpened == null) { return (
diff --git a/src/components/Search/ViewExtensionIframe.tsx b/src/components/Search/ViewExtensionIframe.tsx index 85318ac9..320f5279 100644 --- a/src/components/Search/ViewExtensionIframe.tsx +++ b/src/components/Search/ViewExtensionIframe.tsx @@ -23,15 +23,15 @@ const applyHideScrollbarToIframe = ( const style = doc.createElement("style"); style.id = HIDE_SCROLLBAR_STYLE_ID; style.textContent = ` -* { - scrollbar-width: none; - -ms-overflow-style: none; -} -*::-webkit-scrollbar { - width: 0px; - height: 0px; -} -`; + * { + scrollbar-width: none; + -ms-overflow-style: none; + } + *::-webkit-scrollbar { + width: 0px; + height: 0px; + } + `; const parent = doc.head ?? doc.documentElement; parent?.appendChild(style); diff --git a/src/hooks/useViewExtensionWindow.ts b/src/hooks/useViewExtensionWindow.ts index 356b3d10..3d1b63b8 100644 --- a/src/hooks/useViewExtensionWindow.ts +++ b/src/hooks/useViewExtensionWindow.ts @@ -17,10 +17,11 @@ export function useViewExtensionWindow(opts?: { forceResizable?: boolean; ignoreExplicitSize?: boolean; viewExtension?: ViewExtensionOpened | null; + isStandalone?: boolean; }) { const isTauri = useAppStore((state) => state.isTauri); const storeViewExtension = useExtensionStore((state) => - state.viewExtensions.length > 0 ? state.viewExtensions[state.viewExtensions.length - 1] : undefined + opts?.viewExtension ? undefined : (state.viewExtensions.length > 0 ? state.viewExtensions[state.viewExtensions.length - 1] : undefined) ); const viewExtensionOpened = opts?.viewExtension ?? storeViewExtension; @@ -126,7 +127,8 @@ export function useViewExtensionWindow(opts?: { // If we are in the standalone view extension window (not the preview in main window), // we need to show the window explicitly because it is created hidden to avoid flickering. - if (window.location.pathname.includes("/ui/view-extension")) { + // This ensures the window only becomes visible after the initial resize and setup are complete. + if (opts?.isStandalone) { await platformAdapter.showWindow(); } diff --git a/src/pages/main/index.tsx b/src/pages/main/index.tsx index 6a5ff37f..af8fc816 100644 --- a/src/pages/main/index.tsx +++ b/src/pages/main/index.tsx @@ -22,8 +22,6 @@ function MainApp() { // Events will be sent when users try to open a View extension via hotkey, // whose payload contains the needed information to load the View page. platformAdapter.listenEvent("open_view_extension", async ({ payload }) => { - await platformAdapter.showWindow(); - addViewExtension(payload); }); }, []); diff --git a/src/pages/view-extension/index.tsx b/src/pages/view-extension/index.tsx index 3e766348..d7b455e6 100644 --- a/src/pages/view-extension/index.tsx +++ b/src/pages/view-extension/index.tsx @@ -1,18 +1,26 @@ -import React from "react"; +import React, { useMemo } from "react"; import ViewExtension from "@/components/Search/ViewExtension"; import type { ViewExtensionOpened } from "@/stores/extensionStore"; const ViewExtensionPage: React.FC = () => { + // Parse the 'ext' query parameter from the URL. + // This parameter contains the base64-encoded JSON data of the extension to be opened. + // It allows the standalone window to initialize with the correct extension data passed from the main window. const params = new URLSearchParams(window.location.search); const encoded = params.get("ext"); - let initial: ViewExtensionOpened | null = null; - if (encoded) { - try { - const json = decodeURIComponent(atob(encoded)); - initial = JSON.parse(json) as ViewExtensionOpened; - } catch {} - } + const manual = params.get("manual") === "1"; + + const initial = useMemo(() => { + if (encoded) { + try { + const json = decodeURIComponent(atob(encoded)); + return JSON.parse(json) as ViewExtensionOpened; + } catch {} + } + return null; + }, [encoded]); + return (
{ showDetach={false} showFocus forceResizable + isStandalone={manual} + // Pass the parsed extension data as the initial state to ensure immediate rendering + // without relying on the store, which might be empty in this new window. initialViewExtensionOpened={initial} />