feat(search): 后台可以自己添加搜索引擎

- 搜索引擎 id 放宽:小写字母/数字/-/_,1–30 位,internal 保留;最多 10 个
- 后台搜索框面板加「添加搜索引擎」(id 自动生成 custom-xxx)与每行「删除」
- 自定义引擎用组件库 EngineIcon 的通用地球图标(@stash/ui 0.3.3)
- 搜索地址仍要求 http(s):// + {query},默认项被删/停用时回落到站内搜索
This commit is contained in:
2026-11-06 12:19:06 +08:00
parent 9f1a8863f4
commit 281f04b060
6 changed files with 108 additions and 35 deletions
+53 -20
View File
@@ -28,7 +28,6 @@ import {
ToggleRow,
Toaster,
toast,
type EngineIconName,
type IconName,
type MenuItem,
} from '@stash/ui'
@@ -2092,6 +2091,32 @@ function SearchPanel({
onChange({ ...value, engines, default: nextDefault })
}
/** 自定义引擎:id 只是内部键,点「添加」自动生成,后台不用手填 */
const addEngine = () => {
if (value.engines.length >= 10) return
onChange({
...value,
engines: [
...value.engines,
{
id: `custom-${Date.now().toString(36)}`,
name: '新搜索引擎',
url: '',
enabled: true,
},
],
})
}
const removeEngine = (id: string) => {
const engines = value.engines.filter((engine) => engine.id !== id)
onChange({
...value,
engines,
default: value.default === id ? 'internal' : value.default,
})
}
const defaultOptions = [
{ value: 'internal', label: '站内搜索' },
...value.engines
@@ -2103,8 +2128,8 @@ function SearchPanel({
<Space direction="column" gap="lg" align="stretch" className="admin-form">
<Title level={4}>搜索框</Title>
<Text size="sm" tone="muted">
说明:站内搜索始终可用;外部搜索引擎可以改名称、地址和启停,地址里的 {'{query}'}{' '}
会替换成关键词。默认项是刷新后搜索框选中的引擎。
说明:站内搜索始终可用;外部搜索引擎可以改名称、地址和启停,也可以自己加(最多 10 个),
地址里的 {'{query}'} 会替换成关键词。默认项是刷新后搜索框选中的引擎。
</Text>
<FormField label="默认搜索项">
@@ -2122,13 +2147,16 @@ function SearchPanel({
key={engine.id}
>
<div className="search-engine-head">
<EngineIcon name={engine.id as EngineIconName} size={18} />
<EngineIcon name={engine.id} size={18} />
<Text strong>{engine.name || engine.id}</Text>
<Switch
checked={engine.enabled}
label={`启用 ${engine.name || engine.id}`}
onChange={(enabled) => updateEngine(engine.id, { enabled })}
/>
<div className="search-engine-actions">
<Switch
checked={engine.enabled}
label={`启用 ${engine.name || engine.id}`}
onChange={(enabled) => updateEngine(engine.id, { enabled })}
/>
<TextButton onClick={() => removeEngine(engine.id)}>删除</TextButton>
</div>
</div>
<div className="search-engine-fields">
<FormField label="名称" htmlFor={`search-name-${engine.id}`}>
@@ -2155,17 +2183,22 @@ function SearchPanel({
</div>
<div>
<Button
variant="secondary"
onClick={() =>
onChange({
default: 'internal',
engines: DEFAULT_SEARCH_ENGINES.map((engine) => ({ ...engine })),
})
}
>
恢复默认引擎
</Button>
<Space gap="sm" wrap>
<Button variant="secondary" onClick={addEngine} disabled={value.engines.length >= 10}>
添加搜索引擎
</Button>
<Button
variant="secondary"
onClick={() =>
onChange({
default: 'internal',
engines: DEFAULT_SEARCH_ENGINES.map((engine) => ({ ...engine })),
})
}
>
恢复默认引擎
</Button>
</Space>
</div>
</Space>
)