From 8c1c658b15bff56e29f4f13f60f1dc48c7c03fe0 Mon Sep 17 00:00:00 2001 From: CNWei Date: Wed, 4 Nov 2026 12:39:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(search):=20=E6=95=88=E6=9E=9C=E4=BA=94?= =?UTF-8?q?=E5=BF=AB=E6=8D=B7=E6=A0=8F=E5=8A=A0=E5=85=A5=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引擎选项新增「本地」并作为默认;本地模式回车打开第一个匹配条目/分组 - 快捷栏改为贴在搜索框右侧内部展开,贴合效果五 - 外部引擎仍在新标签页搜索,输入时本地过滤逻辑保持 --- stash-web/src/App.css | 17 +++++++++++-- stash-web/src/AppNext.tsx | 53 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/stash-web/src/App.css b/stash-web/src/App.css index 861a7f3..59c1fd2 100644 --- a/stash-web/src/App.css +++ b/stash-web/src/App.css @@ -1478,8 +1478,8 @@ html.theme-transition .site-background { .engine-menu { position: absolute; - top: calc(100% + 12px); - right: 0; + top: 50%; + right: calc(100% + 6px); z-index: var(--su-z-float); display: flex; flex-direction: row; @@ -1489,6 +1489,19 @@ html.theme-transition .site-background { border-radius: var(--su-radius-sm); background-color: var(--su-bg-card); box-shadow: var(--su-shadow-pop); + transform: translateY(-50%); + animation: engine-bar-in 140ms var(--su-ease-out); +} + +@keyframes engine-bar-in { + from { + opacity: 0; + transform: translate(6px, -50%); + } + to { + opacity: 1; + transform: translate(0, -50%); + } } .engine-option { diff --git a/stash-web/src/AppNext.tsx b/stash-web/src/AppNext.tsx index 23c62a9..92e3187 100644 --- a/stash-web/src/AppNext.tsx +++ b/stash-web/src/AppNext.tsx @@ -63,6 +63,7 @@ type Route = | { kind: 'api' } const SEARCH_ENGINES = [ + { key: 'local', name: '本地', search: '' }, { key: 'google', name: 'Google', search: 'https://www.google.com/search?q=' }, { key: 'bing', name: 'Bing', search: 'https://www.bing.com/search?q=' }, { key: 'baidu', name: '百度', search: 'https://www.baidu.com/s?wd=' }, @@ -202,6 +203,24 @@ function matches(entry: Record, keyword: string): boolean { return text.includes(keyword) } +/** 本地搜索回车时打开第一个匹配项;没有条目命中但分组名命中时打开分组 */ +function findLocalTarget( + groups: GroupNode[], + keyword: string, +): { group: GroupNode; entry?: Record } | undefined { + for (const group of groups) { + if (group.enabled === false) continue + if (group.title.toLowerCase().includes(keyword)) return { group } + const entry = (group.entries as unknown as Record[]).find((item) => + matches(item, keyword), + ) + if (entry !== undefined) return { group, entry } + const child = findLocalTarget(group.children, keyword) + if (child !== undefined) return child + } + return undefined +} + export default function AppNext() { const { status, projects, error, live, refreshing, refresh } = useProjects() const { route, navigate } = useRoute() @@ -388,6 +407,20 @@ export default function AppNext() { } if (event.key !== 'Enter' || keyword.trim() === '') return event.preventDefault() + setEngineOpen(false) + + if (searchEngine.key === 'local') { + const target = findLocalTarget(manage.groups, keywordLower) + if (target !== undefined) { + navigate( + target.entry === undefined + ? `/g/${encodeURIComponent(target.group.key)}` + : `/g/${encodeURIComponent(target.group.key)}/i/${encodeURIComponent(String(target.entry.id))}`, + ) + } + return + } + window.open( `${searchEngine.search}${encodeURIComponent(keyword.trim())}`, '_blank', @@ -396,7 +429,7 @@ export default function AppNext() { } input.addEventListener('keydown', handleKeyDown) return () => input.removeEventListener('keydown', handleKeyDown) - }, [keyword, searchEngine]) + }, [keyword, keywordLower, manage.groups, navigate, searchEngine]) const chooseEngine = (next: SearchEngine) => { setSearchEngine(next) @@ -797,8 +830,16 @@ export default function AppNext() { value={keyword} onChange={setKeyword} inputRef={searchRef} - placeholder="搜索本地项目…" - label="搜索本地项目,回车使用当前搜索引擎" + placeholder={ + searchEngine.key === 'local' + ? '搜索本地项目…' + : `用 ${searchEngine.name} 搜索,回车打开…` + } + label={ + searchEngine.key === 'local' + ? '搜索本地项目,回车打开第一个匹配项' + : `用 ${searchEngine.name} 搜索,回车打开新标签页` + } variant="card" hotkey="k" suffix={ @@ -809,7 +850,11 @@ export default function AppNext() { aria-haspopup="menu" aria-expanded={engineOpen} aria-label={`搜索引擎:${searchEngine.name},点击切换`} - title={`当前:${searchEngine.name};回车用该引擎搜索`} + title={ + searchEngine.key === 'local' + ? '当前:本地搜索;回车打开第一个匹配项' + : `当前:${searchEngine.name};回车用该引擎搜索` + } onClick={() => setEngineOpen((open) => !open)} > {searchEngine.name}