diff --git a/kernel/api/bazaar.go b/kernel/api/bazaar.go index c714c4b7d..22aa56d8b 100644 --- a/kernel/api/bazaar.go +++ b/kernel/api/bazaar.go @@ -21,6 +21,7 @@ import ( "github.com/88250/gulu" "github.com/gin-gonic/gin" + "github.com/siyuan-note/siyuan/kernel/bazaar" "github.com/siyuan-note/siyuan/kernel/model" "github.com/siyuan-note/siyuan/kernel/util" ) @@ -221,6 +222,15 @@ func getBazaarTheme(c *gin.Context) { } } +func getInstalledTheme(c *gin.Context) { + ret := gulu.Ret.NewResult() + defer c.JSON(http.StatusOK, ret) + + ret.Data = map[string]interface{}{ + "packages": bazaar.InstalledThemes(), + } +} + func installBazaarTheme(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret) diff --git a/kernel/api/router.go b/kernel/api/router.go index a38033eed..6985dce66 100644 --- a/kernel/api/router.go +++ b/kernel/api/router.go @@ -243,6 +243,7 @@ func ServeAPI(ginServer *gin.Engine) { ginServer.Handle("POST", "/api/bazaar/installBazaarTemplate", model.CheckAuth, installBazaarTemplate) ginServer.Handle("POST", "/api/bazaar/uninstallBazaarTemplate", model.CheckAuth, uninstallBazaarTemplate) ginServer.Handle("POST", "/api/bazaar/getBazaarTheme", model.CheckAuth, getBazaarTheme) + ginServer.Handle("POST", "/api/bazaar/getInstallTheme", model.CheckAuth, getInstalledTheme) ginServer.Handle("POST", "/api/bazaar/installBazaarTheme", model.CheckAuth, installBazaarTheme) ginServer.Handle("POST", "/api/bazaar/uninstallBazaarTheme", model.CheckAuth, uninstallBazaarTheme) ginServer.Handle("POST", "/api/bazaar/getBazaarPackageREAME", model.CheckAuth, getBazaarPackageREAME) diff --git a/kernel/bazaar/theme.go b/kernel/bazaar/theme.go index 23a2a2ebf..77ccaec94 100644 --- a/kernel/bazaar/theme.go +++ b/kernel/bazaar/theme.go @@ -19,10 +19,12 @@ package bazaar import ( "errors" "os" + "path/filepath" "sort" "strings" "sync" + "github.com/88250/gulu" "github.com/dustin/go-humanize" ants "github.com/panjf2000/ants/v2" "github.com/siyuan-note/httpclient" @@ -129,6 +131,73 @@ func Themes() (ret []*Theme) { return } +func InstalledThemes() (ret []*Theme) { + dir, err := os.Open(util.ThemesPath) + if nil != err { + logging.LogWarnf("open appearance themes folder [%s] failed: %s", util.ThemesPath, err) + return + } + themeDirs, err := dir.Readdir(-1) + if nil != err { + logging.LogWarnf("read appearance themes folder failed: %s", err) + return + } + dir.Close() + + for _, themeDir := range themeDirs { + if !themeDir.IsDir() { + continue + } + dirName := themeDir.Name() + if isBuiltInTheme(dirName) { + continue + } + + themeConf, parseErr := ThemeJSON(dirName) + if nil != parseErr || nil == themeConf { + continue + } + + theme := &Theme{} + theme.Name = themeConf["name"].(string) + theme.Author = themeConf["author"].(string) + theme.URL = themeConf["url"].(string) + theme.Version = themeConf["version"].(string) + theme.Modes = make([]string, 0, len(themeConf["modes"].([]interface{}))) + theme.RepoURL = theme.URL + theme.PreviewURL = "/appearance/themes/" + dirName + "/preview.png" + theme.PreviewURLThumb = "/appearance/themes/" + dirName + "/preview.png" + theme.Updated = themeDir.ModTime().Format("2006-01-02 15:04:05") + theme.Size = themeDir.Size() + theme.HSize = humanize.Bytes(uint64(theme.Size)) + theme.HUpdated = formatUpdated(theme.Updated) + readme, readErr := os.ReadFile(filepath.Join(util.ThemesPath, dirName, "README.md")) + if nil != readErr { + logging.LogWarnf("read install theme README.md failed: %s", readErr) + continue + } + theme.README = gulu.Str.FromBytes(readme) + + if !existThemes(ret, theme) { + ret = append(ret, theme) + } + } + return +} + +func isBuiltInTheme(dirName string) bool { + return "daylight" == dirName || "midnight" == dirName +} + +func existThemes(themes []*Theme, theme *Theme) bool { + for _, t := range themes { + if t.Name == theme.Name { + return true + } + } + return false +} + func InstallTheme(repoURL, repoHash, installPath string, systemID string) error { repoURLHash := repoURL + "@" + repoHash data, err := downloadPackage(repoURLHash, true, systemID) @@ -146,3 +215,25 @@ func UninstallTheme(installPath string) error { //logging.Logger.Infof("uninstalled theme [%s]", installPath) return nil } + +func ThemeJSON(themeName string) (ret map[string]interface{}, err error) { + p := filepath.Join(util.ThemesPath, themeName, "theme.json") + if !gulu.File.IsExist(p) { + err = os.ErrNotExist + return + } + data, err := os.ReadFile(p) + if nil != err { + logging.LogErrorf("read theme.json [%s] failed: %s", p, err) + return + } + if err = gulu.JSON.UnmarshalJSON(data, &ret); nil != err { + logging.LogErrorf("parse theme.json [%s] failed: %s", p, err) + return + } + if 5 > len(ret) { + logging.LogWarnf("invalid theme.json [%s]", p) + return nil, errors.New("invalid theme.json") + } + return +} diff --git a/kernel/model/appearance.go b/kernel/model/appearance.go index 31596194e..e6e9ebdf4 100644 --- a/kernel/model/appearance.go +++ b/kernel/model/appearance.go @@ -28,6 +28,7 @@ import ( "github.com/88250/gulu" "github.com/fsnotify/fsnotify" "github.com/siyuan-note/logging" + "github.com/siyuan-note/siyuan/kernel/bazaar" "github.com/siyuan-note/siyuan/kernel/util" ) @@ -115,8 +116,8 @@ func loadThemes() { continue } name := themeDir.Name() - themeConf, err := themeJSON(name) - if nil != err || nil == themeConf { + themeConf, parseErr := bazaar.ThemeJSON(name) + if nil != parseErr || nil == themeConf { continue } @@ -145,28 +146,6 @@ func loadThemes() { } } -func themeJSON(themeName string) (ret map[string]interface{}, err error) { - p := filepath.Join(util.ThemesPath, themeName, "theme.json") - if !gulu.File.IsExist(p) { - err = os.ErrNotExist - return - } - data, err := os.ReadFile(p) - if nil != err { - logging.LogErrorf("read theme.json [%s] failed: %s", p, err) - return - } - if err = gulu.JSON.UnmarshalJSON(data, &ret); nil != err { - logging.LogErrorf("parse theme.json [%s] failed: %s", p, err) - return - } - if 5 > len(ret) { - logging.LogWarnf("invalid theme.json [%s]", p) - return nil, errors.New("invalid theme.json") - } - return -} - func iconJSON(iconName string) (ret map[string]interface{}, err error) { p := filepath.Join(util.IconsPath, iconName, "icon.json") if !gulu.File.IsExist(p) { diff --git a/kernel/model/bazzar.go b/kernel/model/bazzar.go index 5dded0c90..0cd39280d 100644 --- a/kernel/model/bazzar.go +++ b/kernel/model/bazzar.go @@ -128,7 +128,7 @@ func BazaarThemes() (ret []*bazaar.Theme) { for _, theme := range ret { if installed == theme.Name { theme.Installed = true - if themeConf, err := themeJSON(theme.Name); nil == err { + if themeConf, err := bazaar.ThemeJSON(theme.Name); nil == err { theme.Outdated = theme.Version != themeConf["version"].(string) } theme.Current = theme.Name == Conf.Appearance.ThemeDark || theme.Name == Conf.Appearance.ThemeLight