From c4fd48376dcffc6f43a88dd21491164be1c81ae3 Mon Sep 17 00:00:00 2001 From: fullex <106392080+0xfullex@users.noreply.github.com> Date: Thu, 11 Dec 2025 14:51:32 +0800 Subject: [PATCH] feat(SelectionAssistant): open URL for search action (#11770) * feat(SelectionAssistant): open URL for search action When selected text is a valid URI or file path, directly open it instead of searching. This enhances the search action to be smarter about handling URLs and file paths. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> * fix: format * feat: increase maximum custom and enabled items in settings actions list Updated the maximum number of custom items from 8 to 10 and enabled items from 6 to 8 in the settings actions list to enhance user customization options. --------- Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- .../hooks/useSettingsActionsList.ts | 4 +- .../selection/toolbar/SelectionToolbar.tsx | 90 +++++++++++++------ 2 files changed, 64 insertions(+), 30 deletions(-) diff --git a/src/renderer/src/pages/settings/SelectionAssistantSettings/hooks/useSettingsActionsList.ts b/src/renderer/src/pages/settings/SelectionAssistantSettings/hooks/useSettingsActionsList.ts index 341ac8f9c6..843fee150b 100644 --- a/src/renderer/src/pages/settings/SelectionAssistantSettings/hooks/useSettingsActionsList.ts +++ b/src/renderer/src/pages/settings/SelectionAssistantSettings/hooks/useSettingsActionsList.ts @@ -9,8 +9,8 @@ import { DEFAULT_SEARCH_ENGINES } from '../components/SelectionActionSearchModal const logger = loggerService.withContext('useSettingsActionsList') -const MAX_CUSTOM_ITEMS = 8 -const MAX_ENABLED_ITEMS = 6 +const MAX_CUSTOM_ITEMS = 10 +const MAX_ENABLED_ITEMS = 8 export const useActionItems = ( initialItems: ActionItem[] | undefined, diff --git a/src/renderer/src/windows/selection/toolbar/SelectionToolbar.tsx b/src/renderer/src/windows/selection/toolbar/SelectionToolbar.tsx index 505a3b8fda..37a56acba5 100644 --- a/src/renderer/src/windows/selection/toolbar/SelectionToolbar.tsx +++ b/src/renderer/src/windows/selection/toolbar/SelectionToolbar.tsx @@ -202,6 +202,30 @@ const SelectionToolbar: FC<{ demo?: boolean }> = ({ demo = false }) => { } }, [customCss, demo]) + /** + * Check if text is a valid URI or file path + */ + const isUriOrFilePath = (text: string): boolean => { + const trimmed = text.trim() + // Must not contain newlines or whitespace + if (/\s/.test(trimmed)) { + return false + } + // URI patterns: http://, https://, ftp://, file://, etc. + if (/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(trimmed)) { + return true + } + // Windows absolute path: C:\, D:\, etc. + if (/^[a-zA-Z]:[/\\]/.test(trimmed)) { + return true + } + // Unix absolute path: /path/to/file + if (/^\/[^/]/.test(trimmed)) { + return true + } + return false + } + // copy selected text to clipboard const handleCopy = useCallback(async () => { if (selectedText.current) { @@ -219,6 +243,43 @@ const SelectionToolbar: FC<{ demo?: boolean }> = ({ demo = false }) => { } }, [setTimeoutTimer]) + const handleSearch = useCallback((action: ActionItem) => { + if (!action.selectedText) return + + const selectedText = action.selectedText.trim() + + let actionString = '' + if (isUriOrFilePath(selectedText)) { + actionString = selectedText + } else { + if (!action.searchEngine) return + + const customUrl = action.searchEngine.split('|')[1] + if (!customUrl) return + + actionString = customUrl.replace('{{queryString}}', encodeURIComponent(selectedText)) + } + + window.api?.openWebsite(actionString) + window.api?.selection.hideToolbar() + }, []) + + /** + * Quote the selected text to the inputbar of the main window + */ + const handleQuote = (action: ActionItem) => { + if (action.selectedText) { + window.api?.quoteToMainWindow(action.selectedText) + window.api?.selection.hideToolbar() + } + } + + const handleDefaultAction = (action: ActionItem) => { + // [macOS] only macOS has the available isFullscreen mode + window.api?.selection.processAction(action, isFullScreen.current) + window.api?.selection.hideToolbar() + } + const handleAction = useCallback( (action: ActionItem) => { if (demo) return @@ -241,36 +302,9 @@ const SelectionToolbar: FC<{ demo?: boolean }> = ({ demo = false }) => { break } }, - [demo, handleCopy] + [demo, handleCopy, handleSearch] ) - const handleSearch = (action: ActionItem) => { - if (!action.searchEngine) return - - const customUrl = action.searchEngine.split('|')[1] - if (!customUrl) return - - const searchUrl = customUrl.replace('{{queryString}}', encodeURIComponent(action.selectedText || '')) - window.api?.openWebsite(searchUrl) - window.api?.selection.hideToolbar() - } - - /** - * Quote the selected text to the inputbar of the main window - */ - const handleQuote = (action: ActionItem) => { - if (action.selectedText) { - window.api?.quoteToMainWindow(action.selectedText) - window.api?.selection.hideToolbar() - } - } - - const handleDefaultAction = (action: ActionItem) => { - // [macOS] only macOS has the available isFullscreen mode - window.api?.selection.processAction(action, isFullScreen.current) - window.api?.selection.hideToolbar() - } - return (