feat(search): 自定义引擎支持图标地址(png / svg)

- settings.search.engines[] 新增 icon:空=品牌图标/通用地球,'/' 开头走托管目录,或 http(s) 链接
- 后端校验图标地址(空 / 托管路径 / http(s),≤500 字符)
- 后台每张引擎卡加「图标地址」输入;前后台都用组件库 EngineIcon 的 src(<img> 渲染,SVG 静态不执行脚本)
- @stash/ui 0.3.4
This commit is contained in:
2026-11-06 12:30:15 +08:00
parent 281f04b060
commit fdfd17657c
5 changed files with 65 additions and 6 deletions
+9 -4
View File
@@ -69,7 +69,7 @@ type Route =
| { kind: 'item'; key: string; itemId: string }
| { kind: 'api' }
const INTERNAL_SEARCH = { id: 'internal', name: '站内搜索', url: '' } as const
const INTERNAL_SEARCH = { id: 'internal', name: '站内搜索', url: '', icon: '' } as const
const SEARCH_ENGINE_KEY = 'stash-search-engine'
@@ -362,7 +362,12 @@ export default function AppNext() {
INTERNAL_SEARCH,
...site.search.engines
.filter((engine) => engine.enabled)
.map((engine) => ({ id: engine.id, name: engine.name, url: engine.url })),
.map((engine) => ({
id: engine.id,
name: engine.name,
url: engine.url,
icon: engine.icon ?? '',
})),
]
const searchEngine =
searchEngines.find((engine) => engine.id === searchEngineId) ??
@@ -930,7 +935,7 @@ export default function AppNext() {
}
onClick={() => setEngineOpen((open) => !open)}
>
<EngineIcon name={searchEngine.id} size={14} />
<EngineIcon name={searchEngine.id} src={searchEngine.icon} size={14} />
<span>{searchEngine.name}</span>
</button>
@@ -977,7 +982,7 @@ export default function AppNext() {
className={`engine-option${engine.id === searchEngine.id ? ' is-active' : ''}`}
onClick={() => chooseEngine(engine.id)}
>
<EngineIcon name={engine.id} size={18} />
<EngineIcon name={engine.id} src={engine.icon} size={18} />
<span>{engine.name}</span>
</button>
))}
+14 -1
View File
@@ -2102,6 +2102,7 @@ function SearchPanel({
id: `custom-${Date.now().toString(36)}`,
name: '新搜索引擎',
url: '',
icon: '',
enabled: true,
},
],
@@ -2147,7 +2148,7 @@ function SearchPanel({
key={engine.id}
>
<div className="search-engine-head">
<EngineIcon name={engine.id} size={18} />
<EngineIcon name={engine.id} src={engine.icon} size={18} />
<Text strong>{engine.name || engine.id}</Text>
<div className="search-engine-actions">
<Switch
@@ -2178,6 +2179,18 @@ function SearchPanel({
/>
</FormField>
</div>
<FormField
label="图标地址"
htmlFor={`search-icon-${engine.id}`}
hint="png / svg;留空用默认图标(内置品牌或通用地球),站内图片放 public/ 后填 /xxx.svg"
>
<Input
id={`search-icon-${engine.id}`}
value={engine.icon ?? ''}
placeholder="/icons/xxx.svg 或 https://…"
onChange={(event) => updateEngine(engine.id, { icon: event.target.value })}
/>
</FormField>
</div>
))}
</div>
+7
View File
@@ -84,6 +84,8 @@ export type SearchEngineSettings = {
name: string
/** 搜索地址模板,用 {query} 表示关键词 */
url: string
/** 自定义图标地址(png / svg);空表示用品牌图标或通用地球 */
icon: string
enabled: boolean
}
@@ -99,30 +101,35 @@ export const DEFAULT_SEARCH_ENGINES: SearchEngineSettings[] = [
id: 'google',
name: 'Google',
url: 'https://www.google.com/search?q={query}',
icon: '',
enabled: true,
},
{
id: 'bing',
name: 'Bing',
url: 'https://www.bing.com/search?q={query}',
icon: '',
enabled: true,
},
{
id: 'baidu',
name: '百度',
url: 'https://www.baidu.com/s?wd={query}',
icon: '',
enabled: true,
},
{
id: 'sogou',
name: '搜狗',
url: 'https://www.sogou.com/web?query={query}',
icon: '',
enabled: true,
},
{
id: '360',
name: '360',
url: 'https://www.so.com/s?q={query}',
icon: '',
enabled: true,
},
]