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:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
fetch-depth: 0
- name: Setup Node.js
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(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

View File

@@ -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<HashMap<String, String>>,
extra_args: Option<HashMap<String, Json>>,
) -> 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)

View File

@@ -430,7 +430,7 @@ impl QuicklinkLink {
/// if any.
pub(crate) fn concatenate_url(
&self,
user_supplied_args: &Option<HashMap<String, String>>,
user_supplied_args: &Option<HashMap<String, Json>>,
) -> 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");
}

View File

@@ -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 [{}]",

View File

@@ -185,18 +185,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);
}
}

View File

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

View File

@@ -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<string>("");
// Complete list of the backend APIs, grouped by their category.
const [apis, setApis] = useState<Map<string, string[]> | 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(

View File

@@ -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;

View File

@@ -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<ISearchStore>()(
@@ -128,13 +128,9 @@ export const useSearchStore = create<ISearchStore>()(
setVisibleExtensionDetail: (visibleExtensionDetail) => {
return set({ visibleExtensionDetail });
},
viewExtensionOpened: null,
setViewExtensionOpened: (viewExtensionOpened) => {
return set({ viewExtensionOpened });
},
setViewExtensionData(viewExtensionData) {
return set({ viewExtensionData });
},
}),
{
name: "search-store",

View File

@@ -240,13 +240,11 @@ export const navigateBack = () => {
visibleExtensionStore,
visibleExtensionDetail,
viewExtensionOpened,
viewExtensionData,
setGoAskAi,
setVisibleExtensionDetail,
setVisibleExtensionStore,
setSourceData,
setViewExtensionOpened,
setViewExtensionData,
} = useSearchStore.getState();
if (goAskAi) {
@@ -261,10 +259,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);