Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Vanessa 2026-03-09 11:17:39 +08:00
commit 3289aecb7a
17 changed files with 63 additions and 61 deletions

View file

@ -958,6 +958,8 @@ app.whenReady().then(() => {
new Notification({
title: data.title,
body: data.body,
icon: path.join(appDir, "stage", "icon.png"),
timeoutType: "never",
}).show();
break;
case "setSpellCheckerLanguages":

View file

@ -246,11 +246,11 @@ export const bazaar = {
let themeMode = "";
if (bazaarType === "themes") {
const themeValue = (bazaar.element.querySelector("#bazaarSelect") as HTMLSelectElement).value;
if ((themeValue === "0" && item.modes.includes("dark")) ||
themeValue === "1" && item.modes.includes("light")) {
if ((themeValue === "0" && item.modes?.includes("dark")) ||
themeValue === "1" && item.modes?.includes("light")) {
hide = true;
}
themeMode = item.modes.toString();
themeMode = item.modes?.toString() || "";
}
let showSwitch = false;
if (["icons", "themes"].includes(bazaarType)) {

File diff suppressed because one or more lines are too long

View file

@ -62,6 +62,7 @@ func getUpdatedPackage(c *gin.Context) {
if !util.ParseJsonArgs(arg, ret, util.BindJsonArg("frontend", true, &frontend)) {
return
}
plugins, widgets, icons, themes, templates := model.GetUpdatedPackages(frontend)
ret.Data = map[string]interface{}{
"plugins": plugins,

View file

@ -190,7 +190,7 @@ func loadBazaarInfo() (ret *BazaarInfo) {
return
}
// saveBazaarInfo 保存集市持久化信息(调用者需持有写锁)
// saveBazaarInfo 保存集市持久化信息(调用者需持有 bazaarInfoCacheLock 写锁)
func saveBazaarInfo() {
infoPath := filepath.Join(util.DataDir, "storage", "bazaar.json")

View file

@ -142,7 +142,7 @@ func addClassToKramdownIAL(ial [][]string, class string) [][]string {
if len(attr) < 2 || attr[0] != "class" {
continue
}
for _, item := range strings.Fields(attr[1]) {
for item := range strings.FieldsSeq(attr[1]) {
if item == class {
return ial
}

View file

@ -40,6 +40,7 @@ type StageBazaarResult struct {
}
var stageBazaarFlight singleflight.Group
var onlineCheckFlight singleflight.Group
var bazaarStatsFlight singleflight.Group
// getStageAndBazaar 获取 stage 索引和 bazaar 索引,相同 pkgType 的并发调用会合并为一次实际请求 (single-flight)
@ -112,6 +113,25 @@ func getStageAndBazaar0(pkgType string) (result StageBazaarResult) {
}
}
func isBazaarOnline() bool {
v, err, _ := onlineCheckFlight.Do("bazaarOnline", func() (interface{}, error) {
return isBazaarOnline0(), nil
})
if err != nil {
return false
}
return v.(bool)
}
func isBazaarOnline0() (ret bool) {
// Improve marketplace loading when offline https://github.com/siyuan-note/siyuan/issues/12050
ret = util.IsOnline(util.BazaarOSSServer+"/204", true, 3000)
if !ret {
util.PushErrMsg(util.Langs[util.Lang][24], 5000)
}
return
}
// getStageIndexFromCache 仅从缓存获取 stage 索引,过期或无缓存时返回 nil
func getStageIndexFromCache(pkgType string) (ret *StageIndex, err error) {
if val, found := cachedStageIndex.Get(pkgType); found {
@ -169,15 +189,6 @@ func getStageRepoByURL(ctx context.Context, pkgType, url string) *StageRepo {
return stageIndex.reposByURL[url]
}
func isBazaarOnline() (ret bool) {
// Improve marketplace loading when offline https://github.com/siyuan-note/siyuan/issues/12050
ret = util.IsOnline(util.BazaarOSSServer+"/204", true, 3000)
if !ret {
util.PushErrMsg(util.Langs[util.Lang][24], 5000)
}
return
}
// bazaarStats 集市包统计信息
type bazaarStats struct {
Downloads int `json:"downloads"` // 下载次数

View file

@ -8,7 +8,7 @@ require (
github.com/88250/epub v0.0.0-20230830085737-c19055cd1f48
github.com/88250/go-humanize v0.0.0-20240424102817-4f78fac47ea7
github.com/88250/gulu v1.2.3-0.20260124101918-98654a7ca98a
github.com/88250/lute v1.7.7-0.20260225092249-74bb97dab3e6
github.com/88250/lute v1.7.7-0.20260308140134-42edbfcd7ea6
github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1
github.com/ClarkThan/ahocorasick v0.0.0-20231011042242-30d1ef1347f4
github.com/ConradIrwin/font v0.2.1
@ -164,7 +164,6 @@ require (
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/otiai10/gosseract/v2 v2.4.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect

View file

@ -14,8 +14,8 @@ github.com/88250/go-sqlite3 v1.14.13-0.20231214121541-e7f54c482950 h1:Pa5hMiBceT
github.com/88250/go-sqlite3 v1.14.13-0.20231214121541-e7f54c482950/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/88250/gulu v1.2.3-0.20260124101918-98654a7ca98a h1:s86WMolaqommXG1k2vXg9Gf1iXvgtHSdeD0eeJTQVR8=
github.com/88250/gulu v1.2.3-0.20260124101918-98654a7ca98a/go.mod h1:D+Db16m0N7r9MLZCMcj1a0ZsEGQAxDZkadOn79Gh0vI=
github.com/88250/lute v1.7.7-0.20260225092249-74bb97dab3e6 h1:4MrqDppSGkgymxYMmkmlZOHNF0SCbDsLPJYULJJJHME=
github.com/88250/lute v1.7.7-0.20260225092249-74bb97dab3e6/go.mod h1:WYyUw//5yVw9BJnoVjx7rI/3szsISxNZCYGOqTIrV0o=
github.com/88250/lute v1.7.7-0.20260308140134-42edbfcd7ea6 h1:lpPCdMskqSTU0kz88vcdNh8uU/krTxQKIOnQzs7mXA0=
github.com/88250/lute v1.7.7-0.20260308140134-42edbfcd7ea6/go.mod h1:WYyUw//5yVw9BJnoVjx7rI/3szsISxNZCYGOqTIrV0o=
github.com/88250/pdfcpu v0.3.14-0.20250424122812-f10e8d9d8d46 h1:Bq1JsDfVbHKUxNL/B2JXd8cC/1h6aFjrlXpGycnh0Hk=
github.com/88250/pdfcpu v0.3.14-0.20250424122812-f10e8d9d8d46/go.mod h1:fVfOloBzs2+W2VJCCbq60XIxc3yJHAZ0Gahv1oO0gyI=
github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1 h1:48T899JQDwyyRu9yXHePYlPdHtpJfrJEUGBMH3SMBWY=
@ -296,8 +296,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/mssola/useragent v1.0.0 h1:WRlDpXyxHDNfvZaPEut5Biveq86Ze4o4EMffyMxmH5o=
github.com/mssola/useragent v1.0.0/go.mod h1:hz9Cqz4RXusgg1EdI4Al0INR62kP7aPSRNHnpU+b85Y=
github.com/olahol/melody v1.4.0 h1:Pa5SdeZL/zXPi1tJuMAPDbl4n3gQOThSL6G1p4qZ4SI=
@ -340,13 +338,8 @@ github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEv
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
github.com/restic/chunker v0.4.0 h1:YUPYCUn70MYP7VO4yllypp2SjmsRhRJaad3xKu1QFRw=
github.com/restic/chunker v0.4.0/go.mod h1:z0cH2BejpW636LXw0R/BGyv+Ey8+m9QGiOanDHItzyw=
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
github.com/richardlehane/mscfb v1.0.6 h1:eN3bvvZCp00bs7Zf52bxNwAx5lJDBK1tCuH19qq5aC8=
github.com/richardlehane/mscfb v1.0.6/go.mod h1:pe0+IUIc0AHh0+teNzBlJCtSyZdFOGgV4ZK9bsoV+Jo=
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/richardlehane/msoleps v1.0.6 h1:9BvkpjvD+iUBalUY4esMwv6uBkfOip/Lzvd93jvR9gg=
github.com/richardlehane/msoleps v1.0.6/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@ -432,8 +425,6 @@ github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAz
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
github.com/xuri/excelize/v2 v2.9.0 h1:1tgOaEq92IOEumR1/JfYS/eR0KHOCsRv/rYXXh6YJQE=
github.com/xuri/excelize/v2 v2.9.0/go.mod h1:uqey4QBZ9gdMeWApPLdhm9x+9o2lq4iVmjiLfBS5hdE=
github.com/xuri/excelize/v2 v2.10.1 h1:V62UlqopMqha3kOpnlHy2CcRVw1V8E63jFoWUmMzxN0=
github.com/xuri/excelize/v2 v2.10.1/go.mod h1:iG5tARpgaEeIhTqt3/fgXCGoBRt4hNXgCp3tfXKoOIc=
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
@ -459,8 +450,6 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@ -491,8 +480,6 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

View file

@ -89,6 +89,7 @@ func HandleAssetsRemoveEvent(assetAbsPath string) {
if gulu.File.IsDir(assetAbsPath) {
return
}
// 跳过隐藏文件,如 WPS 的临时文件、Mac 的 .DS_Store
if filelock.IsHidden(assetAbsPath) {
return
}

View file

@ -65,7 +65,6 @@ func watchAssets() {
return
}
//logging.LogInfof("assets changed: %s", event)
if watcher.Write == event.Op {
IncSync()
}
@ -88,6 +87,7 @@ func watchAssets() {
}
}
}()
if err := assetsWatcher.Start(10 * time.Second); err != nil {
logging.LogErrorf("start assets watcher for folder [%s] failed: %s", assetsDir, err)
return

View file

@ -322,6 +322,7 @@ func GetBazaarPackageREADME(ctx context.Context, repoURL, repoHash, pkgType stri
return
}
// InstallBazaarPackage 安装集市包themeMode 仅在 pkgType 为 "themes" 时生效
func InstallBazaarPackage(pkgType, repoURL, repoHash, packageName string, themeMode int) error {
installPath, jsonFileName, err := getPackageInstallPath(pkgType, packageName)
if err != nil {

View file

@ -337,10 +337,11 @@ func InitConf() {
Conf.OpenHelp = true
}
} else {
if 0 < semver.Compare("v"+util.Ver, "v"+Conf.System.KernelVersion) {
cmp := semver.Compare("v"+util.Ver, "v"+Conf.System.KernelVersion)
if 0 < cmp {
logging.LogInfof("upgraded from version [%s] to [%s]", Conf.System.KernelVersion, util.Ver)
Conf.ShowChangelog = true
} else if 0 > semver.Compare("v"+util.Ver, "v"+Conf.System.KernelVersion) {
} else if 0 > cmp {
logging.LogInfof("downgraded from version [%s] to [%s]", Conf.System.KernelVersion, util.Ver)
}
@ -1033,9 +1034,12 @@ const (
MaskedAccessAuthCode = "*******"
)
// GetMaskedConf 获取脱敏后的 Conf
func GetMaskedConf() (ret *AppConf, err error) {
// 脱敏处理
data, err := gulu.JSON.MarshalIndentJSON(Conf, "", " ")
// 序列化时持锁,避免与 loadThemes/LoadIcons 等写操作并发导致 slice 在编码过程中被改写而 panic https://github.com/siyuan-note/siyuan/issues/16978
Conf.m.Lock()
data, err := gulu.JSON.MarshalJSON(Conf)
Conf.m.Unlock()
if err != nil {
logging.LogErrorf("marshal conf failed: %s", err)
return

View file

@ -40,9 +40,9 @@ func WatchEmojis() {
}
func watchEmojis() {
CloseWatchEmojis()
emojisDir := filepath.Join(util.DataDir, "emojis")
CloseWatchEmojis()
emojisWatcher = watcher.New()
if !gulu.File.IsDir(emojisDir) {
@ -74,6 +74,7 @@ func watchEmojis() {
}
}
}()
if err := emojisWatcher.Start(10 * time.Second); err != nil {
logging.LogErrorf("start emojis watcher for folder [%s] failed: %s", emojisDir, err)
return

View file

@ -267,9 +267,12 @@ func getPetals() (ret []*Petal) {
var tmp []*Petal
pluginsDir := filepath.Join(util.DataDir, "plugins")
for _, petal := range ret {
pluginPath := filepath.Join(pluginsDir, petal.Name)
if hasPluginFiles(pluginPath) {
pluginJSONPath := filepath.Join(pluginsDir, petal.Name, "plugin.json")
if filelock.IsExist(pluginJSONPath) {
tmp = append(tmp, petal)
} else {
// 插件不存在时,删除对应的持久化信息
bazaar.RemovePackageInfo("plugins", petal.Name)
}
}
if len(tmp) != len(ret) {
@ -281,20 +284,3 @@ func getPetals() (ret []*Petal) {
}
return
}
// hasPluginFiles 检查插件安装目录是否存在且包含文件
func hasPluginFiles(pluginPath string) bool {
if !filelock.IsExist(pluginPath) {
return false
}
entries, err := os.ReadDir(pluginPath)
if err != nil {
return false
}
for _, entry := range entries {
if !entry.IsDir() {
return true
}
}
return false
}

View file

@ -749,6 +749,10 @@ func checkoutRepo(id string) {
CloseWatchEmojis()
defer WatchEmojis()
// 若主题支持同步,需关闭监听器
// CloseWatchThemes()
// defer WatchThemes()
// 恢复快照时自动暂停同步,避免刚刚恢复后的数据又被同步覆盖
syncEnabled := Conf.Sync.Enabled
Conf.Sync.Enabled = false

View file

@ -569,6 +569,10 @@ func IndexBlockTree(tree *parse.Tree) {
}
execInsertBlocktrees(tx, tree, changedNodes)
if err = tx.Commit(); err != nil {
logging.LogErrorf("commit transaction failed: %s", err)
}
}
func UpsertBlockTree(tree *parse.Tree) {
@ -628,6 +632,10 @@ func UpsertBlockTree(tree *parse.Tree) {
}
execInsertBlocktrees(tx, tree, changedNodes)
if err = tx.Commit(); err != nil {
logging.LogErrorf("commit transaction failed: %s", err)
}
}
func execInsertBlocktrees(tx *sql.Tx, tree *parse.Tree, changedNodes []*ast.Node) {
@ -651,9 +659,6 @@ func execInsertBlocktrees(tx *sql.Tx, tree *parse.Tree, changedNodes []*ast.Node
return
}
}
if err = tx.Commit(); err != nil {
logging.LogErrorf("commit transaction failed: %s", err)
}
}
func InitBlockTree(force bool) {