Something went wrong. Try again.
A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288// 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 <https://www.gnu.org/licenses/>.
package model
import ( "fmt" "os" "path/filepath" "strings" "sync" "time"
"github.com/88250/gulu" "github.com/fsnotify/fsnotify" "github.com/siyuan-note/filelock" "github.com/siyuan-note/logging" "github.com/siyuan-note/siyuan/kernel/bazaar" "github.com/siyuan-note/siyuan/kernel/conf" "github.com/siyuan-note/siyuan/kernel/util")
func InitAppearance() { util.SetBootDetails("Initializing appearance...") if err := os.Mkdir(util.AppearancePath, 0755); err != nil && !os.IsExist(err) { logging.LogErrorf("create appearance folder [%s] failed: %s", util.AppearancePath, err) util.ReportFileSysFatalError(err) return }
unloadThemes() from := filepath.Join(util.WorkingDir, "appearance") if err := filelock.Copy(from, util.AppearancePath); err != nil { logging.LogErrorf("copy appearance resources from [%s] to [%s] failed: %s", from, util.AppearancePath, err) util.ReportFileSysFatalError(err) return } loadThemes()
if !containTheme(Conf.Appearance.ThemeDark, Conf.Appearance.DarkThemes) { Conf.Appearance.ThemeDark = "midnight" Conf.Appearance.ThemeJS = false } if !containTheme(Conf.Appearance.ThemeLight, Conf.Appearance.LightThemes) { Conf.Appearance.ThemeLight = "daylight" Conf.Appearance.ThemeJS = false }
loadIcons() if !gulu.Str.Contains(Conf.Appearance.Icon, Conf.Appearance.Icons) { Conf.Appearance.Icon = "material" }
Conf.Save()
util.InitEmojiChars()}
func containTheme(name string, themes []*conf.AppearanceTheme) bool { for _, t := range themes { if t.Name == name { return true } } return false}
var themeWatchers = sync.Map{} // [string]*fsnotify.Watcher{}
func closeThemeWatchers() { themeWatchers.Range(func(key, value interface{}) bool { if err := value.(*fsnotify.Watcher).Close(); err != nil { logging.LogErrorf("close file watcher failed: %s", err) } return true })}
func unloadThemes() { if !util.IsPathRegularDirOrSymlinkDir(util.ThemesPath) { return }
themeDirs, err := os.ReadDir(util.ThemesPath) if err != nil { logging.LogErrorf("read appearance themes folder failed: %s", err) return }
for _, themeDir := range themeDirs { if !util.IsDirRegularOrSymlink(themeDir) { continue } unwatchTheme(filepath.Join(util.ThemesPath, themeDir.Name())) }}
func loadThemes() { themeDirs, err := os.ReadDir(util.ThemesPath) if err != nil { logging.LogErrorf("read appearance themes folder failed: %s", err) util.ReportFileSysFatalError(err) return }
Conf.Appearance.DarkThemes = nil Conf.Appearance.LightThemes = nil var daylightTheme, midnightTheme *conf.AppearanceTheme for _, themeDir := range themeDirs { if !util.IsDirRegularOrSymlink(themeDir) { continue } name := themeDir.Name() themeConf, parseErr := bazaar.ThemeJSON(name) if nil != parseErr || nil == themeConf { continue }
modes := themeConf.Modes for _, mode := range modes { t := &conf.AppearanceTheme{Name: name} if "midnight" == name || "daylight" == name { t.Label = name + Conf.Language(281) } else { t.Label = name if len(themeConf.DisplayName) > 0 { v := strings.TrimSpace(themeConf.DisplayName[util.Lang]) if "" == v { v = strings.TrimSpace(themeConf.DisplayName["default"]) } if "" != v && name != v { t.Label = v + " (" + name + ")" } } }
if "midnight" == name { midnightTheme = t continue } else if "daylight" == name { daylightTheme = t continue }
if "dark" == mode { Conf.Appearance.DarkThemes = append(Conf.Appearance.DarkThemes, t) } else if "light" == mode { Conf.Appearance.LightThemes = append(Conf.Appearance.LightThemes, t) } }
if 0 == Conf.Appearance.Mode { if Conf.Appearance.ThemeLight == name { Conf.Appearance.ThemeVer = themeConf.Version Conf.Appearance.ThemeJS = gulu.File.IsExist(filepath.Join(util.ThemesPath, name, "theme.js")) } } else { if Conf.Appearance.ThemeDark == name { Conf.Appearance.ThemeVer = themeConf.Version Conf.Appearance.ThemeJS = gulu.File.IsExist(filepath.Join(util.ThemesPath, name, "theme.js")) } }
go watchTheme(filepath.Join(util.ThemesPath, name)) }
Conf.Appearance.LightThemes = append([]*conf.AppearanceTheme{daylightTheme}, Conf.Appearance.LightThemes...) Conf.Appearance.DarkThemes = append([]*conf.AppearanceTheme{midnightTheme}, Conf.Appearance.DarkThemes...)}
func loadIcons() { iconDirs, err := os.ReadDir(util.IconsPath) if err != nil { logging.LogErrorf("read appearance icons folder failed: %s", err) util.ReportFileSysFatalError(err) return }
Conf.Appearance.Icons = nil for _, iconDir := range iconDirs { if !util.IsDirRegularOrSymlink(iconDir) { continue } name := iconDir.Name() iconConf, err := bazaar.IconJSON(name) if err != nil || nil == iconConf { continue } Conf.Appearance.Icons = append(Conf.Appearance.Icons, name) if Conf.Appearance.Icon == name { Conf.Appearance.IconVer = iconConf.Version } }}
func ReloadIcon() { loadIcons()}
func unwatchTheme(folder string) { val, _ := themeWatchers.Load(folder) if nil != val { themeWatcher := val.(*fsnotify.Watcher) themeWatcher.Close() }}
func watchTheme(folder string) { val, _ := themeWatchers.Load(folder) var themeWatcher *fsnotify.Watcher if nil != val { themeWatcher = val.(*fsnotify.Watcher) themeWatcher.Close() }
var err error if themeWatcher, err = fsnotify.NewWatcher(); err != nil { logging.LogErrorf("add theme file watcher for folder [%s] failed: %s", folder, err) return } themeWatchers.Store(folder, themeWatcher)
done := make(chan bool) go func() { for { select { case event, ok := <-themeWatcher.Events: if !ok { return }
//logging.LogInfof(event.String()) if event.Op&fsnotify.Write == fsnotify.Write && (strings.HasSuffix(event.Name, "theme.css")) { var themeName string if themeName = isCurrentUseTheme(event.Name); "" == themeName { break }
if strings.HasSuffix(event.Name, "theme.css") { util.BroadcastByType("main", "refreshtheme", 0, "", map[string]interface{}{ "theme": "/appearance/themes/" + themeName + "/theme.css?" + fmt.Sprintf("%d", time.Now().Unix()), }) break } } case err, ok := <-themeWatcher.Errors: if !ok { return } logging.LogErrorf("watch theme file failed: %s", err) } } }()
//logging.LogInfof("add file watcher [%s]", folder) if err := themeWatcher.Add(folder); err != nil { logging.LogErrorf("add theme files watcher for folder [%s] failed: %s", folder, err) } <-done}
func isCurrentUseTheme(themePath string) string { themeName := filepath.Base(filepath.Dir(themePath)) if 0 == Conf.Appearance.Mode { // 明亮 if Conf.Appearance.ThemeLight == themeName { return themeName } } else if 1 == Conf.Appearance.Mode { // 暗黑 if Conf.Appearance.ThemeDark == themeName { return themeName } } return ""}