-
Notifications
You must be signed in to change notification settings - Fork 2
feat: add search online function #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -242,8 +242,7 @@ fn index_applications() -> Vec<FileItem> { | |
| .into_iter() | ||
| .filter_map(|e| e.ok()) | ||
| { | ||
| let path = entry.path(); | ||
| if is_app_file(&path.to_path_buf()) { | ||
| let path = entry.path(); if is_app_file(&path.to_path_buf()) { | ||
| if let (Ok(metadata), Some(name)) = (path.metadata(), path.file_name().and_then(|n| n.to_str())) { | ||
| let modified = metadata | ||
| .modified() | ||
|
|
@@ -497,6 +496,12 @@ fn refresh_file_index( | |
| Ok(()) | ||
| } | ||
|
|
||
| #[tauri::command] | ||
| fn hide_window(app: tauri::AppHandle) { | ||
| if let Some(window) = app.get_webview_window("main") { | ||
| let _ = window.hide(); | ||
| } | ||
| } | ||
| fn start_clipboard_monitor(app_handle: tauri::AppHandle, db: Arc<Mutex<ClipboardDatabase>>) { | ||
| std::thread::spawn(move || { | ||
| let mut last_content = String::new(); | ||
|
|
@@ -545,8 +550,31 @@ fn start_clipboard_monitor(app_handle: tauri::AppHandle, db: Arc<Mutex<Clipboard | |
| } | ||
|
|
||
| pub fn run() { | ||
| // --- FIX 1: Define the handler logic --- | ||
| // This handler will be attached to the main builder. | ||
| // It must be able to check *which* shortcut was pressed. | ||
| let shortcut_handler = ShortcutBuilder::new() | ||
| .with_handler(move |app, scut, event| { | ||
| // Re-create the shortcut struct to compare its ID | ||
| let shortcut = Shortcut::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::Space); | ||
|
|
||
| if scut.id() == shortcut.id() && event.state() == ShortcutState::Pressed { | ||
| let win = app.get_webview_window("main").expect("window not found"); | ||
| if win.is_visible().unwrap_or(false) { | ||
| let _ = win.hide(); | ||
| } else { | ||
| let _ = win.show(); | ||
| let _ = win.set_focus(); | ||
| } | ||
| } | ||
| }) | ||
| .build(); | ||
|
|
||
|
Comment on lines
+553
to
+572
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Shortcut handler logic is sound; minor hardening optional. Comparison via id + Pressed state is correct. Consider logging errors from show()/hide()/set_focus() for diagnosability. 🤖 Prompt for AI Agents |
||
| tauri::Builder::default() | ||
| .plugin(tauri_plugin_opener::init()) | ||
| .plugin(tauri_plugin_clipboard_manager::init()) | ||
| // --- Add the handler plugin --- | ||
| .plugin(shortcut_handler) | ||
| .setup(|app| { | ||
| // Initialize clipboard database | ||
| let db_path = get_db_path(&app.handle()); | ||
|
|
@@ -573,27 +601,12 @@ pub fn run() { | |
|
|
||
| #[cfg(desktop)] | ||
| { | ||
| // --- FIX 2: Register the shortcut --- | ||
| // The v2 register() function does NOT take a closure, | ||
| // as the handler is already registered above. | ||
| let shortcut = | ||
| Shortcut::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::Space); | ||
| let handle = app.handle(); | ||
|
|
||
| handle.plugin( | ||
| ShortcutBuilder::new() | ||
| .with_handler(move |app, scut, event| { | ||
| if scut.id() == shortcut.id() && event.state() == ShortcutState::Pressed | ||
| { | ||
| let win = app.get_webview_window("main").expect("window not found"); | ||
| if win.is_visible().unwrap_or(false) { | ||
| let _ = win.hide(); | ||
| } else { | ||
| let _ = win.show(); | ||
| let _ = win.set_focus(); | ||
| } | ||
| } | ||
| }) | ||
| .build(), | ||
| )?; | ||
|
|
||
|
|
||
| app.global_shortcut().register(shortcut)?; | ||
| } | ||
| Ok(()) | ||
|
|
@@ -609,6 +622,7 @@ pub fn run() { | |
| get_recent_files, | ||
| open_file, | ||
| refresh_file_index, | ||
| hide_window, | ||
| ]) | ||
| .run(tauri::generate_context!()) | ||
| .expect("error while running tauri"); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,6 @@ | ||
| import { useKeyboardNavigation } from "../hooks/useKeyboardNavigation"; | ||
| import { openUrl } from '@tauri-apps/plugin-opener'; | ||
| import { invoke } from '@tauri-apps/api/core'; | ||
| import Fuse from "fuse.js"; | ||
|
|
||
| const OPTIONS = [ | ||
|
|
@@ -14,29 +16,54 @@ export default function HomeOptions({ query, onSelect, clearQuery }) { | |
| ? fuse.search(query).map((result) => result.item) | ||
| : OPTIONS; | ||
|
|
||
| const { getItemProps } = useKeyboardNavigation(filtered, (item) => { | ||
| onSelect(item.page); | ||
| clearQuery(); | ||
| const handleSearch = async (searchQuery) => { | ||
| if (!searchQuery.trim()) return; | ||
|
|
||
| try { | ||
| // Construct search URL (Google search) | ||
| const searchUrl = `https://www.google.com/search?q=${encodeURIComponent(searchQuery)}`; | ||
|
|
||
| // Open URL in browser | ||
| await openUrl(searchUrl); | ||
|
|
||
| // Hide the PathFinder window | ||
| await invoke('hide_window'); | ||
|
|
||
| // Clear query | ||
| clearQuery(); | ||
| } catch (error) { | ||
| console.error("Failed to open browser:", error); | ||
| } | ||
| }; | ||
|
Comment on lines
+19
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider extracting the duplicate handleSearch logic. The implementation is correct and handles the search flow properly (URL construction, opening browser, hiding window, clearing query). However, this logic is nearly identical to the Consider extracting this shared logic into a custom hook or utility function: // src/utils/webSearch.js or src/hooks/useWebSearch.js
export async function performWebSearch(searchQuery, clearQuery) {
if (!searchQuery.trim()) return;
try {
const searchUrl = `https://www.google.com/search?q=${encodeURIComponent(searchQuery)}`;
await openUrl(searchUrl);
await invoke('hide_window');
clearQuery();
} catch (error) {
console.error("Failed to open browser:", error);
}
}Then use it in both components: const handleSearch = async (searchQuery) => {
await performWebSearch(searchQuery, clearQuery);
};🤖 Prompt for AI Agents |
||
|
|
||
| // If no matches, show web search option | ||
| const itemsToShow = filtered.length > 0 | ||
| ? filtered | ||
| : [{ title: `Search online for "${query}"`, icon: "🌐", isWebSearch: true }]; | ||
|
|
||
| const { getItemProps } = useKeyboardNavigation(itemsToShow, (item) => { | ||
| if (item.isWebSearch) { | ||
| // Trigger web search | ||
| handleSearch(query); | ||
| } else { | ||
| // Navigate to page | ||
| onSelect(item.page); | ||
| clearQuery(); | ||
| } | ||
| }); | ||
|
|
||
| return ( | ||
| <div className="option-list"> | ||
| {filtered.length ? ( | ||
| filtered.map((opt, idx) => ( | ||
| <div | ||
| {...getItemProps(idx)} | ||
| className={`option-item ${getItemProps(idx).className}`} | ||
| key={idx} | ||
| > | ||
| <span className="icon">{opt.icon}</span> | ||
| <span>{opt.title}</span> | ||
| </div> | ||
| )) | ||
| ) : ( | ||
| <div className="option-item selected"> | ||
| <span className="icon">🌐</span> | ||
| <span>Search online for "{query}"</span> | ||
| {itemsToShow.map((opt, idx) => ( | ||
| <div | ||
| {...getItemProps(idx)} | ||
| className={`option-item ${getItemProps(idx).className}`} | ||
| key={idx} | ||
|
Comment on lines
+58
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix className duplication and inefficient getItemProps calls. There are two issues here:
Apply this diff to fix both issues: - {itemsToShow.map((opt, idx) => (
- <div
- {...getItemProps(idx)}
- className={`option-item ${getItemProps(idx).className}`}
- key={idx}
- >
+ {itemsToShow.map((opt, idx) => {
+ const itemProps = getItemProps(idx);
+ return (
+ <div
+ {...itemProps}
+ key={idx}
+ >
<span className="icon">{opt.icon}</span>
<span>{opt.title}</span>
</div>
- ))}
+ )})}The
🤖 Prompt for AI Agents |
||
| > | ||
| <span className="icon">{opt.icon}</span> | ||
| <span>{opt.title}</span> | ||
| </div> | ||
| )} | ||
| ))} | ||
| </div> | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,19 +1,44 @@ | ||||||||||||||
| import { useEffect } from "react"; | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove unused import. useEffect isn’t used anymore. -import { useEffect } from "react";
+// (unused) import removed
🤖 Prompt for AI Agents |
||||||||||||||
| // 1. Import the correct named function 'openUrl' | ||||||||||||||
| import { openUrl } from '@tauri-apps/plugin-opener'; | ||||||||||||||
| import { invoke } from '@tauri-apps/api/core'; | ||||||||||||||
| import { useKeyboardNavigation } from "../hooks/useKeyboardNavigation"; | ||||||||||||||
|
|
||||||||||||||
| export default function OnlineSearchPage({ query }) { | ||||||||||||||
| const { getItemProps } = useKeyboardNavigation([query], (item, idx) => { | ||||||||||||||
| console.log("Selected:", item); | ||||||||||||||
| const { getItemProps } = useKeyboardNavigation([query], async (item, idx) => { | ||||||||||||||
| await handleSearch(query); | ||||||||||||||
| }); | ||||||||||||||
|
Comment on lines
+8
to
10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid stale closure: use the item passed by the hook. Use item to ensure the searched query matches the selected entry. - const { getItemProps } = useKeyboardNavigation([query], async (item, idx) => {
- await handleSearch(query);
- });
+ const { getItemProps } = useKeyboardNavigation([query], async (item) => {
+ await handleSearch(item);
+ });📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| // 2. Removed the redundant useEffect hook. | ||||||||||||||
| // 'useKeyboardNavigation' already handles the Enter key. | ||||||||||||||
|
|
||||||||||||||
| const handleSearch = async (searchQuery) => { | ||||||||||||||
| if (!searchQuery.trim()) return; | ||||||||||||||
|
|
||||||||||||||
| try { | ||||||||||||||
| // Construct search URL (Google search) | ||||||||||||||
| const searchUrl = `https://www.google.com/search?q=${encodeURIComponent(searchQuery)}`; | ||||||||||||||
|
|
||||||||||||||
| // 3. Call the correct 'openUrl' function | ||||||||||||||
| await openUrl(searchUrl); | ||||||||||||||
|
|
||||||||||||||
| // Hide the PathFinder window | ||||||||||||||
| await invoke('hide_window'); | ||||||||||||||
| } catch (error) { | ||||||||||||||
| console.error("Failed to open browser:", error); | ||||||||||||||
| } | ||||||||||||||
| }; | ||||||||||||||
|
Comment on lines
+15
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Handle permission errors and keep UX responsive. If openUrl is denied by capabilities, this throws. Consider surfacing a toast and keeping the window visible on failure. Also verify opener scopes include https URLs. Docs detail URL scope configuration. (v2.tauri.app) 🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| return ( | ||||||||||||||
| <div className="option-list"> | ||||||||||||||
| <div | ||||||||||||||
| {...getItemProps(0)} | ||||||||||||||
| className={`option-item ${getItemProps(0).className}`} | ||||||||||||||
| onClick={() => handleSearch(query)} | ||||||||||||||
| > | ||||||||||||||
| <span className="icon">🌐</span> | ||||||||||||||
| <span>Search online for “{query}”</span> | ||||||||||||||
| <span>Search online for "{query}"</span> | ||||||||||||||
| </div> | ||||||||||||||
|
Comment on lines
34
to
41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Drop duplicate className derivation. getItemProps already includes className; avoid concatenating it again. Included in the button refactor above. 🧰 Tools🪛 Biome (2.1.2)[error] 34-38: Static Elements should not be interactive. To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value. (lint/a11y/noStaticElementInteractions) [error] 34-38: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event. Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation. (lint/a11y/useKeyWithClickEvents) 🤖 Prompt for AI AgentsFix a11y: use a button, not an interactive div. Addresses Biome errors: static element interactions and missing keyboard equivalence. - <div
- {...getItemProps(0)}
- className={`option-item ${getItemProps(0).className}`}
- onClick={() => handleSearch(query)}
- >
+ <button
+ {...getItemProps(0)}
+ type="button"
+ aria-label={`Search online for ${query}`}
+ >
<span className="icon">🌐</span>
<span>Search online for "{query}"</span>
- </div>
+ </button>
🧰 Tools🪛 Biome (2.1.2)[error] 34-38: Static Elements should not be interactive. To add interactivity such as a mouse or key event listener to a static element, give the element an appropriate role value. (lint/a11y/noStaticElementInteractions) [error] 34-38: Enforce to have the onClick mouse event with the onKeyUp, the onKeyDown, or the onKeyPress keyboard event. Actions triggered using mouse events should have corresponding keyboard events to account for keyboard-only navigation. (lint/a11y/useKeyWithClickEvents) 🤖 Prompt for AI Agents |
||||||||||||||
| </div> | ||||||||||||||
| ); | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Formatting glitch and missed AppImage match.
Split statements for readability; also ensure AppImage is detected (is_app_file currently matches "AppImage" but extensions are lowercased).
Additionally (outside this range), fix extension match:
📝 Committable suggestion
🤖 Prompt for AI Agents