From 0080deca3273d407af61ce430dbdbbc2af0389d5 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Fri, 6 Mar 2026 10:28:08 +0800 Subject: [PATCH 1/8] :art: Improve Linux Wayland compatibility https://github.com/siyuan-note/siyuan/issues/17141 Signed-off-by: Daniel <845765@qq.com> --- app/electron/main.js | 110 ++++++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/app/electron/main.js b/app/electron/main.js index 30f58a4eb..44b27420b 100644 --- a/app/electron/main.js +++ b/app/electron/main.js @@ -63,8 +63,39 @@ if (!app.requestSingleInstanceLock()) { } if (process.platform === "linux") { - app.commandLine.appendSwitch("enable-wayland-ime"); - app.commandLine.appendSwitch("wayland-text-input-version", "3"); + // Linux 平台回退到 x11/XWayland 以解决某些系统上无法输入中文的问题 + // 如果需要使用原生 Wayland 可以通过启动参数 --ozone-platform=wayland 进行覆盖 + app.commandLine.appendSwitch('ozone-platform', 'x11'); +} + +app.setAsDefaultProtocolClient("siyuan"); + +app.commandLine.appendSwitch("disable-web-security"); +app.commandLine.appendSwitch("auto-detect", "false"); +app.commandLine.appendSwitch("no-proxy-server"); +app.commandLine.appendSwitch("enable-features", "PlatformHEVCDecoderSupport"); +app.commandLine.appendSwitch("xdg-portal-required-version", "4"); + +// Support set Chromium command line arguments on the desktop https://github.com/siyuan-note/siyuan/issues/9696 +writeLog("app is packaged [" + app.isPackaged + "], command line args [" + process.argv.join(", ") + "]"); +let argStart = 1; +if (!app.isPackaged) { + argStart = 2; +} + +for (let i = argStart; i < process.argv.length; i++) { + let arg = process.argv[i]; + if (arg.startsWith("--workspace=") || arg.startsWith("--openAsHidden") || arg.startsWith("--port=") || arg.startsWith("siyuan://")) { + // 跳过内置参数 + if (arg.startsWith("--openAsHidden")) { + openAsHidden = true; + writeLog("open as hidden"); + } + continue; + } + + app.commandLine.appendSwitch(arg); + writeLog("command line switch [" + arg + "]"); } try { @@ -297,28 +328,6 @@ const showErrorWindow = (titleZh, titleEn, content, emoji = "⚠️") => { return errWindow.id; }; -const writeLog = (out) => { - console.log(out); - const logFile = path.join(confDir, "app.log"); - let log = ""; - const maxLogLines = 1024; - try { - if (fs.existsSync(logFile)) { - log = fs.readFileSync(logFile).toString(); - let lines = log.split("\n"); - if (maxLogLines < lines.length) { - log = lines.slice(maxLogLines / 2, maxLogLines).join("\n") + "\n"; - } - } - out = out.toString(); - out = new Date().toISOString().replace(/T/, " ").replace(/\..+/, "") + " " + out; - log += out + "\n"; - fs.writeFileSync(logFile, log); - } catch (e) { - console.error(e); - } -}; - let openAsHidden = false; const isOpenAsHidden = function () { return 1 === workspaces.length && openAsHidden; @@ -730,36 +739,6 @@ const initKernel = (workspace, port, lang) => { }); }; -app.setAsDefaultProtocolClient("siyuan"); - -app.commandLine.appendSwitch("disable-web-security"); -app.commandLine.appendSwitch("auto-detect", "false"); -app.commandLine.appendSwitch("no-proxy-server"); -app.commandLine.appendSwitch("enable-features", "PlatformHEVCDecoderSupport"); -app.commandLine.appendSwitch("xdg-portal-required-version", "4"); - -// Support set Chromium command line arguments on the desktop https://github.com/siyuan-note/siyuan/issues/9696 -writeLog("app is packaged [" + app.isPackaged + "], command line args [" + process.argv.join(", ") + "]"); -let argStart = 1; -if (!app.isPackaged) { - argStart = 2; -} - -for (let i = argStart; i < process.argv.length; i++) { - let arg = process.argv[i]; - if (arg.startsWith("--workspace=") || arg.startsWith("--openAsHidden") || arg.startsWith("--port=") || arg.startsWith("siyuan://")) { - // 跳过内置参数 - if (arg.startsWith("--openAsHidden")) { - openAsHidden = true; - writeLog("open as hidden"); - } - continue; - } - - app.commandLine.appendSwitch(arg); - writeLog("command line switch [" + arg + "]"); -} - app.whenReady().then(() => { const resetTrayMenu = (tray, lang, mainWindow) => { if (!mainWindow || mainWindow.isDestroyed()) { @@ -1542,3 +1521,26 @@ app.on("before-quit", (event) => { } }); }); + + +function writeLog(out) { + console.log(out); + const logFile = path.join(confDir, "app.log"); + let log = ""; + const maxLogLines = 1024; + try { + if (fs.existsSync(logFile)) { + log = fs.readFileSync(logFile).toString(); + let lines = log.split("\n"); + if (maxLogLines < lines.length) { + log = lines.slice(maxLogLines / 2, maxLogLines).join("\n") + "\n"; + } + } + out = out.toString(); + out = new Date().toISOString().replace(/T/, " ").replace(/\..+/, "") + " " + out; + log += out + "\n"; + fs.writeFileSync(logFile, log); + } catch (e) { + console.error(e); + } +} \ No newline at end of file From da6720e8f9067899c00bca789a4365bfd9187c2f Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Fri, 6 Mar 2026 11:31:16 +0800 Subject: [PATCH 2/8] :art: Improve Linux Wayland compatibility https://github.com/siyuan-note/siyuan/issues/17141 Signed-off-by: Daniel <845765@qq.com> --- app/electron/main.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/electron/main.js b/app/electron/main.js index 44b27420b..6090d5dbb 100644 --- a/app/electron/main.js +++ b/app/electron/main.js @@ -63,9 +63,8 @@ if (!app.requestSingleInstanceLock()) { } if (process.platform === "linux") { - // Linux 平台回退到 x11/XWayland 以解决某些系统上无法输入中文的问题 - // 如果需要使用原生 Wayland 可以通过启动参数 --ozone-platform=wayland 进行覆盖 - app.commandLine.appendSwitch('ozone-platform', 'x11'); + app.commandLine.appendSwitch("enable-wayland-ime"); + app.commandLine.appendSwitch("wayland-text-input-version", "3"); } app.setAsDefaultProtocolClient("siyuan"); From 6feb2bc8ec2dc2d58d62faad68caeec705dbf62d Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Fri, 6 Mar 2026 16:41:03 +0800 Subject: [PATCH 3/8] :lock: https://github.com/siyuan-note/siyuan/security/advisories/GHSA-2h2p-mvfx-868w Signed-off-by: Daniel <845765@qq.com> --- kernel/server/serve.go | 5 +++++ kernel/util/path.go | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/kernel/server/serve.go b/kernel/server/serve.go index 94897546f..81a217e23 100644 --- a/kernel/server/serve.go +++ b/kernel/server/serve.go @@ -318,6 +318,11 @@ func serveExport(ginServer *gin.Engine) { } fullPath := filepath.Join(exportBaseDir, decodedPath) + if util.IsSensitivePath(fullPath) { + logging.LogErrorf("refuse to export sensitive file [%s]", c.Request.URL.Path) + c.Status(http.StatusForbidden) + return + } fileInfo, err := os.Stat(fullPath) if os.IsNotExist(err) { diff --git a/kernel/util/path.go b/kernel/util/path.go index c60d1eec1..6e45255bf 100644 --- a/kernel/util/path.go +++ b/kernel/util/path.go @@ -391,6 +391,12 @@ func IsSensitivePath(p string) bool { } } + // 工作空间/conf 目录(小写比较) + workspaceConfPrefix := strings.ToLower(filepath.Join(WorkspaceDir, "conf")) + if strings.HasPrefix(pp, workspaceConfPrefix) { + return true + } + homePrefixes := []string{ strings.ToLower(filepath.Join(HomeDir, ".ssh")), strings.ToLower(filepath.Join(HomeDir, ".config")), From 013306f94937e3713349526cc4f31204d6be69ea Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Fri, 6 Mar 2026 17:06:13 +0800 Subject: [PATCH 4/8] :art: https://github.com/siyuan-note/siyuan/issues/17148 Signed-off-by: Daniel <845765@qq.com> --- app/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/index.ts b/app/src/index.ts index c28901f47..1091a0af2 100644 --- a/app/src/index.ts +++ b/app/src/index.ts @@ -192,7 +192,7 @@ export class App { openFileById({app: this, id: data.data.id, action: [Constants.CB_GET_FOCUS]}); break; case "exit": - if (isBrowser()) { + if (isBrowser() && !isInMobileApp()) { window.location.href = "about:blank"; } } From d141c2870026c35813e8f23db50128fd89259d46 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Fri, 6 Mar 2026 17:16:53 +0800 Subject: [PATCH 5/8] :bug: The API `insertBlock` was not inserting completely via nextID https://github.com/siyuan-note/siyuan/issues/17149 Signed-off-by: Daniel <845765@qq.com> --- kernel/model/transaction.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/kernel/model/transaction.go b/kernel/model/transaction.go index a94c37be4..eb16f7ab2 100644 --- a/kernel/model/transaction.go +++ b/kernel/model/transaction.go @@ -1237,6 +1237,9 @@ func (tx *Transaction) doInsert0(operation *Operation, tree *parse.Tree) (ret *T insertedNode = insertedNode.FirstChild } node.InsertBefore(insertedNode) + for _, remain := range remains { + node.InsertBefore(remain) + } } else if "" != previousID { node = treenode.GetNodeInTree(tree, previousID) if nil == node { From f4b380d62e27aec8c0802529741fc2290400ee03 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 17:40:30 +0800 Subject: [PATCH 6/8] :arrow_up: Bump immutable from 5.1.4 to 5.1.5 in /app (#17139) Bumps [immutable](https://github.com/immutable-js/immutable-js) from 5.1.4 to 5.1.5. - [Release notes](https://github.com/immutable-js/immutable-js/releases) - [Changelog](https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/immutable-js/immutable-js/compare/v5.1.4...v5.1.5) --- updated-dependencies: - dependency-name: immutable dependency-version: 5.1.5 dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- app/pnpm-lock.yaml | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/app/pnpm-lock.yaml b/app/pnpm-lock.yaml index 3cb32bd11..e507a629d 100644 --- a/app/pnpm-lock.yaml +++ b/app/pnpm-lock.yaml @@ -1415,6 +1415,10 @@ packages: resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==} engines: {node: '>=14.14'} + fs-extra@11.3.4: + resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==} + engines: {node: '>=14.14'} + fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -1465,16 +1469,17 @@ packages: glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} - deprecated: Glob versions prior to v9 are no longer supported + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me global-agent@3.0.0: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} @@ -2491,6 +2496,7 @@ packages: tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me temp-file@3.4.0: resolution: {integrity: sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==} @@ -2849,7 +2855,7 @@ snapshots: dependencies: cross-dirname: 0.1.0 debug: 4.4.3 - fs-extra: 11.3.3 + fs-extra: 11.3.4 minimist: 1.2.8 postject: 1.0.0-alpha.6 transitivePeerDependencies: @@ -4221,6 +4227,13 @@ snapshots: jsonfile: 6.2.0 universalify: 2.0.1 + fs-extra@11.3.4: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + optional: true + fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 From 14c35bfa0c87d335683d93bae5a5f6b30522e690 Mon Sep 17 00:00:00 2001 From: Jeffrey Chen <78434827+TCOTC@users.noreply.github.com> Date: Fri, 6 Mar 2026 17:44:20 +0800 Subject: [PATCH 7/8] :art: Add the code-block class name when rendering code blocks in the market README (#17145) --- kernel/api/bazaar.go | 2 +- kernel/bazaar/icon.go | 2 +- kernel/bazaar/plugin.go | 2 +- kernel/bazaar/readme.go | 200 ++++++++++++++++++++------------------ kernel/bazaar/template.go | 2 +- kernel/bazaar/theme.go | 2 +- kernel/bazaar/widget.go | 2 +- kernel/model/bazaar.go | 6 +- 8 files changed, 116 insertions(+), 102 deletions(-) diff --git a/kernel/api/bazaar.go b/kernel/api/bazaar.go index 26fbc9872..df965b415 100644 --- a/kernel/api/bazaar.go +++ b/kernel/api/bazaar.go @@ -95,7 +95,7 @@ func getBazaarPackageREADME(c *gin.Context) { return } ret.Data = map[string]interface{}{ - "html": model.GetPackageREADME(repoURL, repoHash, pkgType), + "html": model.GetBazaarPackageREADME(c.Request.Context(), repoURL, repoHash, pkgType), } } diff --git a/kernel/bazaar/icon.go b/kernel/bazaar/icon.go index cc5191e5c..a6d7549d3 100644 --- a/kernel/bazaar/icon.go +++ b/kernel/bazaar/icon.go @@ -153,7 +153,7 @@ func InstalledIcons() (ret []*Icon) { packageInstallSizeCache.SetDefault(icon.RepoURL, is) } icon.HInstallSize = humanize.BytesCustomCeil(uint64(icon.InstallSize), 2) - icon.PreferredReadme = loadInstalledReadme(installPath, "/appearance/icons/"+dirName+"/", icon.Readme) + icon.PreferredReadme = getInstalledPackageREADME(installPath, "/appearance/icons/"+dirName+"/", icon.Readme) icon.Outdated = isOutdatedIcon(icon, bazaarIcons) ret = append(ret, icon) } diff --git a/kernel/bazaar/plugin.go b/kernel/bazaar/plugin.go index 8d159cb0a..190444f69 100644 --- a/kernel/bazaar/plugin.go +++ b/kernel/bazaar/plugin.go @@ -189,7 +189,7 @@ func InstalledPlugins(frontend string) (ret []*Plugin) { packageInstallSizeCache.SetDefault(plugin.RepoURL, is) } plugin.HInstallSize = humanize.BytesCustomCeil(uint64(plugin.InstallSize), 2) - plugin.PreferredReadme = loadInstalledReadme(installPath, "/plugins/"+dirName+"/", plugin.Readme) + plugin.PreferredReadme = getInstalledPackageREADME(installPath, "/plugins/"+dirName+"/", plugin.Readme) plugin.Outdated = isOutdatedPlugin(plugin, bazaarPlugins) plugin.Incompatible = isIncompatiblePlugin(plugin, frontend) ret = append(ret, plugin) diff --git a/kernel/bazaar/readme.go b/kernel/bazaar/readme.go index ff3a350cb..c68c8f8d4 100644 --- a/kernel/bazaar/readme.go +++ b/kernel/bazaar/readme.go @@ -18,26 +18,45 @@ package bazaar import ( "bytes" + "context" "fmt" "os" "path/filepath" "strings" + "github.com/88250/gulu" "github.com/88250/lute" + "github.com/88250/lute/ast" + "github.com/88250/lute/parse" "github.com/siyuan-note/logging" "github.com/siyuan-note/siyuan/kernel/util" textUnicode "golang.org/x/text/encoding/unicode" "golang.org/x/text/transform" ) -func GetPackageREADME(repoURL, repoHash, packageType string) (ret string) { +// getReadmeFileCandidates 根据包的 README 配置返回去重的按优先级排序的 README 候选文件名列表:当前语言首选、default、README.md。 +func getReadmeFileCandidates(readme LocaleStrings) []string { + preferred := getPreferredReadme(readme) + defaultName := "README.md" + if v := strings.TrimSpace(readme["default"]); v != "" { + defaultName = v + } + return gulu.Str.RemoveDuplicatedElem([]string{preferred, defaultName, "README.md"}) +} + +// GetBazaarPackageREADME 获取集市包的在线 README。 +func GetBazaarPackageREADME(ctx context.Context, repoURL, repoHash, packageType string) (ret string) { repoURLHash := repoURL + "@" + repoHash stageIndexLock.RLock() stageIndex := cachedStageIndex[packageType] stageIndexLock.RUnlock() - if nil == stageIndex { - return + if stageIndex == nil { + var err error + stageIndex, err = getStageIndex(ctx, packageType) + if err != nil { + return + } } url := strings.TrimPrefix(repoURLHash, "https://github.com/") @@ -48,113 +67,106 @@ func GetPackageREADME(repoURL, repoHash, packageType string) (ret string) { break } } - if nil == repo || nil == repo.Package { + if repo == nil || repo.Package == nil { return } - readme := getPreferredReadme(repo.Package.Readme) - - data, err := downloadPackage(repoURLHash+"/"+readme, false, "") - if err != nil { - ret = fmt.Sprintf("Load bazaar package's preferred README(%s) failed: %s", readme, err.Error()) - // 回退到 Default README - var defaultReadme string - if len(repo.Package.Readme) > 0 { - defaultReadme = repo.Package.Readme["default"] - } - if "" == strings.TrimSpace(defaultReadme) { - defaultReadme = "README.md" - } - if readme != defaultReadme { - data, err = downloadPackage(repoURLHash+"/"+defaultReadme, false, "") - if err != nil { - ret += fmt.Sprintf("
Load bazaar package's default README(%s) failed: %s", defaultReadme, err.Error()) - } - } - // 回退到 README.md - if err != nil && readme != "README.md" && defaultReadme != "README.md" { - data, err = downloadPackage(repoURLHash+"/README.md", false, "") - if err != nil { - ret += fmt.Sprintf("
Load bazaar package's README.md failed: %s", err.Error()) - return - } - } else if err != nil { - return + candidates := getReadmeFileCandidates(repo.Package.Readme) + var data []byte + var loadErr error + var errMsgs []string + for _, name := range candidates { + data, loadErr = downloadPackage(repoURLHash+"/"+name, false, "") + if loadErr == nil { + break } + errMsgs = append(errMsgs, fmt.Sprintf("Load bazaar package's README(%s) failed: %s", name, loadErr.Error())) } - - if 2 < len(data) { - if 255 == data[0] && 254 == data[1] { - data, _, err = transform.Bytes(textUnicode.UTF16(textUnicode.LittleEndian, textUnicode.ExpectBOM).NewDecoder(), data) - } else if 254 == data[0] && 255 == data[1] { - data, _, err = transform.Bytes(textUnicode.UTF16(textUnicode.BigEndian, textUnicode.ExpectBOM).NewDecoder(), data) - } - } - - ret, err = renderREADME(repoURL, data) - return -} - -func loadInstalledReadme(installPath, basePath string, readme LocaleStrings) (ret string) { - readmeFilename := getPreferredReadme(readme) - readmeData, readErr := os.ReadFile(filepath.Join(installPath, readmeFilename)) - if nil == readErr { - ret, _ = renderLocalREADME(basePath, readmeData) + if loadErr != nil { + ret = strings.Join(errMsgs, "
") return } - logging.LogWarnf("read installed %s failed: %s", readmeFilename, readErr) - ret = fmt.Sprintf("File %s not found", readmeFilename) - // 回退到 Default README - var defaultReadme string - if len(readme) > 0 { - defaultReadme = strings.TrimSpace(readme["default"]) - } - if "" == defaultReadme { - defaultReadme = "README.md" - } - if readmeFilename != defaultReadme { - readmeData, readErr = os.ReadFile(filepath.Join(installPath, defaultReadme)) - if nil == readErr { - ret, _ = renderLocalREADME(basePath, readmeData) - return + if len(data) > 2 { + var decoded []byte + var err error + if data[0] == 0xFF && data[1] == 0xFE { + decoded, _, err = transform.Bytes(textUnicode.UTF16(textUnicode.LittleEndian, textUnicode.ExpectBOM).NewDecoder(), data) + } else if data[0] == 0xFE && data[1] == 0xFF { + decoded, _, err = transform.Bytes(textUnicode.UTF16(textUnicode.BigEndian, textUnicode.ExpectBOM).NewDecoder(), data) } - logging.LogWarnf("read installed %s failed: %s", defaultReadme, readErr) - ret += fmt.Sprintf("
File %s not found", defaultReadme) - } - // 回退到 README.md - if nil != readErr && readmeFilename != "README.md" && defaultReadme != "README.md" { - readmeData, readErr = os.ReadFile(filepath.Join(installPath, "README.md")) - if nil == readErr { - ret, _ = renderLocalREADME(basePath, readmeData) - return + if decoded != nil && err == nil { + data = decoded } - logging.LogWarnf("read installed README.md failed: %s", readErr) - ret += "
File README.md not found" } - return -} -func renderREADME(repoURL string, mdData []byte) (ret string, err error) { - mdData = bytes.TrimPrefix(mdData, []byte("\xef\xbb\xbf")) - luteEngine := lute.New() - luteEngine.SetSoftBreak2HardBreak(false) - luteEngine.SetCodeSyntaxHighlight(false) linkBase := "https://cdn.jsdelivr.net/gh/" + strings.TrimPrefix(repoURL, "https://github.com/") + ret = renderPackageREADME(linkBase, data) + return +} + +// getInstalledPackageREADME 获取集市包的本地 README。 +func getInstalledPackageREADME(installPath, linkBase string, readme LocaleStrings) (ret string) { + candidates := getReadmeFileCandidates(readme) + var errMsgs []string + for _, name := range candidates { + readmeData, readErr := os.ReadFile(filepath.Join(installPath, name)) + if readErr == nil { + ret = renderPackageREADME(linkBase, readmeData) + return + } + logging.LogWarnf("read installed %s failed: %s", name, readErr) + errMsgs = append(errMsgs, fmt.Sprintf("File [%s] not found", name)) + } + ret = strings.Join(errMsgs, "
") + return +} + +// renderPackageREADME 渲染 README Markdown 为 HTML。 +func renderPackageREADME(linkBase string, mdData []byte) (ret string) { + mdData = bytes.TrimPrefix(mdData, []byte("\xef\xbb\xbf")) // 移除文件开头的 BOM + luteEngine := lute.New() + luteEngine.SetSoftBreak2HardBreak(false) + luteEngine.SetCodeSyntaxHighlight(false) luteEngine.SetLinkBase(linkBase) - ret = luteEngine.Md2HTML(string(mdData)) + + tree := parse.Parse("", mdData, luteEngine.ParseOptions) + normalizeNodesIAL(tree) + ret = luteEngine.Tree2HTML(tree, luteEngine.RenderOptions, luteEngine.ParseOptions) ret = util.LinkTarget(ret, linkBase) return } -func renderLocalREADME(basePath string, mdData []byte) (ret string, err error) { - mdData = bytes.TrimPrefix(mdData, []byte("\xef\xbb\xbf")) - luteEngine := lute.New() - luteEngine.SetSoftBreak2HardBreak(false) - luteEngine.SetCodeSyntaxHighlight(false) - linkBase := basePath - luteEngine.SetLinkBase(linkBase) - ret = luteEngine.Md2HTML(string(mdData)) - ret = util.LinkTarget(ret, linkBase) - return +func normalizeNodesIAL(tree *parse.Tree) { + if tree == nil || tree.Root == nil { + return + } + + ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { + if !entering { + return ast.WalkContinue + } + if n.Type == ast.NodeCodeBlock { + // 代码块添加 code-block 类名以修正样式。 + n.KramdownIAL = addClassToKramdownIAL(n.KramdownIAL, "code-block") + } + return ast.WalkContinue + }) +} + +func addClassToKramdownIAL(ial [][]string, class string) [][]string { + for i, attr := range ial { + if len(attr) < 2 || attr[0] != "class" { + continue + } + for _, item := range strings.Fields(attr[1]) { + if item == class { + return ial + } + } + attr[1] = strings.TrimSpace(attr[1] + " " + class) + ial[i] = attr + return ial + } + return append(ial, []string{"class", class}) } diff --git a/kernel/bazaar/template.go b/kernel/bazaar/template.go index 07cfd3155..dfab139a8 100644 --- a/kernel/bazaar/template.go +++ b/kernel/bazaar/template.go @@ -154,7 +154,7 @@ func InstalledTemplates() (ret []*Template) { packageInstallSizeCache.SetDefault(template.RepoURL, is) } template.HInstallSize = humanize.BytesCustomCeil(uint64(template.InstallSize), 2) - template.PreferredReadme = loadInstalledReadme(installPath, "/templates/"+dirName+"/", template.Readme) + template.PreferredReadme = getInstalledPackageREADME(installPath, "/templates/"+dirName+"/", template.Readme) template.Outdated = isOutdatedTemplate(template, bazaarTemplates) ret = append(ret, template) } diff --git a/kernel/bazaar/theme.go b/kernel/bazaar/theme.go index efcc38bd8..a2e2f76fd 100644 --- a/kernel/bazaar/theme.go +++ b/kernel/bazaar/theme.go @@ -156,7 +156,7 @@ func InstalledThemes() (ret []*Theme) { packageInstallSizeCache.SetDefault(theme.RepoURL, is) } theme.HInstallSize = humanize.BytesCustomCeil(uint64(theme.InstallSize), 2) - theme.PreferredReadme = loadInstalledReadme(installPath, "/appearance/themes/"+dirName+"/", theme.Readme) + theme.PreferredReadme = getInstalledPackageREADME(installPath, "/appearance/themes/"+dirName+"/", theme.Readme) theme.Outdated = isOutdatedTheme(theme, bazaarThemes) ret = append(ret, theme) } diff --git a/kernel/bazaar/widget.go b/kernel/bazaar/widget.go index 405f5c1f5..8009a14d8 100644 --- a/kernel/bazaar/widget.go +++ b/kernel/bazaar/widget.go @@ -151,7 +151,7 @@ func InstalledWidgets() (ret []*Widget) { packageInstallSizeCache.SetDefault(widget.RepoURL, is) } widget.HInstallSize = humanize.BytesCustomCeil(uint64(widget.InstallSize), 2) - widget.PreferredReadme = loadInstalledReadme(installPath, "/widgets/"+dirName+"/", widget.Readme) + widget.PreferredReadme = getInstalledPackageREADME(installPath, "/widgets/"+dirName+"/", widget.Readme) widget.Outdated = isOutdatedWidget(widget, bazaarWidgets) ret = append(ret, widget) } diff --git a/kernel/model/bazaar.go b/kernel/model/bazaar.go index 7eaf4e3ae..a9f3bf488 100644 --- a/kernel/model/bazaar.go +++ b/kernel/model/bazaar.go @@ -17,6 +17,7 @@ package model import ( + "context" "errors" "fmt" "path" @@ -192,8 +193,9 @@ func UpdatedPackages(frontend string) (plugins []*bazaar.Plugin, widgets []*baza return } -func GetPackageREADME(repoURL, repoHash, packageType string) (ret string) { - ret = bazaar.GetPackageREADME(repoURL, repoHash, packageType) +// GetBazaarPackageREADME 获取集市包的在线 README。 +func GetBazaarPackageREADME(ctx context.Context, repoURL, repoHash, packageType string) (ret string) { + ret = bazaar.GetBazaarPackageREADME(ctx, repoURL, repoHash, packageType) return } From 4abcb0d81147e8177c7b310bbd0ef108e8a43faf Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Fri, 6 Mar 2026 17:56:00 +0800 Subject: [PATCH 8/8] :art: Clean code Signed-off-by: Daniel <845765@qq.com> --- kernel/model/conf.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/model/conf.go b/kernel/model/conf.go index 16e015fa3..c82de59a6 100644 --- a/kernel/model/conf.go +++ b/kernel/model/conf.go @@ -807,7 +807,9 @@ func Close(force, setCurrentWorkspace bool, execInstallPkg int) (exitCode int) { time.Sleep(30 * time.Second) } } + closeSyncWebSocket() + go func() { time.Sleep(500 * time.Millisecond) logging.LogInfof("exited kernel")