siyuan/kernel/model/css.go

303 lines
7.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SiYuan - Build Your Eternal Digital Garden
// 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 <https://www.gnu.org/licenses/>.
package model
import (
"bytes"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/88250/css"
"github.com/88250/gulu"
"github.com/siyuan-note/siyuan/kernel/util"
)
var colorKeys = map[string][]string{
"colorPrimary": colorPrimary,
"colorFont": colorFont,
"colorBorder": colorBorder,
"colorScroll": colorScroll,
"colorTab": colorTab,
"colorTip": colorTip,
"colorGraph": colorGraph,
"colorInline": colorInline,
}
var colorPrimary = []string{
"--b3-theme-primary",
"--b3-theme-primary-light",
"--b3-theme-primary-lighter",
"--b3-theme-primary-lightest",
"--b3-theme-secondary",
"--b3-theme-background",
"--b3-theme-surface",
"--b3-theme-error",
}
var colorFont = []string{
"--b3-theme-on-primary",
"--b3-theme-on-secondary",
"--b3-theme-on-background",
"--b3-theme-on-surface",
"--b3-theme-on-error",
}
var colorBorder = []string{
"--b3-border-color",
}
var colorScroll = []string{
"--b3-scroll-color",
}
var colorTab = []string{
"--b3-tab-background",
}
var colorTip = []string{
"--b3-tooltips-color",
}
var colorGraph = []string{
"--b3-graph-line",
"--b3-graph-hl-point",
"--b3-graph-hl-line",
"--b3-graph-p-point",
"--b3-graph-heading-point",
"--b3-graph-math-point",
"--b3-graph-code-point",
"--b3-graph-table-point",
"--b3-graph-list-point",
"--b3-graph-todo-point",
"--b3-graph-olist-point",
"--b3-graph-listitem-point",
"--b3-graph-bq-point",
"--b3-graph-super-point",
"--b3-graph-doc-point",
"--b3-graph-tag-point",
"--b3-graph-asset-point",
"--b3-graph-line",
"--b3-graph-tag-line",
"--b3-graph-ref-line",
"--b3-graph-tag-tag-line",
"--b3-graph-asset-line",
"--b3-graph-hl-point",
"--b3-graph-hl-line",
}
var colorInline = []string{
"--b3-protyle-inline-strong-color",
"--b3-protyle-inline-em-color",
"--b3-protyle-inline-s-color",
"--b3-protyle-inline-link-color",
"--b3-protyle-inline-tag-color",
"--b3-protyle-inline-blockref-color",
"--b3-protyle-inline-mark-background",
"--b3-protyle-inline-mark-color",
}
func currentCSSValue(key string) string {
var themeName string
if 0 == Conf.Appearance.Mode {
themeName = Conf.Appearance.ThemeLight
} else {
themeName = Conf.Appearance.ThemeDark
}
themePath := filepath.Join(util.ThemesPath, themeName)
theme := filepath.Join(themePath, "theme.css")
custom := filepath.Join(themePath, "custom.css")
var data []byte
var err error
if Conf.Appearance.CustomCSS {
data, _ = os.ReadFile(custom)
}
if 1 > len(data) {
data, err = os.ReadFile(theme)
if nil != err {
util.LogErrorf("read theme css [%s] failed: %s", theme, err)
return "#ffffff"
}
}
ss := css.Parse(string(data))
rules := ss.GetCSSRuleList()
for _, rule := range rules {
for _, style := range rule.Style.Styles {
fixStyle(style)
if key == style.Property {
return style.Value.Text()
}
}
}
return ""
}
func ReadCustomCSS(themeName string) (ret map[string]map[string]string, err error) {
ret = map[string]map[string]string{}
themePath := filepath.Join(util.ThemesPath, themeName)
theme := filepath.Join(themePath, "theme.css")
custom := filepath.Join(themePath, "custom.css")
if !gulu.File.IsExist(custom) {
if err = gulu.File.CopyFile(theme, custom); nil != err {
util.LogErrorf("copy theme [%s] to [%s] failed: %s", theme, custom, err)
return
}
}
data, err := os.ReadFile(custom)
if nil != err {
util.LogErrorf("read custom css [%s] failed: %s", custom, err)
return
}
fullColorMap := map[string]string{}
ss := css.Parse(string(data))
rules := ss.GetCSSRuleList()
for _, rule := range rules {
for _, style := range rule.Style.Styles {
fixStyle(style)
fullColorMap[style.Property] = style.Value.Text()
}
}
// 补充现有主题中的样式
data, err = os.ReadFile(theme)
if nil != err {
util.LogErrorf("read theme css [%s] failed: %s", theme, err)
return
}
ss = css.Parse(string(data))
rules = ss.GetCSSRuleList()
for _, rule := range rules {
for _, style := range rule.Style.Styles {
fixStyle(style)
if _, ok := fullColorMap[style.Property]; !ok {
fullColorMap[style.Property] = style.Value.ParsedText()
}
}
}
buildColor(&ret, fullColorMap, "colorPrimary")
buildColor(&ret, fullColorMap, "colorFont")
buildColor(&ret, fullColorMap, "colorBorder")
buildColor(&ret, fullColorMap, "colorScroll")
buildColor(&ret, fullColorMap, "colorTab")
buildColor(&ret, fullColorMap, "colorTip")
buildColor(&ret, fullColorMap, "colorGraph")
buildColor(&ret, fullColorMap, "colorInline")
return
}
func buildColor(ret *map[string]map[string]string, fullColorMap map[string]string, colorMapKey string) {
colorMap := map[string]string{}
for _, colorKey := range colorKeys[colorMapKey] {
colorMap[colorKey] = fullColorMap[colorKey]
}
(*ret)[colorMapKey] = colorMap
}
func WriteCustomCSS(themeName string, cssMap map[string]interface{}) (err error) {
customCSS := map[string]string{}
for _, vMap := range cssMap {
cssKV := vMap.(map[string]interface{})
for k, v := range cssKV {
customCSS[k] = v.(string)
}
}
themePath := filepath.Join(util.ThemesPath, themeName)
custom := filepath.Join(themePath, "custom.css")
data, err := os.ReadFile(custom)
if nil != err {
util.LogErrorf("read custom css [%s] failed: %s", custom, err)
return
}
cssData := util.RemoveInvisible(string(data))
customStyleSheet := css.Parse(cssData)
buf := &bytes.Buffer{}
customRules := customStyleSheet.CssRuleList
for _, customRule := range customRules {
if css.KEYFRAMES_RULE == customRule.Type {
keyframes(customRule, buf)
continue
} else if css.STYLE_RULE != customRule.Type {
buf.WriteString(customRule.Type.Text())
buf.WriteString(customRule.Style.Text())
buf.WriteString("\n\n")
continue
}
for _, style := range customRule.Style.Styles {
fixStyle(style)
if val, ok := customCSS[style.Property]; ok {
style.Value = css.NewCSSValue(val)
delete(customCSS, style.Property)
}
}
for k, v := range customCSS {
customRule.Style.Styles = append(customRule.Style.Styles, &css.CSSStyleDeclaration{Property: k, Value: css.NewCSSValue(v)})
}
buf.WriteString(customRule.Style.Text())
buf.WriteString("\n\n")
}
if err := gulu.File.WriteFileSafer(custom, buf.Bytes(), 0644); nil != err {
util.LogErrorf("write custom css [%s] failed: %s", custom, err)
}
util.BroadcastByType("main", "refreshtheme", 0, "", map[string]interface{}{
"theme": "/appearance/themes/" + themeName + "/custom.css?" + fmt.Sprintf("%d", time.Now().Unix()),
})
return
}
func keyframes(rule *css.CSSRule, buf *bytes.Buffer) {
buf.WriteString(rule.Type.Text())
buf.WriteString(" ")
buf.WriteString(rule.Style.Selector.Text())
buf.WriteString(" {\n")
for _, r := range rule.Rules {
buf.WriteString(r.Style.Text())
buf.WriteString("\n")
}
buf.WriteString("\n\n")
}
func fixStyle(style *css.CSSStyleDeclaration) {
// css 解析库似乎有 bug这里做修正
if strings.HasPrefix(style.Property, "-") && !strings.HasPrefix(style.Property, "--") {
style.Property = "-" + style.Property
}
if strings.HasPrefix(style.Value.Text(), "- ") {
value := style.Value.Text()[2:]
style.Value = css.NewCSSValue(value)
}
}