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

This commit is contained in:
Vanessa 2026-03-06 20:23:40 +08:00
commit 7831dccd7e
15 changed files with 202 additions and 158 deletions

View file

@ -67,6 +67,36 @@ if (process.platform === "linux") {
app.commandLine.appendSwitch("wayland-text-input-version", "3");
}
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 {
firstOpen = !fs.existsSync(path.join(confDir, "workspace.json"));
if (!fs.existsSync(confDir)) {
@ -297,28 +327,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 +738,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 +1520,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);
}
}

19
app/pnpm-lock.yaml generated
View file

@ -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

View file

@ -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";
}
}

View file

@ -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),
}
}

View file

@ -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)
}

View file

@ -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)

View file

@ -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("<br>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("<br>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, "<br>")
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("<br>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 += "<br>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, "<br>")
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})
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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")

View file

@ -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 {

View file

@ -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) {

View file

@ -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")),