diff --git a/app/src/plugin/loader.ts b/app/src/plugin/loader.ts index 4d2995cd6..4bcb7717d 100644 --- a/app/src/plugin/loader.ts +++ b/app/src/plugin/loader.ts @@ -17,7 +17,7 @@ const runCode = (code: string, sourceURL: string) => { } export const loadPlugins = (app: App) => { - fetchPost("/api/plugin/loadPlugins", {}, response => { + fetchPost("/api/petal/loadPetals", {}, response => { let css = ""; response.data.forEach((item: { id: string, name: string, jsCode: string, cssCode: string, lang: IObject }) => { const moduleObj = {} diff --git a/kernel/api/plugin.go b/kernel/api/plugin.go new file mode 100644 index 000000000..ddcd71ca4 --- /dev/null +++ b/kernel/api/plugin.go @@ -0,0 +1,33 @@ +// 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 . + +package api + +import ( + "net/http" + + "github.com/88250/gulu" + "github.com/gin-gonic/gin" + "github.com/siyuan-note/siyuan/kernel/model" +) + +func loadPetals(c *gin.Context) { + ret := gulu.Ret.NewResult() + defer c.JSON(http.StatusOK, ret) + + petals := model.LoadPetals() + ret.Data = petals +} diff --git a/kernel/api/router.go b/kernel/api/router.go index 11f69abb7..32d33f918 100644 --- a/kernel/api/router.go +++ b/kernel/api/router.go @@ -343,4 +343,6 @@ func ServeAPI(ginServer *gin.Engine) { ginServer.Handle("POST", "/api/ai/chatGPT", model.CheckAuth, model.CheckReadonly, chatGPT) ginServer.Handle("POST", "/api/ai/chatGPTWithAction", model.CheckAuth, model.CheckReadonly, chatGPTWithAction) + + ginServer.Handle("POST", "/api/petal/loadPetals", model.CheckAuth, model.CheckReadonly, loadPetals) } diff --git a/kernel/bazaar/package.go b/kernel/bazaar/package.go index bf8ab61e2..ebd8c1b3c 100644 --- a/kernel/bazaar/package.go +++ b/kernel/bazaar/package.go @@ -38,10 +38,18 @@ import ( "golang.org/x/text/transform" ) +type Funding struct { + OpenCollective string `json:"openCollective"` + Patreon string `json:"patreon"` + GitHub string `json:"github"` + Custom []string `json:"custom"` +} + type Package struct { - Author string `json:"author"` - URL string `json:"url"` - Version string `json:"version"` + Author string `json:"author"` + URL string `json:"url"` + Version string `json:"version"` + Funding *Funding `json:"funding"` Name string `json:"name"` RepoURL string `json:"repoURL"` diff --git a/kernel/model/plugin.go b/kernel/model/plugin.go new file mode 100644 index 000000000..e777f7491 --- /dev/null +++ b/kernel/model/plugin.go @@ -0,0 +1,127 @@ +// 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 . + +package model + +import ( + "crypto/sha1" + "fmt" + "path/filepath" + + "github.com/88250/gulu" + "github.com/siyuan-note/filelock" + "github.com/siyuan-note/logging" + "github.com/siyuan-note/siyuan/kernel/bazaar" + "github.com/siyuan-note/siyuan/kernel/util" +) + +// Petal represents a plugin's management status. +type Petal struct { + ID string `json:"id"` // Plugin ID + Name string `json:"name"` // Plugin name + Enabled bool `json:"enabled"` // Whether enabled + + JS string `json:"js"` // JS code + CSS string `json:"css"` // CSS code + I18n map[string]interface{} `json:"i18n"` // i18n text +} + +func LoadPetals() (ret []*Petal) { + petalDir := filepath.Join(util.DataDir, "storage", "petal") + confPath := filepath.Join(petalDir, "petals.json") + + ret = []*Petal{} + if !gulu.File.IsExist(confPath) { + data, err := gulu.JSON.MarshalIndentJSON(ret, "", "\t") + if nil != err { + logging.LogErrorf("marshal petals failed: %s", err) + return + } + if err = filelock.WriteFile(confPath, data); nil != err { + logging.LogErrorf("write petals [%s] failed: %s", confPath, err) + return + } + return + } + + data, err := filelock.ReadFile(confPath) + if nil != err { + logging.LogErrorf("read petal file [%s] failed: %s", confPath, err) + return + } + + if err = gulu.JSON.UnmarshalJSON(data, &ret); nil != err { + logging.LogErrorf("unmarshal petals failed: %s", err) + return + } + + plugins := bazaar.InstalledPlugins() + for _, plugin := range plugins { + id := hash(plugin.URL) + petal := getPetalByID(id, ret) + if nil == petal { + continue + } + + pluginDir := filepath.Join(util.DataDir, "plugins", plugin.Name) + data, err := filelock.ReadFile(filepath.Join(pluginDir, "index.js")) + if nil != err { + logging.LogErrorf("read plugin [%s] js failed: %s", plugin.Name, err) + continue + } + petal.JS = string(data) + + cssPath := filepath.Join(pluginDir, "index.css") + if gulu.File.IsExist(cssPath) { + data, err := filelock.ReadFile(cssPath) + if nil != err { + logging.LogErrorf("read plugin [%s] css failed: %s", plugin.Name, err) + } else { + petal.CSS = string(data) + } + } + + i18nPath := filepath.Join(pluginDir, "i18n", Conf.Lang) + if gulu.File.IsExist(i18nPath) { + data, err := filelock.ReadFile(i18nPath) + if nil != err { + logging.LogErrorf("read plugin [%s] i18n failed: %s", plugin.Name, err) + } else { + petal.I18n = map[string]interface{}{} + if err = gulu.JSON.UnmarshalJSON(data, &petal.I18n); nil != err { + logging.LogErrorf("unmarshal plugin [%s] i18n failed: %s", plugin.Name, err) + } + } + } + + ret = append(ret, petal) + } + return +} + +func getPetalByID(id string, petals []*Petal) (ret *Petal) { + for _, p := range petals { + if id == p.ID { + ret = p + break + } + } + return +} + +func hash(str string) string { + return fmt.Sprintf("%x", sha1.Sum([]byte(str))) +}