feat(search): 按参考稿重做搜索框并支持后台配置
- 顶部悬浮引擎标签 + 下拉选项,使用 stash-ui 新增的 EngineIcon
- 站内搜索保持现有本地过滤与回车打开首个匹配项
- 外部引擎使用配置的 {query} 模板新标签页搜索
- settings 新增 search.default / search.engines,后台新增『搜索框』配置默认项、名称、地址与启停
- 默认默认搜索项为站内搜索,地址校验必须含 {query}
This commit is contained in:
@@ -119,6 +119,64 @@ pub struct BackgroundSettings {
|
||||
pub dark: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
|
||||
#[serde(default)]
|
||||
pub struct SearchEngineSettings {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
/// 搜索地址模板,用 `{query}` 表示关键词
|
||||
pub url: String,
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(default)]
|
||||
pub struct SearchSettings {
|
||||
/// `internal` 或某个引擎 id
|
||||
pub default: String,
|
||||
pub engines: Vec<SearchEngineSettings>,
|
||||
}
|
||||
|
||||
impl Default for SearchSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
default: "internal".into(),
|
||||
engines: vec![
|
||||
SearchEngineSettings {
|
||||
id: "google".into(),
|
||||
name: "Google".into(),
|
||||
url: "https://www.google.com/search?q={query}".into(),
|
||||
enabled: true,
|
||||
},
|
||||
SearchEngineSettings {
|
||||
id: "bing".into(),
|
||||
name: "Bing".into(),
|
||||
url: "https://www.bing.com/search?q={query}".into(),
|
||||
enabled: true,
|
||||
},
|
||||
SearchEngineSettings {
|
||||
id: "baidu".into(),
|
||||
name: "百度".into(),
|
||||
url: "https://www.baidu.com/s?wd={query}".into(),
|
||||
enabled: true,
|
||||
},
|
||||
SearchEngineSettings {
|
||||
id: "sogou".into(),
|
||||
name: "搜狗".into(),
|
||||
url: "https://www.sogou.com/web?query={query}".into(),
|
||||
enabled: true,
|
||||
},
|
||||
SearchEngineSettings {
|
||||
id: "360".into(),
|
||||
name: "360".into(),
|
||||
url: "https://www.so.com/s?q={query}".into(),
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(default)]
|
||||
pub struct ProfileSettings {
|
||||
@@ -230,6 +288,7 @@ pub struct Settings {
|
||||
pub header: HeaderSettings,
|
||||
pub sidebar: SidebarSettings,
|
||||
pub background: BackgroundSettings,
|
||||
pub search: SearchSettings,
|
||||
pub profile: ProfileSettings,
|
||||
pub site: SiteMetaSettings,
|
||||
}
|
||||
@@ -295,6 +354,46 @@ pub fn validate(settings: &Settings) -> Result<(), StashError> {
|
||||
"暗色背景地址最多 500 个字符".into(),
|
||||
));
|
||||
}
|
||||
if settings.search.engines.len() > 10 {
|
||||
return Err(StashError::InvalidDirectory("搜索引擎最多 10 个".into()));
|
||||
}
|
||||
let mut ids = std::collections::HashSet::new();
|
||||
for engine in &settings.search.engines {
|
||||
if !matches!(
|
||||
engine.id.as_str(),
|
||||
"google" | "bing" | "baidu" | "sogou" | "360"
|
||||
) {
|
||||
return Err(StashError::InvalidDirectory("搜索引擎标识不合法".into()));
|
||||
}
|
||||
if !ids.insert(engine.id.as_str()) {
|
||||
return Err(StashError::InvalidDirectory("搜索引擎标识不能重复".into()));
|
||||
}
|
||||
let name = engine.name.trim();
|
||||
if name.is_empty() || name.chars().count() > 20 {
|
||||
return Err(StashError::InvalidDirectory("搜索引擎名称需为 1–20 个字".into()));
|
||||
}
|
||||
let url = engine.url.trim();
|
||||
if url.chars().count() > 500
|
||||
|| !(url.starts_with("http://") || url.starts_with("https://"))
|
||||
|| !url.contains("{query}")
|
||||
{
|
||||
return Err(StashError::InvalidDirectory(
|
||||
"搜索地址需以 http(s):// 开头并包含 {query}".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
let default = settings.search.default.trim();
|
||||
if default != "internal" {
|
||||
let engine = settings
|
||||
.search
|
||||
.engines
|
||||
.iter()
|
||||
.find(|engine| engine.id == default)
|
||||
.ok_or_else(|| StashError::InvalidDirectory("默认搜索引擎不存在".into()))?;
|
||||
if !engine.enabled {
|
||||
return Err(StashError::InvalidDirectory("默认搜索引擎未启用".into()));
|
||||
}
|
||||
}
|
||||
if settings.site.title.chars().count() > 120 {
|
||||
return Err(StashError::InvalidDirectory("页面标题最多 120 个字".into()));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user