fix(view extension): broken search bar UI when opening extensions via hotkey (#938)

* fix: open view extension via hotkey

* refactor: update

* refactor: update

* chore: check error

* chore: ci update

* release notes

---------

Co-authored-by: ayang <473033518@qq.com>
Co-authored-by: rain9 <15911122312@163.com>
This commit is contained in:
SteveLauC
2025-10-22 10:08:00 +08:00
committed by GitHub
parent 731cfc5bd7
commit e029ddf2ba
11 changed files with 74 additions and 39 deletions

View File

@@ -19,6 +19,9 @@ jobs:
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
fetch-depth: 0
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v4

View File

@@ -32,6 +32,7 @@ fix: resolve pinned window shortcut not working #917
fix: WM ext does not work when operating focused win from another display #919 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(Window Management): Next/Previous Desktop do not work #926
fix: fix page rapidly flickering issue #935 fix: fix page rapidly flickering issue #935
fix(view extension): broken search bar UI when opening extensions via hotkey #938
### ✈️ Improvements ### ✈️ Improvements

View File

@@ -2,6 +2,7 @@
use crate::extension::built_in::window_management::actions::Action; use crate::extension::built_in::window_management::actions::Action;
use crate::extension::{ExtensionPermission, ExtensionSettings, ViewExtensionUISettings}; use crate::extension::{ExtensionPermission, ExtensionSettings, ViewExtensionUISettings};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value as Json;
use std::collections::HashMap; use std::collections::HashMap;
use tauri::{AppHandle, Emitter}; use tauri::{AppHandle, Emitter};
@@ -133,7 +134,7 @@ impl OnOpened {
pub(crate) async fn open( pub(crate) async fn open(
tauri_app_handle: AppHandle, tauri_app_handle: AppHandle,
on_opened: OnOpened, on_opened: OnOpened,
extra_args: Option<HashMap<String, String>>, extra_args: Option<HashMap<String, Json>>,
) -> Result<(), String> { ) -> Result<(), String> {
use crate::util::open as homemade_tauri_shell_open; use crate::util::open as homemade_tauri_shell_open;
use std::process::Command; use std::process::Command;
@@ -244,10 +245,17 @@ pub(crate) async fn open(
use serde_json::Value as Json; use serde_json::Value as Json;
use serde_json::to_value; 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), Json::String(page),
to_value(permission).unwrap(), to_value(permission).unwrap(),
to_value(ui).unwrap(), to_value(ui).unwrap(),
document,
]; ];
tauri_app_handle tauri_app_handle
.emit("open_view_extension", page_and_permission) .emit("open_view_extension", page_and_permission)

View File

@@ -430,7 +430,7 @@ impl QuicklinkLink {
/// if any. /// if any.
pub(crate) fn concatenate_url( pub(crate) fn concatenate_url(
&self, &self,
user_supplied_args: &Option<HashMap<String, String>>, user_supplied_args: &Option<HashMap<String, Json>>,
) -> String { ) -> String {
let mut out = String::new(); let mut out = String::new();
for component in self.components.iter() { for component in self.components.iter() {
@@ -442,20 +442,23 @@ impl QuicklinkLink {
argument_name, argument_name,
default, default,
} => { } => {
let opt_argument_value = { let opt_argument_value: Option<&str> = {
let user_supplied_arg = user_supplied_args let user_supplied_arg = user_supplied_args
.as_ref() .as_ref()
.and_then(|map| map.get(argument_name.as_str())); .and_then(|map| map.get(argument_name.as_str()));
if user_supplied_arg.is_some() { if user_supplied_arg.is_some() {
user_supplied_arg user_supplied_arg.map(|json| {
json.as_str()
.expect("quicklink should provide string arguments")
})
} else { } else {
default.as_ref() default.as_deref()
} }
}; };
let argument_value_str = match opt_argument_value { let argument_value_str = match opt_argument_value {
Some(str) => str.as_str(), Some(str) => str,
// None => an empty string // None => an empty string
None => "", None => "",
}; };
@@ -1763,7 +1766,7 @@ mod tests {
], ],
}; };
let mut user_args = HashMap::new(); 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)); let result = link.concatenate_url(&Some(user_args));
assert_eq!(result, "https://www.google.com/search?q="); assert_eq!(result, "https://www.google.com/search?q=");
} }
@@ -1800,7 +1803,7 @@ mod tests {
], ],
}; };
let mut user_args = HashMap::new(); 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)); let result = link.concatenate_url(&Some(user_args));
assert_eq!(result, "https://www.google.com/search?q=rust"); assert_eq!(result, "https://www.google.com/search?q=rust");
} }
@@ -1823,7 +1826,7 @@ mod tests {
], ],
}; };
let mut user_args = HashMap::new(); 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)); let result = link.concatenate_url(&Some(user_args));
assert_eq!(result, "https://www.google.com/search?q=python"); assert_eq!(result, "https://www.google.com/search?q=python");
} }

View File

@@ -23,6 +23,7 @@ use async_trait::async_trait;
use borrowme::ToOwned; use borrowme::ToOwned;
use check::general_check; use check::general_check;
use function_name::named; use function_name::named;
use std::collections::HashMap;
use std::io::ErrorKind; use std::io::ErrorKind;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
@@ -524,6 +525,24 @@ impl ThirdPartyExtensionsSearchSource {
let on_opened = extension.on_opened().unwrap_or_else(|| panic!( 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, "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(); let bundle_id_owned = bundle_id.to_owned();
tauri_app_handle tauri_app_handle
@@ -533,9 +552,16 @@ impl ThirdPartyExtensionsSearchSource {
let bundle_id_clone = bundle_id_owned.clone(); let bundle_id_clone = bundle_id_owned.clone();
let app_handle_clone = tauri_app_handle.clone(); let app_handle_clone = tauri_app_handle.clone();
let document_clone = document.clone();
if event.state() == ShortcutState::Pressed { if event.state() == ShortcutState::Pressed {
async_runtime::spawn(async move { 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 { if let Err(msg) = result {
log::warn!( log::warn!(
"failed to open extension [{:?}], error [{}]", "failed to open extension [{:?}], error [{}]",

View File

@@ -185,18 +185,17 @@ export function useAssistantManager({
const onOpened = selectedSearchContent?.on_opened; const onOpened = selectedSearchContent?.on_opened;
if (onOpened?.Extension?.ty?.View) { if (onOpened?.Extension?.ty?.View) {
const { setViewExtensionOpened, setViewExtensionData } = const { setViewExtensionOpened } = useSearchStore.getState();
useSearchStore.getState();
const viewData = onOpened.Extension.ty.View; const viewData = onOpened.Extension.ty.View;
const extensionPermission = onOpened.Extension.permission; const extensionPermission = onOpened.Extension.permission;
clearSearchValue(); clearSearchValue();
setViewExtensionOpened([ return setViewExtensionOpened([
viewData.page, viewData.page,
extensionPermission, extensionPermission,
viewData.ui, viewData.ui,
selectedSearchContent as any,
]); ]);
return setViewExtensionData(selectedSearchContent as any);
} }
} }

View File

@@ -75,7 +75,6 @@ export default function SearchIcons({
visibleExtensionDetail, visibleExtensionDetail,
selectedExtension, selectedExtension,
viewExtensionOpened, viewExtensionOpened,
viewExtensionData,
} = useSearchStore(); } = useSearchStore();
if (isChatMode) { if (isChatMode) {
@@ -106,8 +105,8 @@ export default function SearchIcons({
return <MultilevelWrapper title={name} icon={icon} />; return <MultilevelWrapper title={name} icon={icon} />;
} }
if (viewExtensionOpened && viewExtensionData) { if (viewExtensionOpened) {
const { title, icon } = viewExtensionData; const { title, icon } = viewExtensionOpened[3];
const iconPath = icon ? platformAdapter.convertFileSrc(icon) : void 0; const iconPath = icon ? platformAdapter.convertFileSrc(icon) : void 0;

View File

@@ -7,12 +7,14 @@ import {
FileSystemAccess, FileSystemAccess,
} from "../Settings/Extensions"; } from "../Settings/Extensions";
import platformAdapter from "@/utils/platformAdapter"; import platformAdapter from "@/utils/platformAdapter";
import { useShortcutsStore } from "@/stores/shortcutsStore";
const ViewExtension: React.FC = () => { const ViewExtension: React.FC = () => {
const { viewExtensionOpened } = useSearchStore(); const { viewExtensionOpened } = useSearchStore();
const [page, setPage] = useState<string>(""); const [page, setPage] = useState<string>("");
// Complete list of the backend APIs, grouped by their category. // Complete list of the backend APIs, grouped by their category.
const [apis, setApis] = useState<Map<string, string[]> | null>(null); const [apis, setApis] = useState<Map<string, string[]> | null>(null);
const { setModifierKeyPressed } = useShortcutsStore();
if (viewExtensionOpened == null) { if (viewExtensionOpened == null) {
// When this view gets loaded, this state should not be NULL. // When this view gets loaded, this state should not be NULL.
@@ -29,12 +31,11 @@ const ViewExtension: React.FC = () => {
const page = viewExtensionOpened[0]; const page = viewExtensionOpened[0];
// Only convert to file source if it's a local file path, not a URL // 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); setPage(page);
} else { } else {
setPage(platformAdapter.convertFileSrc(page)); setPage(platformAdapter.convertFileSrc(page));
} }
}; };
setupFileUrl(); setupFileUrl();
@@ -42,6 +43,8 @@ const ViewExtension: React.FC = () => {
// invoke `apis()` and set the state // invoke `apis()` and set the state
useEffect(() => { useEffect(() => {
setModifierKeyPressed(false);
const fetchApis = async () => { const fetchApis = async () => {
try { try {
const availableApis = (await platformAdapter.invokeBackend( const availableApis = (await platformAdapter.invokeBackend(

View File

@@ -1,7 +1,8 @@
import { ExtensionId } from "@/components/Settings/Extensions";
import { create } from "zustand"; import { create } from "zustand";
import { persist, subscribeWithSelector } from "zustand/middleware"; import { persist, subscribeWithSelector } from "zustand/middleware";
import { ExtensionId } from "@/components/Settings/Extensions";
export type IExtensionsStore = { export type IExtensionsStore = {
quickAiAccessServer?: any; quickAiAccessServer?: any;
setQuickAiAccessServer: (quickAiAccessServer?: any) => void; setQuickAiAccessServer: (quickAiAccessServer?: any) => void;

View File

@@ -7,9 +7,12 @@ import { SearchDocument } from "@/types/search";
import { create } from "zustand"; import { create } from "zustand";
import { persist } from "zustand/middleware"; import { persist } from "zustand/middleware";
export type ViewExtensionOpened = export type ViewExtensionOpened = [
| [string, ExtensionPermission | null, ViewExtensionUISettings | null] string,
| null; ExtensionPermission | null,
ViewExtensionUISettings | null,
SearchDocument
];
export type ISearchStore = { export type ISearchStore = {
sourceData: any; sourceData: any;
@@ -58,11 +61,8 @@ export type ISearchStore = {
// The first array element is the path to the page that we should load // 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 second element is the permission that this extension requires.
// The third argument is the UI Settings // The third argument is the UI Settings
viewExtensionOpened: ViewExtensionOpened; viewExtensionOpened?: ViewExtensionOpened;
setViewExtensionOpened: (showViewExtension: ViewExtensionOpened) => void; setViewExtensionOpened: (showViewExtension?: ViewExtensionOpened) => void;
viewExtensionData?: SearchDocument;
setViewExtensionData: (viewExtensionData?: SearchDocument) => void;
}; };
export const useSearchStore = create<ISearchStore>()( export const useSearchStore = create<ISearchStore>()(
@@ -128,13 +128,9 @@ export const useSearchStore = create<ISearchStore>()(
setVisibleExtensionDetail: (visibleExtensionDetail) => { setVisibleExtensionDetail: (visibleExtensionDetail) => {
return set({ visibleExtensionDetail }); return set({ visibleExtensionDetail });
}, },
viewExtensionOpened: null,
setViewExtensionOpened: (viewExtensionOpened) => { setViewExtensionOpened: (viewExtensionOpened) => {
return set({ viewExtensionOpened }); return set({ viewExtensionOpened });
}, },
setViewExtensionData(viewExtensionData) {
return set({ viewExtensionData });
},
}), }),
{ {
name: "search-store", name: "search-store",

View File

@@ -240,13 +240,11 @@ export const navigateBack = () => {
visibleExtensionStore, visibleExtensionStore,
visibleExtensionDetail, visibleExtensionDetail,
viewExtensionOpened, viewExtensionOpened,
viewExtensionData,
setGoAskAi, setGoAskAi,
setVisibleExtensionDetail, setVisibleExtensionDetail,
setVisibleExtensionStore, setVisibleExtensionStore,
setSourceData, setSourceData,
setViewExtensionOpened, setViewExtensionOpened,
setViewExtensionData,
} = useSearchStore.getState(); } = useSearchStore.getState();
if (goAskAi) { if (goAskAi) {
@@ -261,10 +259,8 @@ export const navigateBack = () => {
return setVisibleExtensionStore(false); return setVisibleExtensionStore(false);
} }
if (viewExtensionOpened || viewExtensionData) { if (viewExtensionOpened) {
setViewExtensionData(void 0); return setViewExtensionOpened(void 0);
return setViewExtensionOpened(null);
} }
setSourceData(void 0); setSourceData(void 0);