diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml index e3af39ad..81421f50 100644 --- a/.github/workflows/frontend-ci.yml +++ b/.github/workflows/frontend-ci.yml @@ -19,6 +19,9 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4 diff --git a/docs/content.en/docs/release-notes/_index.md b/docs/content.en/docs/release-notes/_index.md index 6fcb9e4a..d344cb72 100644 --- a/docs/content.en/docs/release-notes/_index.md +++ b/docs/content.en/docs/release-notes/_index.md @@ -29,6 +29,7 @@ fix: resolve pinned window shortcut not working #917 fix: WM ext does not work when operating focused win from another display #919 fix(Window Management): Next/Previous Desktop do not work #926 fix: fix page rapidly flickering issue #935 +fix(view extension): broken search bar UI when opening extensions via hotkey #938 ### ✈️ Improvements diff --git a/src-tauri/src/common/document.rs b/src-tauri/src/common/document.rs index a1a8aa84..77707194 100644 --- a/src-tauri/src/common/document.rs +++ b/src-tauri/src/common/document.rs @@ -2,6 +2,7 @@ use crate::extension::built_in::window_management::actions::Action; use crate::extension::{ExtensionPermission, ExtensionSettings, ViewExtensionUISettings}; use serde::{Deserialize, Serialize}; +use serde_json::Value as Json; use std::collections::HashMap; use tauri::{AppHandle, Emitter}; @@ -133,7 +134,7 @@ impl OnOpened { pub(crate) async fn open( tauri_app_handle: AppHandle, on_opened: OnOpened, - extra_args: Option>, + extra_args: Option>, ) -> Result<(), String> { use crate::util::open as homemade_tauri_shell_open; use std::process::Command; @@ -244,10 +245,17 @@ pub(crate) async fn open( use serde_json::Value as Json; use serde_json::to_value; - let page_and_permission: [Json; 3] = [ + let mut extra_args = + extra_args.expect("extra_args is needed to open() a view extension"); + let document = extra_args.remove("document").expect( + "extra argument [document] should be provided to open a view extension", + ); + + let page_and_permission: [Json; 4] = [ Json::String(page), to_value(permission).unwrap(), to_value(ui).unwrap(), + document, ]; tauri_app_handle .emit("open_view_extension", page_and_permission) diff --git a/src-tauri/src/extension/mod.rs b/src-tauri/src/extension/mod.rs index 938030b0..f5ee7415 100644 --- a/src-tauri/src/extension/mod.rs +++ b/src-tauri/src/extension/mod.rs @@ -430,7 +430,7 @@ impl QuicklinkLink { /// if any. pub(crate) fn concatenate_url( &self, - user_supplied_args: &Option>, + user_supplied_args: &Option>, ) -> String { let mut out = String::new(); for component in self.components.iter() { @@ -442,20 +442,23 @@ impl QuicklinkLink { argument_name, default, } => { - let opt_argument_value = { + let opt_argument_value: Option<&str> = { let user_supplied_arg = user_supplied_args .as_ref() .and_then(|map| map.get(argument_name.as_str())); if user_supplied_arg.is_some() { - user_supplied_arg + user_supplied_arg.map(|json| { + json.as_str() + .expect("quicklink should provide string arguments") + }) } else { - default.as_ref() + default.as_deref() } }; let argument_value_str = match opt_argument_value { - Some(str) => str.as_str(), + Some(str) => str, // None => an empty string None => "", }; @@ -1763,7 +1766,7 @@ mod tests { ], }; let mut user_args = HashMap::new(); - user_args.insert("other_param".to_string(), "value".to_string()); + user_args.insert("other_param".to_string(), Json::String("value".to_string())); let result = link.concatenate_url(&Some(user_args)); assert_eq!(result, "https://www.google.com/search?q="); } @@ -1800,7 +1803,7 @@ mod tests { ], }; let mut user_args = HashMap::new(); - user_args.insert("other_param".to_string(), "value".to_string()); + user_args.insert("other_param".to_string(), Json::String("value".to_string())); let result = link.concatenate_url(&Some(user_args)); assert_eq!(result, "https://www.google.com/search?q=rust"); } @@ -1823,7 +1826,7 @@ mod tests { ], }; let mut user_args = HashMap::new(); - user_args.insert("query".to_string(), "python".to_string()); + user_args.insert("query".to_string(), Json::String("python".to_string())); let result = link.concatenate_url(&Some(user_args)); assert_eq!(result, "https://www.google.com/search?q=python"); } diff --git a/src-tauri/src/extension/third_party/mod.rs b/src-tauri/src/extension/third_party/mod.rs index c2c33b53..ff557d05 100644 --- a/src-tauri/src/extension/third_party/mod.rs +++ b/src-tauri/src/extension/third_party/mod.rs @@ -23,6 +23,7 @@ use async_trait::async_trait; use borrowme::ToOwned; use check::general_check; use function_name::named; +use std::collections::HashMap; use std::io::ErrorKind; use std::path::Path; use std::path::PathBuf; @@ -524,6 +525,24 @@ impl ThirdPartyExtensionsSearchSource { let on_opened = extension.on_opened().unwrap_or_else(|| panic!( "setting hotkey for an extension that cannot be opened, extension ID [{:?}], extension type [{:?}]", bundle_id, extension.r#type, )); + let url = on_opened.url(); + let extension_type_string = extension.r#type.to_string(); + let document = Document { + id: extension.id.clone(), + title: Some(extension.name.clone()), + icon: Some(extension.icon.clone()), + on_opened: Some(on_opened.clone()), + url: Some(url), + category: Some(extension_type_string.clone()), + source: Some(DataSourceReference { + id: Some(extension_type_string.clone()), + name: Some(extension_type_string.clone()), + icon: None, + r#type: Some(extension_type_string), + }), + + ..Default::default() + }; let bundle_id_owned = bundle_id.to_owned(); tauri_app_handle @@ -533,9 +552,16 @@ impl ThirdPartyExtensionsSearchSource { let bundle_id_clone = bundle_id_owned.clone(); let app_handle_clone = tauri_app_handle.clone(); + let document_clone = document.clone(); if event.state() == ShortcutState::Pressed { async_runtime::spawn(async move { - let result = open(app_handle_clone, on_opened_clone, None).await; + let mut args = HashMap::new(); + args.insert( + String::from("document"), + serde_json::to_value(&document_clone).unwrap(), + ); + + let result = open(app_handle_clone, on_opened_clone, Some(args)).await; if let Err(msg) = result { log::warn!( "failed to open extension [{:?}], error [{}]", diff --git a/src/components/Search/AssistantManager.tsx b/src/components/Search/AssistantManager.tsx index b9c50ea7..fb16da75 100644 --- a/src/components/Search/AssistantManager.tsx +++ b/src/components/Search/AssistantManager.tsx @@ -183,18 +183,17 @@ export function useAssistantManager({ const onOpened = selectedSearchContent?.on_opened; if (onOpened?.Extension?.ty?.View) { - const { setViewExtensionOpened, setViewExtensionData } = - useSearchStore.getState(); + const { setViewExtensionOpened } = useSearchStore.getState(); const viewData = onOpened.Extension.ty.View; const extensionPermission = onOpened.Extension.permission; clearSearchValue(); - setViewExtensionOpened([ + return setViewExtensionOpened([ viewData.page, extensionPermission, viewData.ui, + selectedSearchContent as any, ]); - return setViewExtensionData(selectedSearchContent as any); } } diff --git a/src/components/Search/SearchIcons.tsx b/src/components/Search/SearchIcons.tsx index 86b0fcaf..2ee68e50 100644 --- a/src/components/Search/SearchIcons.tsx +++ b/src/components/Search/SearchIcons.tsx @@ -75,7 +75,6 @@ export default function SearchIcons({ visibleExtensionDetail, selectedExtension, viewExtensionOpened, - viewExtensionData, } = useSearchStore(); if (isChatMode) { @@ -106,8 +105,8 @@ export default function SearchIcons({ return ; } - if (viewExtensionOpened && viewExtensionData) { - const { title, icon } = viewExtensionData; + if (viewExtensionOpened) { + const { title, icon } = viewExtensionOpened[3]; const iconPath = icon ? platformAdapter.convertFileSrc(icon) : void 0; diff --git a/src/components/Search/ViewExtension.tsx b/src/components/Search/ViewExtension.tsx index 561ab18e..87f5f948 100644 --- a/src/components/Search/ViewExtension.tsx +++ b/src/components/Search/ViewExtension.tsx @@ -7,12 +7,14 @@ import { FileSystemAccess, } from "../Settings/Extensions"; import platformAdapter from "@/utils/platformAdapter"; +import { useShortcutsStore } from "@/stores/shortcutsStore"; const ViewExtension: React.FC = () => { const { viewExtensionOpened } = useSearchStore(); const [page, setPage] = useState(""); // Complete list of the backend APIs, grouped by their category. const [apis, setApis] = useState | null>(null); + const { setModifierKeyPressed } = useShortcutsStore(); if (viewExtensionOpened == null) { // When this view gets loaded, this state should not be NULL. @@ -29,12 +31,11 @@ const ViewExtension: React.FC = () => { const page = viewExtensionOpened[0]; // Only convert to file source if it's a local file path, not a URL - if (page.startsWith('http://') || page.startsWith('https://')) { + if (page.startsWith("http://") || page.startsWith("https://")) { setPage(page); } else { setPage(platformAdapter.convertFileSrc(page)); } - }; setupFileUrl(); @@ -42,6 +43,8 @@ const ViewExtension: React.FC = () => { // invoke `apis()` and set the state useEffect(() => { + setModifierKeyPressed(false); + const fetchApis = async () => { try { const availableApis = (await platformAdapter.invokeBackend( diff --git a/src/stores/extensionsStore.ts b/src/stores/extensionsStore.ts index 71683b25..8040667a 100644 --- a/src/stores/extensionsStore.ts +++ b/src/stores/extensionsStore.ts @@ -1,7 +1,8 @@ -import { ExtensionId } from "@/components/Settings/Extensions"; import { create } from "zustand"; import { persist, subscribeWithSelector } from "zustand/middleware"; +import { ExtensionId } from "@/components/Settings/Extensions"; + export type IExtensionsStore = { quickAiAccessServer?: any; setQuickAiAccessServer: (quickAiAccessServer?: any) => void; diff --git a/src/stores/searchStore.ts b/src/stores/searchStore.ts index d9c60031..8d98a25d 100644 --- a/src/stores/searchStore.ts +++ b/src/stores/searchStore.ts @@ -7,9 +7,12 @@ import { SearchDocument } from "@/types/search"; import { create } from "zustand"; import { persist } from "zustand/middleware"; -export type ViewExtensionOpened = - | [string, ExtensionPermission | null, ViewExtensionUISettings | null] - | null; +export type ViewExtensionOpened = [ + string, + ExtensionPermission | null, + ViewExtensionUISettings | null, + SearchDocument +]; export type ISearchStore = { sourceData: any; @@ -58,11 +61,8 @@ export type ISearchStore = { // The first array element is the path to the page that we should load // The second element is the permission that this extension requires. // The third argument is the UI Settings - viewExtensionOpened: ViewExtensionOpened; - setViewExtensionOpened: (showViewExtension: ViewExtensionOpened) => void; - - viewExtensionData?: SearchDocument; - setViewExtensionData: (viewExtensionData?: SearchDocument) => void; + viewExtensionOpened?: ViewExtensionOpened; + setViewExtensionOpened: (showViewExtension?: ViewExtensionOpened) => void; }; export const useSearchStore = create()( @@ -128,13 +128,9 @@ export const useSearchStore = create()( setVisibleExtensionDetail: (visibleExtensionDetail) => { return set({ visibleExtensionDetail }); }, - viewExtensionOpened: null, setViewExtensionOpened: (viewExtensionOpened) => { return set({ viewExtensionOpened }); }, - setViewExtensionData(viewExtensionData) { - return set({ viewExtensionData }); - }, }), { name: "search-store", diff --git a/src/utils/index.ts b/src/utils/index.ts index 343d4410..ff23fdf4 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -220,13 +220,11 @@ export const navigateBack = () => { visibleExtensionStore, visibleExtensionDetail, viewExtensionOpened, - viewExtensionData, setGoAskAi, setVisibleExtensionDetail, setVisibleExtensionStore, setSourceData, setViewExtensionOpened, - setViewExtensionData, } = useSearchStore.getState(); if (goAskAi) { @@ -241,10 +239,8 @@ export const navigateBack = () => { return setVisibleExtensionStore(false); } - if (viewExtensionOpened || viewExtensionData) { - setViewExtensionData(void 0); - - return setViewExtensionOpened(null); + if (viewExtensionOpened) { + return setViewExtensionOpened(void 0); } setSourceData(void 0);