303 lines
7.8 KiB
Go
303 lines
7.8 KiB
Go
// 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)
|
||
}
|
||
}
|