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>
This commit is contained in:
fullex 2025-12-11 14:51:32 +08:00 committed by GitHub
parent 600a045ff7
commit c4fd48376d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 64 additions and 30 deletions

View File

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

View File

@ -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 (
<Container>
<LogoWrapper $draggable={!demo}>