Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/frontend-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/content.en/docs/release-notes/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 10 additions & 2 deletions src-tauri/src/common/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down
19 changes: 11 additions & 8 deletions src-tauri/src/extension/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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 => "",
};
Expand Down Expand Up @@ -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=");
}
Expand Down Expand Up @@ -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");
}
Expand All @@ -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");
}
Expand Down
28 changes: 27 additions & 1 deletion src-tauri/src/extension/third_party/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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 [{}]",
Expand Down
7 changes: 3 additions & 4 deletions src/components/Search/AssistantManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
5 changes: 2 additions & 3 deletions src/components/Search/SearchIcons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ export default function SearchIcons({
visibleExtensionDetail,
selectedExtension,
viewExtensionOpened,
viewExtensionData,
} = useSearchStore();

if (isChatMode) {
Expand Down Expand Up @@ -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;

Expand Down
7 changes: 5 additions & 2 deletions src/components/Search/ViewExtension.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -29,19 +31,20 @@ 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();
}, [viewExtensionOpened]);

// invoke `apis()` and set the state
useEffect(() => {
setModifierKeyPressed(false);

const fetchApis = async () => {
try {
const availableApis = (await platformAdapter.invokeBackend(
Expand Down
3 changes: 2 additions & 1 deletion src/stores/extensionsStore.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
20 changes: 8 additions & 12 deletions src/stores/searchStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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>()(
Expand Down Expand Up @@ -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",
Expand Down
8 changes: 2 additions & 6 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,13 +220,11 @@ export const navigateBack = () => {
visibleExtensionStore,
visibleExtensionDetail,
viewExtensionOpened,
viewExtensionData,
setGoAskAi,
setVisibleExtensionDetail,
setVisibleExtensionStore,
setSourceData,
setViewExtensionOpened,
setViewExtensionData,
} = useSearchStore.getState();

if (goAskAi) {
Expand All @@ -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);
Expand Down