diff --git a/kernel/go.mod b/kernel/go.mod index 505ba7e06..27bcec2d9 100644 --- a/kernel/go.mod +++ b/kernel/go.mod @@ -33,6 +33,7 @@ require ( github.com/go-ole/go-ole v1.3.0 github.com/goccy/go-json v0.10.2 github.com/gofrs/flock v0.8.1 + github.com/gorilla/css v1.0.0 github.com/gorilla/websocket v1.5.1 github.com/imroc/req/v3 v3.43.5 github.com/jinzhu/copier v0.4.0 @@ -60,6 +61,7 @@ require ( github.com/spf13/cast v1.6.0 github.com/steambap/captcha v1.4.1 github.com/studio-b12/gowebdav v0.9.0 + github.com/vanng822/css v1.0.1 github.com/vmihailenco/msgpack/v5 v5.4.1 github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 github.com/xuri/excelize/v2 v2.8.1 diff --git a/kernel/go.sum b/kernel/go.sum index 2c8930aaf..b7659b4ad 100644 --- a/kernel/go.sum +++ b/kernel/go.sum @@ -171,6 +171,8 @@ github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25d github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= @@ -402,6 +404,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2 github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/vanng822/css v1.0.1 h1:10yiXc4e8NI8ldU6mSrWmSWMuyWgPr9DZ63RSlsgDw8= +github.com/vanng822/css v1.0.1/go.mod h1:tcnB1voG49QhCrwq1W0w5hhGasvOg+VQp9i9H1rCM1w= github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= diff --git a/kernel/model/export.go b/kernel/model/export.go index 8a3d4d8c7..b64add327 100644 --- a/kernel/model/export.go +++ b/kernel/model/export.go @@ -878,9 +878,14 @@ func ExportHTML(id, savePath string, pdf, image, keepFold, merge bool) (name, do luteEngine.SetFootnotes(true) luteEngine.RenderOptions.ProtyleContenteditable = false luteEngine.SetProtyleMarkNetImg(false) + // 不进行安全过滤,因为导出时需要保留所有的 HTML 标签 // 使用属性 `data-export-html` 导出时 `` 标签丢失 https://github.com/siyuan-note/siyuan/issues/6228 luteEngine.SetSanitize(false) + + // 使用实际主题样式值替换样式变量 Use real theme style value replace var in preview mode https://github.com/siyuan-note/siyuan/issues/11458 + fillThemeStyleVar(tree) + renderer := render.NewProtyleExportRenderer(tree, luteEngine.RenderOptions) dom = gulu.Str.FromBytes(renderer.Render()) return diff --git a/kernel/model/theme.go b/kernel/model/theme.go new file mode 100644 index 000000000..fdccbdb15 --- /dev/null +++ b/kernel/model/theme.go @@ -0,0 +1,127 @@ +// SiYuan - Refactor your thinking +// Copyright (c) 2020-present, b3log.org +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package model + +import ( + "bytes" + "os" + "path/filepath" + "strings" + + "github.com/88250/lute/ast" + "github.com/88250/lute/parse" + "github.com/gorilla/css/scanner" + "github.com/siyuan-note/logging" + "github.com/siyuan-note/siyuan/kernel/util" + "github.com/vanng822/css" +) + +func fillThemeStyleVar(tree *parse.Tree) { + themeStyles := getThemeStyleVar(Conf.Appearance.ThemeLight) + if 1 > len(themeStyles) { + return + } + + ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { + if !entering { + return ast.WalkContinue + } + + for _, ial := range n.KramdownIAL { + if "style" != ial[0] { + continue + } + + styleSheet := css.Parse(ial[1]) + buf := bytes.Buffer{} + for _, r := range styleSheet.GetCSSRuleList() { + styles := getStyleVarName(r.Style.Selector) + for style, name := range styles { + buf.WriteString(style) + buf.WriteString(": ") + value := themeStyles[name] + if "" == value { + // 回退为变量 + buf.WriteString("var(") + buf.WriteString(name) + buf.WriteString(")") + } else { + buf.WriteString(value) + } + buf.WriteString("; ") + } + } + if 0 < buf.Len() { + ial[1] = strings.TrimSpace(buf.String()) + } + } + return ast.WalkContinue + }) +} + +func getStyleVarName(value *css.CSSValue) (ret map[string]string) { + ret = map[string]string{} + + var start, end int + var style, name string + for i, t := range value.Tokens { + if scanner.TokenIdent == t.Type && 0 == start { + style = strings.TrimSpace(t.Value) + continue + } + + if scanner.TokenFunction == t.Type && "var(" == t.Value { + start = i + continue + } + if scanner.TokenChar == t.Type && ")" == t.Value { + end = i + + if 0 < start && 0 < end { + for _, tt := range value.Tokens[start+1 : end] { + name += tt.Value + } + name = strings.TrimSpace(name) + } + start, end = 0, 0 + ret[style] = name + style, name = "", "" + } + } + return +} + +func getThemeStyleVar(theme string) (ret map[string]string) { + ret = map[string]string{} + + data, err := os.ReadFile(filepath.Join(util.ThemesPath, theme, "theme.css")) + if nil != err { + logging.LogErrorf("read theme [%s] css file failed: %s", theme, err) + return + } + + styleSheet := css.Parse(string(data)) + for _, rule := range styleSheet.GetCSSRuleList() { + for _, style := range rule.Style.Styles { + ret[style.Property] = strings.TrimSpace(style.Value.Text()) + // 如果两个短横线开头 CSS 解析器有问题,--b3-theme-primary: #3575f0; 会被解析为 -b3-theme-primary:- #3575f0 + // 这里两种解析都放到结果中 + ret["-"+style.Property] = strings.TrimSpace(strings.TrimPrefix(style.Value.Text(), "-")) + } + } + return +}