Skip to content

Commit 2012c29

Browse files
committed
✨ 搜索支持区分繁简选项 Add Simplified/Traditional Chinese sensitivity option for search
新增搜索配置 hanSensitive(区分繁简,默认开启 = 现状行为,命名与语义 向 caseSensitive 对齐)。关闭后简体与繁体可互相检索。 - FTS 层:关闭区分繁简时,blocks_fts / blocks_fts_case_insensitive 建表 追加 han_insensitive 分词器参数(依赖 88250/go-sqlite3 配套补丁), 繁体在分词时按单字折叠为简体,token 偏移仍指向原文; - 切换选项复用区分大小写现有的 FullReindex 流程; - 高亮:EncloseHighlighting 在关闭区分繁简时将关键字逐字符展开为 繁简等价字符类(如 诗经 -> [诗詩][经經]),与分词器使用同一份 OpenCC TSCharacters 映射数据; - 兼容:旧版本 conf.json 缺 hanSensitive 字段时按既往行为置为开启; setSearch 请求未携带该字段(现行前端/第三方调用)时保持当前值, 避免被零值意外翻转并触发重建索引; - 遵照配置界面重写计划(#17601),本次不改动设置界面与 i18n,仅添加 配置数据结构字段与前端类型定义(config.d.ts)。 范围说明:本次仅覆盖块全文搜索(关键字/查询语法)的命中与高亮; 历史/资源文件搜索、数据库属性过滤、虚拟引用、查找替换的替换动作 不在本次范围内(替换保持字节精确以避免误改原文)。 Fixes #13304
1 parent 56d1d10 commit 2012c29

14 files changed

Lines changed: 576 additions & 6 deletions

File tree

app/src/types/config.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,6 +1336,10 @@ declare namespace Config {
13361336
* Whether to search embedded blocks
13371337
*/
13381338
embedBlock: boolean;
1339+
/**
1340+
* Whether to distinguish between Simplified and Traditional Chinese characters when searching
1341+
*/
1342+
hanSensitive: boolean;
13391343
/**
13401344
* Whether to search heading blocks
13411345
*/

kernel/api/setting.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,11 +501,17 @@ func setSearch(c *gin.Context) {
501501
return
502502
}
503503

504+
if _, exists := arg["hanSensitive"]; !exists {
505+
// 兼容未携带该字段的旧版前端/第三方调用:保持当前值,避免被零值意外关闭并触发重建索引
506+
s.HanSensitive = model.Conf.Search.HanSensitive
507+
}
508+
504509
if 32 > s.Limit {
505510
s.Limit = 32
506511
}
507512

508513
oldCaseSensitive := model.Conf.Search.CaseSensitive
514+
oldHanSensitive := model.Conf.Search.HanSensitive
509515
oldIndexAssetPath := model.Conf.Search.IndexAssetPath
510516

511517
oldVirtualRefName := model.Conf.Search.VirtualRefName
@@ -517,9 +523,10 @@ func setSearch(c *gin.Context) {
517523
model.Conf.Save()
518524

519525
sql.SetCaseSensitive(s.CaseSensitive)
526+
sql.SetHanSensitive(s.HanSensitive)
520527
sql.SetIndexAssetPath(s.IndexAssetPath)
521528

522-
if needFullReindex := s.CaseSensitive != oldCaseSensitive || s.IndexAssetPath != oldIndexAssetPath; needFullReindex {
529+
if needFullReindex := s.CaseSensitive != oldCaseSensitive || s.HanSensitive != oldHanSensitive || s.IndexAssetPath != oldIndexAssetPath; needFullReindex {
523530
model.FullReindex(false)
524531
}
525532

kernel/cli/cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ var rootCmd = &cobra.Command{
8787
logging.SetLogToStdout(false)
8888

8989
model.InitConf()
90+
sql.SetHanSensitive(model.Conf.Search.HanSensitive) // 须在 InitDatabase 之前设置:FTS 建表的分词器参数依赖该选项
9091
sql.InitDatabase(false)
9192
sql.InitHistoryDatabase(false)
9293
sql.InitAssetContentDatabase(false)

kernel/cli/cmd/serve.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ import (
3232
)
3333

3434
var serveCmd = &cobra.Command{
35-
Use: "serve",
36-
Short: "Start kernel HTTP server",
35+
Use: "serve",
36+
Short: "Start kernel HTTP server",
3737
FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
3838
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
3939
return nil // bypass root's init — Boot() handles it
@@ -67,6 +67,7 @@ var serveCmd = &cobra.Command{
6767
model.InitConf()
6868
go server.Serve(false, model.Conf.CookieKey)
6969
model.InitAppearance()
70+
sql.SetHanSensitive(model.Conf.Search.HanSensitive) // 须在 InitDatabase 之前设置:FTS 建表的分词器参数依赖该选项
7071
sql.InitDatabase(false)
7172
sql.InitHistoryDatabase(false)
7273
sql.InitAssetContentDatabase(false)

kernel/conf/search.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ type Search struct {
4646

4747
Limit int `json:"limit"`
4848
CaseSensitive bool `json:"caseSensitive"`
49+
HanSensitive bool `json:"hanSensitive"` // 区分繁简:默认开启(与既往行为一致);关闭后全文搜索不区分简体/繁体中文字形
4950

5051
Name bool `json:"name"`
5152
Alias bool `json:"alias"`
@@ -89,6 +90,7 @@ func NewSearch() *Search {
8990

9091
Limit: 64,
9192
CaseSensitive: false,
93+
HanSensitive: true,
9294

9395
Name: true,
9496
Alias: true,

kernel/harmony/kernel.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ func StartKernel(container, appDir, workspaceBaseDir, timezoneID, localIPs, lang
5454
go server.Serve(false, model.Conf.CookieKey)
5555
go func() {
5656
model.InitAppearance()
57+
sql.SetHanSensitive(model.Conf.Search.HanSensitive) // 须在 InitDatabase 之前设置:FTS 建表的分词器参数依赖该选项
5758
sql.InitDatabase(false)
5859
sql.InitHistoryDatabase(false)
5960
sql.InitAssetContentDatabase(false)

kernel/mobile/kernel.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ func StartKernel(container, appDir, workspaceBaseDir, timezoneID, localIPs, lang
212212
go server.Serve(false, model.Conf.CookieKey)
213213
go func() {
214214
model.InitAppearance()
215+
sql.SetHanSensitive(model.Conf.Search.HanSensitive) // 须在 InitDatabase 之前设置:FTS 建表的分词器参数依赖该选项
215216
sql.InitDatabase(false)
216217
sql.InitHistoryDatabase(false)
217218
sql.InitAssetContentDatabase(false)

kernel/model/conf.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ func InitConf() {
133133
logging.LogErrorf("parse conf [%s] failed: %s", confPath, err)
134134
} else {
135135
logging.LogInfof("loaded conf [%s]", confPath)
136+
if nil != Conf.Search && !bytes.Contains(data, []byte("\"hanSensitive\"")) {
137+
// 旧版本配置无 hanSensitive 字段:保持既往行为(区分繁简)
138+
Conf.Search.HanSensitive = true
139+
}
136140
}
137141
}
138142
}

kernel/search/hanconv.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// SiYuan - Refactor your thinking
2+
// Copyright (c) 2020-present, b3log.org
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
17+
package search
18+
19+
import (
20+
"regexp"
21+
"sort"
22+
"strings"
23+
)
24+
25+
// hanSimpToTrads 是 hanTradToSimp 的反向索引:简体字 -> 折叠到该简体的全部繁体字。
26+
var hanSimpToTrads = map[rune][]rune{}
27+
28+
func init() {
29+
for t, s := range hanTradToSimp {
30+
hanSimpToTrads[s] = append(hanSimpToTrads[s], t)
31+
}
32+
for _, ts := range hanSimpToTrads {
33+
sort.Slice(ts, func(i, j int) bool { return ts[i] < ts[j] })
34+
}
35+
}
36+
37+
// hanCharClass 返回与 r 繁简等价的所有字符(含 r 自身对应的简体),用于构造高亮正则。
38+
func hanCharClass(r rune) (ret []rune) {
39+
canon := r
40+
if s, ok := hanTradToSimp[r]; ok {
41+
canon = s
42+
}
43+
ret = append(ret, canon)
44+
ret = append(ret, hanSimpToTrads[canon]...)
45+
return
46+
}
47+
48+
// hanInsensitiveRegexp 将关键字逐字符展开为繁简等价字符类,例如 "诗经" -> "[诗詩][经經]"。
49+
// 仅用于搜索结果高亮;等价关系与 go-sqlite3 中 siyuan 分词器 han_insensitive 的映射表
50+
// 来自同一份 OpenCC TSCharacters 数据,必须保持一致。
51+
func hanInsensitiveRegexp(k string) string {
52+
var b strings.Builder
53+
for _, r := range k {
54+
class := hanCharClass(r)
55+
if 1 == len(class) {
56+
b.WriteString(regexp.QuoteMeta(string(r)))
57+
continue
58+
}
59+
b.WriteString("[")
60+
for _, c := range class {
61+
b.WriteString(regexp.QuoteMeta(string(c)))
62+
}
63+
b.WriteString("]")
64+
}
65+
return b.String()
66+
}

0 commit comments

Comments
 (0)