This commit is contained in:
Liang Ding 2022-05-26 15:18:53 +08:00
parent e650b8100c
commit f40ed985e1
No known key found for this signature in database
GPG key ID: 136F30F901A2231D
1214 changed files with 345766 additions and 9 deletions

92
kernel/api/account.go Normal file
View file

@ -0,0 +1,92 @@
// 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 api
import (
"net/http"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func useActivationcode(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
code := arg["data"].(string)
err := model.UseActivationcode(code)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
}
func checkActivationcode(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
code := arg["data"].(string)
ret.Code, ret.Msg = model.CheckActivationcode(code)
}
func deactivateUser(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
err := model.DeactivateUser()
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
}
func login(c *gin.Context) {
ret := gulu.Ret.NewResult()
ret.Code = -1
arg := map[string]interface{}{}
if err := c.BindJSON(&arg); nil != err {
ret.Code = -1
ret.Msg = "parses request failed"
c.JSON(http.StatusOK, ret)
return
}
name := arg["userName"].(string)
password := arg["userPassword"].(string)
captcha := arg["captcha"].(string)
result, err := model.Login(name, password, captcha)
if nil != err {
return
}
c.JSON(http.StatusOK, result)
}

223
kernel/api/asset.go Normal file
View file

@ -0,0 +1,223 @@
// 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 api
import (
"net/http"
"os"
"path/filepath"
"strings"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func getDocImageAssets(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
assets, err := model.DocImageAssets(id)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = assets
}
func setFileAnnotation(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
p := arg["path"].(string)
p = strings.ReplaceAll(p, "%23", "#")
data := arg["data"].(string)
writePath, err := resolveFileAnnotationAbsPath(p)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
if err := gulu.File.WriteFileSafer(writePath, []byte(data), 0644); nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
model.IncWorkspaceDataVer()
}
func getFileAnnotation(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
p := arg["path"].(string)
p = strings.ReplaceAll(p, "%23", "#")
readPath, err := resolveFileAnnotationAbsPath(p)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 5000}
return
}
if !gulu.File.IsExist(readPath) {
ret.Code = 1
return
}
data, err := os.ReadFile(readPath)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = map[string]interface{}{
"data": string(data),
}
}
func resolveFileAnnotationAbsPath(assetRelPath string) (ret string, err error) {
filePath := strings.TrimSuffix(assetRelPath, ".sya")
absPath, err := model.GetAssetAbsPath(filePath)
if nil != err {
return
}
dir := filepath.Dir(absPath)
base := filepath.Base(assetRelPath)
ret = filepath.Join(dir, base)
return
}
func removeUnusedAsset(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
p := arg["path"].(string)
asset := model.RemoveUnusedAsset(p)
ret.Data = map[string]interface{}{
"path": asset,
}
}
func removeUnusedAssets(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
paths := model.RemoveUnusedAssets()
ret.Data = map[string]interface{}{
"paths": paths,
}
}
func getUnusedAssets(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
unusedAssets := model.UnusedAssets()
ret.Data = map[string]interface{}{
"unusedAssets": unusedAssets,
}
}
func resolveAssetPath(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
path := arg["path"].(string)
p, err := model.GetAssetAbsPath(path)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 3000}
return
}
ret.Data = p
return
}
func uploadCloud(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
rootID := arg["id"].(string)
err := model.UploadAssets2Cloud(rootID)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 3000}
} else {
util.PushMsg(model.Conf.Language(41), 3000)
}
}
func insertLocalAssets(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
assetPathsArg := arg["assetPaths"].([]interface{})
var assetPaths []string
for _, pathArg := range assetPathsArg {
assetPaths = append(assetPaths, pathArg.(string))
}
id := arg["id"].(string)
succMap, err := model.InsertLocalAssets(id, assetPaths)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = map[string]interface{}{
"succMap": succMap,
}
}

92
kernel/api/attr.go Normal file
View file

@ -0,0 +1,92 @@
// 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 api
import (
"net/http"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func getBookmarkLabels(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
ret.Data = model.BookmarkLabels()
}
func getBlockAttrs(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
ret.Data = model.GetBlockAttrs(id)
}
func setBlockAttrs(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
attrs := arg["attrs"].(map[string]interface{})
nameValues := map[string]string{}
for name, value := range attrs {
nameValues[name] = value.(string)
}
err := model.SetBlockAttrs(id, nameValues)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
}
func resetBlockAttrs(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
attrs := arg["attrs"].(map[string]interface{})
nameValues := map[string]string{}
for name, value := range attrs {
nameValues[name] = value.(string)
}
err := model.ResetBlockAttrs(id, nameValues)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
}

129
kernel/api/backup.go Normal file
View file

@ -0,0 +1,129 @@
// 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 api
import (
"net/http"
"github.com/88250/gulu"
"github.com/dustin/go-humanize"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func removeCloudBackup(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
err := model.RemoveCloudBackup()
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
}
func downloadCloudBackup(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
err := model.DownloadBackup()
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
}
func uploadLocalBackup(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
err := model.UploadBackup()
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
}
func recoverLocalBackup(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
err := model.RecoverLocalBackup()
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
}
func createLocalBackup(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
err := model.CreateLocalBackup()
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
}
func getLocalBackup(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
backup, err := model.GetLocalBackup()
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = map[string]interface{}{
"backup": backup,
}
}
func getCloudSpace(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
sync, backup, size, assetSize, totalSize, err := model.GetCloudSpace()
if nil != err {
ret.Code = 1
ret.Msg = err.Error()
util.PushErrMsg(err.Error(), 3000)
return
}
hTrafficUploadSize := humanize.Bytes(uint64(model.Conf.User.UserTrafficUpload))
hTrafficDownloadSize := humanize.Bytes(uint64(model.Conf.User.UserTrafficDownload))
ret.Data = map[string]interface{}{
"sync": sync,
"backup": backup,
"hAssetSize": assetSize,
"hSize": size,
"hTotalSize": totalSize,
"hTrafficUploadSize": hTrafficUploadSize,
"hTrafficDownloadSize": hTrafficDownloadSize,
}
}

276
kernel/api/bazaar.go Normal file
View file

@ -0,0 +1,276 @@
// 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 api
import (
"net/http"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func getBazaarPackageREAME(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
repoURL := arg["repoURL"].(string)
repoHash := arg["repoHash"].(string)
ret.Data = map[string]interface{}{
"html": model.GetPackageREADME(repoURL, repoHash),
}
}
func getBazaarWidget(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
ret.Data = map[string]interface{}{
"packages": model.BazaarWidgets(),
}
}
func installBazaarWidget(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
repoURL := arg["repoURL"].(string)
repoHash := arg["repoHash"].(string)
packageName := arg["packageName"].(string)
err := model.InstallBazaarWidget(repoURL, repoHash, packageName)
if nil != err {
ret.Code = 1
ret.Msg = err.Error()
return
}
util.PushMsg(model.Conf.Language(69), 3000)
ret.Data = map[string]interface{}{
"packages": model.BazaarWidgets(),
}
}
func uninstallBazaarWidget(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
packageName := arg["packageName"].(string)
err := model.UninstallBazaarWidget(packageName)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = map[string]interface{}{
"packages": model.BazaarWidgets(),
}
}
func getBazaarIcon(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
ret.Data = map[string]interface{}{
"packages": model.BazaarIcons(),
}
}
func installBazaarIcon(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
repoURL := arg["repoURL"].(string)
repoHash := arg["repoHash"].(string)
packageName := arg["packageName"].(string)
err := model.InstallBazaarIcon(repoURL, repoHash, packageName)
if nil != err {
ret.Code = 1
ret.Msg = err.Error()
return
}
util.PushMsg(model.Conf.Language(69), 3000)
ret.Data = map[string]interface{}{
"packages": model.BazaarIcons(),
"appearance": model.Conf.Appearance,
}
}
func uninstallBazaarIcon(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
packageName := arg["packageName"].(string)
err := model.UninstallBazaarIcon(packageName)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = map[string]interface{}{
"packages": model.BazaarIcons(),
"appearance": model.Conf.Appearance,
}
}
func getBazaarTemplate(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
ret.Data = map[string]interface{}{
"packages": model.BazaarTemplates(),
}
}
func installBazaarTemplate(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
repoURL := arg["repoURL"].(string)
repoHash := arg["repoHash"].(string)
packageName := arg["packageName"].(string)
err := model.InstallBazaarTemplate(repoURL, repoHash, packageName)
if nil != err {
ret.Code = 1
ret.Msg = err.Error()
return
}
ret.Data = map[string]interface{}{
"packages": model.BazaarTemplates(),
}
util.PushMsg(model.Conf.Language(69), 3000)
}
func uninstallBazaarTemplate(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
packageName := arg["packageName"].(string)
err := model.UninstallBazaarTemplate(packageName)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = map[string]interface{}{
"packages": model.BazaarTemplates(),
}
}
func getBazaarTheme(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
ret.Data = map[string]interface{}{
"packages": model.BazaarThemes(),
}
}
func installBazaarTheme(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
repoURL := arg["repoURL"].(string)
repoHash := arg["repoHash"].(string)
packageName := arg["packageName"].(string)
mode := arg["mode"].(float64)
update := false
if nil != arg["update"] {
update = arg["update"].(bool)
}
err := model.InstallBazaarTheme(repoURL, repoHash, packageName, int(mode), update)
if nil != err {
ret.Code = 1
ret.Msg = err.Error()
return
}
util.PushMsg(model.Conf.Language(69), 3000)
ret.Data = map[string]interface{}{
"packages": model.BazaarThemes(),
"appearance": model.Conf.Appearance,
}
}
func uninstallBazaarTheme(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
packageName := arg["packageName"].(string)
err := model.UninstallBazaarTheme(packageName)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = map[string]interface{}{
"packages": model.BazaarThemes(),
"appearance": model.Conf.Appearance,
}
}

282
kernel/api/block.go Normal file
View file

@ -0,0 +1,282 @@
// 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 api
import (
"fmt"
"net/http"
"github.com/88250/gulu"
"github.com/88250/lute/html"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/filesys"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/sql"
"github.com/siyuan-note/siyuan/kernel/util"
)
func setBlockReminder(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
timed := arg["timed"].(string) // yyyyMMddHHmmss
err := model.SetBlockReminder(id, timed)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 7000}
return
}
}
func checkBlockFold(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
ret.Data = sql.IsBlockFolded(id)
}
func checkBlockExist(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
b, err := model.GetBlock(id)
if filesys.ErrUnableLockFile == err {
ret.Code = 2
ret.Data = id
return
}
ret.Data = nil != b
}
func getDocInfo(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
info := model.GetDocInfo(id)
ret.Data = info
}
func getRecentUpdatedBlocks(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
blocks := model.RecentUpdatedBlocks()
ret.Data = blocks
}
func getBlockWordCount(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
blockRuneCount, blockWordCount, rootBlockRuneCount, rootBlockWordCount := model.BlockWordCount(id)
ret.Data = map[string]interface{}{
"blockRuneCount": blockRuneCount,
"blockWordCount": blockWordCount,
"rootBlockRuneCount": rootBlockRuneCount,
"rootBlockWordCount": rootBlockWordCount,
}
}
func getRefText(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
ret.Data = model.GetBlockRefText(id)
}
func getRefIDs(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
refIDs, refTexts, defIDs := model.GetBlockRefIDs(id)
ret.Data = map[string][]string{
"refIDs": refIDs,
"refTexts": refTexts,
"defIDs": defIDs,
}
}
func getRefIDsByFileAnnotationID(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
refIDs, refTexts := model.GetBlockRefIDsByFileAnnotationID(id)
ret.Data = map[string][]string{
"refIDs": refIDs,
"refTexts": refTexts,
}
}
func getBlockDefIDsByRefText(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
anchor := arg["anchor"].(string)
excludeIDsArg := arg["excludeIDs"].([]interface{})
var excludeIDs []string
for _, excludeID := range excludeIDsArg {
excludeIDs = append(excludeIDs, excludeID.(string))
}
excludeIDs = nil // 不限制虚拟引用搜索自己 https://ld246.com/article/1633243424177
ids := model.GetBlockDefIDsByRefText(anchor, excludeIDs)
ret.Data = ids
}
func getBlockBreadcrumb(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
blockPath, err := model.BuildBlockBreadcrumb(id)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = blockPath
}
func getBlockInfo(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
block, err := model.GetBlock(id)
if filesys.ErrUnableLockFile == err {
ret.Code = 2
ret.Data = id
return
}
if nil == block {
ret.Code = 1
ret.Msg = fmt.Sprintf(model.Conf.Language(15), id)
return
}
var rootChildID string
b := block
for i := 0; i < 128; i++ {
parentID := b.ParentID
if "" == parentID {
rootChildID = b.ID
break
}
if b, _ = model.GetBlock(parentID); nil == b {
util.LogErrorf("not found parent")
break
}
}
root, err := model.GetBlock(block.RootID)
if filesys.ErrUnableLockFile == err {
ret.Code = 2
ret.Data = id
return
}
rootTitle := root.IAL["title"]
rootTitle = html.UnescapeString(rootTitle)
ret.Data = map[string]string{
"box": block.Box,
"path": block.Path,
"rootID": block.RootID,
"rootTitle": rootTitle,
"rootChildID": rootChildID,
"rootIcon": root.IAL["icon"],
}
}
func getBlockDOM(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
dom := model.GetBlockDOM(id)
ret.Data = map[string]string{
"id": id,
"dom": dom,
}
}

301
kernel/api/block_op.go Normal file
View file

@ -0,0 +1,301 @@
// 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 api
import (
"net/http"
"github.com/88250/gulu"
"github.com/88250/lute"
"github.com/88250/lute/ast"
"github.com/88250/protyle"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func appendBlock(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
data := arg["data"].(string)
dataType := arg["dataType"].(string)
parentID := arg["parentID"].(string)
if "markdown" == dataType {
luteEngine := model.NewLute()
data = dataBlockDOM(data, luteEngine)
}
transactions := []*model.Transaction{
{
DoOperations: []*model.Operation{
{
Action: "appendInsert",
Data: data,
ParentID: parentID,
},
},
},
}
err := model.PerformTransactions(&transactions)
if nil != err {
ret.Code = 1
ret.Msg = err.Error()
return
}
model.WaitForWritingFiles()
ret.Data = transactions
broadcastTransactions(transactions)
}
func prependBlock(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
data := arg["data"].(string)
dataType := arg["dataType"].(string)
parentID := arg["parentID"].(string)
if "markdown" == dataType {
luteEngine := model.NewLute()
data = dataBlockDOM(data, luteEngine)
}
transactions := []*model.Transaction{
{
DoOperations: []*model.Operation{
{
Action: "prependInsert",
Data: data,
ParentID: parentID,
},
},
},
}
err := model.PerformTransactions(&transactions)
if nil != err {
ret.Code = 1
ret.Msg = err.Error()
return
}
model.WaitForWritingFiles()
ret.Data = transactions
broadcastTransactions(transactions)
}
func insertBlock(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
data := arg["data"].(string)
dataType := arg["dataType"].(string)
var parentID, previousID string
if nil != arg["parentID"] {
parentID = arg["parentID"].(string)
}
if nil != arg["previousID"] {
previousID = arg["previousID"].(string)
}
if "markdown" == dataType {
luteEngine := model.NewLute()
data = dataBlockDOM(data, luteEngine)
}
transactions := []*model.Transaction{
{
DoOperations: []*model.Operation{
{
Action: "insert",
Data: data,
ParentID: parentID,
PreviousID: previousID,
},
},
},
}
err := model.PerformTransactions(&transactions)
if nil != err {
ret.Code = 1
ret.Msg = err.Error()
return
}
model.WaitForWritingFiles()
ret.Data = transactions
broadcastTransactions(transactions)
}
func updateBlock(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
data := arg["data"].(string)
dataType := arg["dataType"].(string)
id := arg["id"].(string)
luteEngine := model.NewLute()
if "markdown" == dataType {
data = dataBlockDOM(data, luteEngine)
}
tree := luteEngine.BlockDOM2Tree(data)
if nil == tree || nil == tree.Root || nil == tree.Root.FirstChild {
ret.Code = -1
ret.Msg = "parse tree failed"
return
}
block, err := model.GetBlock(id)
if nil != err {
ret.Code = -1
ret.Msg = "get block failed: " + err.Error()
return
}
var transactions []*model.Transaction
if "NodeDocument" == block.Type {
oldTree, err := model.LoadTree(block.Box, block.Path)
if nil != err {
ret.Code = -1
ret.Msg = "load tree failed: " + err.Error()
return
}
var toRemoves []*ast.Node
var ops []*model.Operation
for n := oldTree.Root.FirstChild; nil != n; n = n.Next {
toRemoves = append(toRemoves, n)
ops = append(ops, &model.Operation{Action: "delete", ID: n.ID})
}
for _, n := range toRemoves {
n.Unlink()
}
ops = append(ops, &model.Operation{Action: "appendInsert", Data: data, ParentID: id})
transactions = append(transactions, &model.Transaction{
DoOperations: ops,
})
} else {
if "NodeListItem" == block.Type {
// 使用 API `api/block/updateBlock` 更新列表项时渲染错误 https://github.com/siyuan-note/siyuan/issues/4658
tree.Root.AppendChild(tree.Root.FirstChild.FirstChild) // 将列表下的第一个列表项移到文档结尾,移动以后根下面直接挂列表项,渲染器可以正常工作
tree.Root.FirstChild.Unlink() // 删除列表
tree.Root.FirstChild.Unlink() // 继续删除列表 IAL
}
tree.Root.FirstChild.SetIALAttr("id", id)
data = luteEngine.Tree2BlockDOM(tree, luteEngine.RenderOptions)
transactions = []*model.Transaction{
{
DoOperations: []*model.Operation{
{
Action: "update",
ID: id,
Data: data,
},
},
},
}
}
err = model.PerformTransactions(&transactions)
if nil != err {
ret.Code = 1
ret.Msg = err.Error()
return
}
model.WaitForWritingFiles()
ret.Data = transactions
broadcastTransactions(transactions)
}
func deleteBlock(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
transactions := []*model.Transaction{
{
DoOperations: []*model.Operation{
{
Action: "delete",
ID: id,
},
},
},
}
err := model.PerformTransactions(&transactions)
if nil != err {
ret.Code = 1
ret.Msg = err.Error()
return
}
ret.Data = transactions
broadcastTransactions(transactions)
}
func broadcastTransactions(transactions []*model.Transaction) {
evt := util.NewCmdResult("transactions", 0, util.PushModeBroadcast, util.PushModeBroadcast)
evt.Data = transactions
util.PushEvent(evt)
}
func dataBlockDOM(data string, luteEngine *lute.Lute) (ret string) {
ret = luteEngine.Md2BlockDOM(data)
if "" == ret {
// 使用 API 插入空字符串出现错误 https://github.com/siyuan-note/siyuan/issues/3931
blankParagraph := protyle.NewParagraph()
ret = lute.RenderNodeBlockDOM(blankParagraph, luteEngine.ParseOptions, luteEngine.RenderOptions)
}
return
}

52
kernel/api/bookmark.go Normal file
View file

@ -0,0 +1,52 @@
// 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 api
import (
"net/http"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func getBookmark(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
ret.Data = model.BuildBookmark()
}
func renameBookmark(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
oldBookmark := arg["oldBookmark"].(string)
newBookmark := arg["newBookmark"].(string)
if err := model.RenameBookmark(oldBookmark, newBookmark); nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 5000}
return
}
}

31
kernel/api/clipboard.go Normal file
View file

@ -0,0 +1,31 @@
// 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 api
import (
"github.com/88250/clipboard"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
)
func readFilePaths(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(200, ret)
paths, _ := clipboard.ReadFilePaths()
ret.Data = paths
}

219
kernel/api/export.go Normal file
View file

@ -0,0 +1,219 @@
// 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 api
import (
"net/http"
"path"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func exportDataInFolder(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
exportFolder := arg["folder"].(string)
err := model.ExportDataInFolder(exportFolder)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 7000}
return
}
}
func exportData(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
zipPath := model.ExportData()
ret.Data = map[string]interface{}{
"zip": zipPath,
}
}
func batchExportMd(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
notebook := arg["notebook"].(string)
p := arg["path"].(string)
zipPath := model.BatchExportMarkdown(notebook, p)
ret.Data = map[string]interface{}{
"name": path.Base(zipPath),
"zip": zipPath,
}
}
func exportMd(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
name, zipPath := model.ExportMarkdown(id)
ret.Data = map[string]interface{}{
"name": name,
"zip": zipPath,
}
}
func exportSY(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
name, zipPath := model.ExportSY(id)
ret.Data = map[string]interface{}{
"name": name,
"zip": zipPath,
}
}
func exportMdContent(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
hPath, content := model.ExportMarkdownContent(id)
ret.Data = map[string]interface{}{
"hPath": hPath,
"content": content,
}
}
func exportDocx(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
savePath := arg["savePath"].(string)
err := model.ExportDocx(id, savePath)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 7000}
return
}
}
func exportMdHTML(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
savePath := arg["savePath"].(string)
name, content := model.ExportMarkdownHTML(id, savePath, false)
ret.Data = map[string]interface{}{
"id": id,
"name": name,
"content": content,
}
}
func exportHTML(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
pdf := arg["pdf"].(bool)
savePath := arg["savePath"].(string)
name, content := model.ExportHTML(id, savePath, pdf)
ret.Data = map[string]interface{}{
"id": id,
"name": name,
"content": content,
}
}
func addPDFOutline(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
path := arg["path"].(string)
err := model.AddPDFOutline(id, path)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
}
func exportPreview(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
stdHTML := model.Preview(id)
ret.Data = map[string]interface{}{
"html": stdHTML,
}
}

135
kernel/api/extension.go Normal file
View file

@ -0,0 +1,135 @@
// 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 api
import (
"bytes"
"io"
"net/url"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"github.com/88250/gulu"
"github.com/88250/lute/ast"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func extensionCopy(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(200, ret)
form, _ := c.MultipartForm()
dom := form.Value["dom"][0]
assets := filepath.Join(util.DataDir, "assets")
if notebookVal := form.Value["notebook"]; 0 < len(notebookVal) {
assets = filepath.Join(util.DataDir, notebookVal[0], "assets")
if !gulu.File.IsDir(assets) {
assets = filepath.Join(util.DataDir, "assets")
}
}
if err := os.MkdirAll(assets, 0755); nil != err {
util.LogErrorf("create assets folder [%s] failed: %s", assets, err)
ret.Msg = err.Error()
return
}
luteEngine := model.NewLute()
md := luteEngine.HTML2Md(dom)
md = strings.TrimSpace(md)
ret.Data = map[string]interface{}{
"md": md,
}
uploaded := map[string]string{}
for originalName, file := range form.File {
oName, err := url.PathUnescape(originalName)
if nil != err {
if strings.Contains(originalName, "%u") {
originalName = strings.ReplaceAll(originalName, "%u", "\\u")
originalName, err = strconv.Unquote("\"" + originalName + "\"")
if nil != err {
continue
}
oName, err = url.PathUnescape(originalName)
if nil != err {
continue
}
} else {
continue
}
}
u, _ := url.Parse(oName)
if "" == u.Path {
continue
}
fName := path.Base(u.Path)
fName = util.FilterUploadFileName(fName)
f, err := file[0].Open()
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
break
}
data, err := io.ReadAll(f)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
break
}
ext := path.Ext(fName)
fName = fName[0 : len(fName)-len(ext)]
if "" == ext && bytes.HasPrefix(data, []byte("<svg ")) && bytes.HasSuffix(data, []byte("</svg>")) {
ext = ".svg"
}
fName = fName + "-" + ast.NewNodeID() + ext
writePath := filepath.Join(assets, fName)
if err = gulu.File.WriteFileSafer(writePath, data, 0644); nil != err {
ret.Code = -1
ret.Msg = err.Error()
break
}
uploaded[oName] = "assets/" + fName
}
for k, v := range uploaded {
if "" == md {
// 复制单个图片的情况
md = "![](" + v + ")"
break
}
md = strings.ReplaceAll(md, "]("+k+")", "]("+v+")")
p, err := url.Parse(k)
if nil != err {
continue
}
md = strings.ReplaceAll(md, "]("+p.Path+")", "]("+v+")")
}
ret.Data = map[string]interface{}{
"md": md,
}
ret.Msg = model.Conf.Language(72)
}

147
kernel/api/file.go Normal file
View file

@ -0,0 +1,147 @@
// 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 api
import (
"errors"
"fmt"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"strconv"
"time"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/filesys"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func getFile(c *gin.Context) {
ret := gulu.Ret.NewResult()
arg, ok := util.JsonArg(c, ret)
if !ok {
c.JSON(http.StatusOK, ret)
return
}
filePath := arg["path"].(string)
filePath = filepath.Join(util.WorkspaceDir, filePath)
info, err := os.Stat(filePath)
if os.IsNotExist(err) {
c.Status(404)
return
}
if nil != err {
util.LogErrorf("stat [%s] failed: %s", filePath, err)
c.Status(500)
return
}
if info.IsDir() {
util.LogErrorf("file [%s] is a directory", filePath)
c.Status(405)
return
}
if err = model.ServeFile(c, filePath); nil != err {
c.Status(http.StatusConflict)
return
}
}
func putFile(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
filePath := c.PostForm("path")
filePath = filepath.Join(util.WorkspaceDir, filePath)
isDirStr := c.PostForm("isDir")
isDir, _ := strconv.ParseBool(isDirStr)
var err error
if isDir {
err = os.MkdirAll(filePath, 0755)
if nil != err {
util.LogErrorf("make a dir [%s] failed: %s", filePath, err)
}
} else {
file, _ := c.FormFile("file")
if nil == file {
util.LogErrorf("form file is nil [path=%s]", filePath)
c.Status(400)
return
}
dir := filepath.Dir(filePath)
if err = os.MkdirAll(dir, 0755); nil != err {
util.LogErrorf("put a file [%s] make dir [%s] failed: %s", filePath, dir, err)
} else {
if filesys.IsLocked(filePath) {
msg := fmt.Sprintf("file [%s] is locked", filePath)
util.LogErrorf(msg)
err = errors.New(msg)
} else {
err = writeFile(file, filePath)
if nil != err {
util.LogErrorf("put a file [%s] failed: %s", filePath, err)
}
}
}
}
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
modTimeStr := c.PostForm("modTime")
modTimeInt, err := strconv.ParseInt(modTimeStr, 10, 64)
if nil != err {
util.LogErrorf("parse mod time [%s] failed: %s", modTimeStr, err)
c.Status(500)
return
}
modTime := millisecond2Time(modTimeInt)
if err = os.Chtimes(filePath, modTime, modTime); nil != err {
util.LogErrorf("change time failed: %s", err)
ret.Code = -1
ret.Msg = err.Error()
return
}
}
func writeFile(file *multipart.FileHeader, dst string) error {
src, err := file.Open()
if err != nil {
return err
}
defer src.Close()
err = gulu.File.WriteFileSaferByReader(dst, src, 0644)
if nil != err {
return err
}
return nil
}
func millisecond2Time(t int64) time.Time {
sec := t / 1000
msec := t % 1000
return time.Unix(sec, msec*int64(time.Millisecond))
}

636
kernel/api/filetree.go Normal file
View file

@ -0,0 +1,636 @@
// 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 api
import (
"fmt"
"net/http"
"path"
"regexp"
"strings"
"unicode/utf8"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/filesys"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func refreshFiletree(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
model.RefreshFileTree()
}
func doc2Heading(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
srcID := arg["srcID"].(string)
targetID := arg["targetID"].(string)
after := arg["after"].(bool)
srcTreeBox, srcTreePath, err := model.Doc2Heading(srcID, targetID, after)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 5000}
return
}
ret.Data = map[string]interface{}{
"srcTreeBox": srcTreeBox,
"srcTreePath": srcTreePath,
}
}
func heading2Doc(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
srcHeadingID := arg["srcHeadingID"].(string)
targetNotebook := arg["targetNoteBook"].(string)
targetPath := arg["targetPath"].(string)
srcRootBlockID, targetPath, err := model.Heading2Doc(srcHeadingID, targetNotebook, targetPath)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 5000}
return
}
model.WaitForWritingFiles()
tree, err := model.LoadTree(targetNotebook, targetPath)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
name := path.Base(targetPath)
box := model.Conf.Box(targetNotebook)
files, _, _ := model.ListDocTree(targetNotebook, path.Dir(targetPath), model.Conf.FileTree.Sort)
evt := util.NewCmdResult("heading2doc", 0, util.PushModeBroadcast, util.PushModeNone)
evt.Data = map[string]interface{}{
"box": box,
"path": targetPath,
"files": files,
"name": name,
"id": tree.Root.ID,
"srcRootBlockID": srcRootBlockID,
}
evt.Callback = arg["callback"]
util.PushEvent(evt)
}
func li2Doc(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
srcListItemID := arg["srcListItemID"].(string)
targetNotebook := arg["targetNoteBook"].(string)
targetPath := arg["targetPath"].(string)
srcRootBlockID, targetPath, err := model.ListItem2Doc(srcListItemID, targetNotebook, targetPath)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 5000}
return
}
model.WaitForWritingFiles()
tree, err := model.LoadTree(targetNotebook, targetPath)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
name := path.Base(targetPath)
box := model.Conf.Box(targetNotebook)
files, _, _ := model.ListDocTree(targetNotebook, path.Dir(targetPath), model.Conf.FileTree.Sort)
evt := util.NewCmdResult("li2doc", 0, util.PushModeBroadcast, util.PushModeNone)
evt.Data = map[string]interface{}{
"box": box,
"path": targetPath,
"files": files,
"name": name,
"id": tree.Root.ID,
"srcRootBlockID": srcRootBlockID,
}
evt.Callback = arg["callback"]
util.PushEvent(evt)
}
func getHPathByPath(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
notebook := arg["notebook"].(string)
p := arg["path"].(string)
hPath, err := model.GetHPathByPath(notebook, p)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = hPath
}
func getHPathByID(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
hPath, err := model.GetHPathByID(id)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = hPath
}
func getFullHPathByID(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
hPath, err := model.GetFullHPathByID(id)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = hPath
}
func moveDoc(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
fromNotebook := arg["fromNotebook"].(string)
toNotebook := arg["toNotebook"].(string)
fromPath := arg["fromPath"].(string)
toPath := arg["toPath"].(string)
newPath, err := model.MoveDoc(fromNotebook, fromPath, toNotebook, toPath)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 7000}
return
}
evt := util.NewCmdResult("moveDoc", 0, util.PushModeBroadcast, util.PushModeNone)
evt.Data = map[string]interface{}{
"fromNotebook": fromNotebook,
"toNotebook": toNotebook,
"fromPath": fromPath,
"toPath": toPath,
"newPath": newPath,
}
util.PushEvent(evt)
}
func removeDoc(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
notebook := arg["notebook"].(string)
p := arg["path"].(string)
err := model.RemoveDoc(notebook, p)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
evt := util.NewCmdResult("remove", 0, util.PushModeBroadcast, util.PushModeNone)
evt.Data = map[string]interface{}{
"box": notebook,
"path": p,
}
util.PushEvent(evt)
}
func renameDoc(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
notebook := arg["notebook"].(string)
p := arg["path"].(string)
title := arg["title"].(string)
err := model.RenameDoc(notebook, p, title)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
return
}
func duplicateDoc(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
err := model.DuplicateDoc(id)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 7000}
return
}
block, _ := model.GetBlock(id)
p := block.Path
notebook := block.Box
box := model.Conf.Box(notebook)
tree, err := model.LoadTree(box.ID, p)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
pushCreate(box, p, tree.Root.ID, arg)
}
func createDoc(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
notebook := arg["notebook"].(string)
p := arg["path"].(string)
title := arg["title"].(string)
md := arg["md"].(string)
err := model.CreateDocByMd(notebook, p, title, md)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 7000}
return
}
box := model.Conf.Box(notebook)
tree, err := model.LoadTree(box.ID, p)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
pushCreate(box, p, tree.Root.ID, arg)
}
func createDailyNote(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
notebook := arg["notebook"].(string)
p, err := model.CreateDailyNote(notebook)
if nil != err {
if model.ErrBoxNotFound == err {
ret.Code = 1
} else {
ret.Code = -1
}
ret.Msg = err.Error()
return
}
box := model.Conf.Box(notebook)
model.WaitForWritingFiles()
tree, err := model.LoadTree(box.ID, p)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
evt := util.NewCmdResult("createdailynote", 0, util.PushModeBroadcast, util.PushModeNone)
name := path.Base(p)
files, _, _ := model.ListDocTree(box.ID, path.Dir(p), model.Conf.FileTree.Sort)
evt.Data = map[string]interface{}{
"box": box,
"path": p,
"files": files,
"name": name,
"id": tree.Root.ID,
}
evt.Callback = arg["callback"]
util.PushEvent(evt)
}
func createDocWithMd(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
notebook := arg["notebook"].(string)
hPath := arg["path"].(string)
markdown := arg["markdown"].(string)
baseName := path.Base(hPath)
dir := path.Dir(hPath)
r, _ := regexp.Compile("\r\n|\r|\n|\u2028|\u2029|\t|/")
baseName = r.ReplaceAllString(baseName, "")
if 512 < utf8.RuneCountInString(baseName) {
baseName = gulu.Str.SubStr(baseName, 512)
}
hPath = path.Join(dir, baseName)
if !strings.HasPrefix(hPath, "/") {
hPath = "/" + hPath
}
id, err := model.CreateWithMarkdown(notebook, hPath, markdown)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = id
box := model.Conf.Box(notebook)
b, _ := model.GetBlock(id)
p := b.Path
pushCreate(box, p, id, arg)
}
func lockFile(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
locked, filePath := model.LockFileByBlockID(id)
if !locked {
ret.Code = -1
ret.Msg = fmt.Sprintf(model.Conf.Language(75), filePath)
ret.Data = map[string]interface{}{"closeTimeout": 5000}
}
}
func getDocNameTemplate(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
notebook := arg["notebook"].(string)
box := model.Conf.Box(notebook)
nameTemplate := model.Conf.FileTree.CreateDocNameTemplate
if nil != box {
nameTemplate = box.GetConf().CreateDocNameTemplate
}
if "" == nameTemplate {
nameTemplate = model.Conf.FileTree.CreateDocNameTemplate
}
name, err := model.RenderCreateDocNameTemplate(nameTemplate)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = map[string]interface{}{
"name": name,
}
}
func changeSort(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
notebook := arg["notebook"].(string)
pathsArg := arg["paths"].([]interface{})
var paths []string
for _, p := range pathsArg {
paths = append(paths, p.(string))
}
model.ChangeFileTreeSort(notebook, paths)
}
func searchDocs(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
k := arg["k"].(string)
ret.Data = model.SearchDocsByKeyword(k)
}
func listDocsByPath(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
notebook := arg["notebook"].(string)
p := arg["path"].(string)
sortParam := arg["sort"]
sortMode := model.Conf.FileTree.Sort
if nil != sortParam {
sortMode = int(sortParam.(float64))
}
files, totals, err := model.ListDocTree(notebook, p, sortMode)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
if model.Conf.FileTree.MaxListCount < totals {
util.PushMsg(fmt.Sprintf(model.Conf.Language(48), len(files)), 7000)
}
ret.Data = map[string]interface{}{
"box": notebook,
"path": p,
"files": files,
}
// 持久化文档面板排序
model.Conf.FileTree.Sort = sortMode
model.Conf.Save()
}
func getDoc(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
idx := arg["index"]
index := 0
if nil != idx {
index = int(idx.(float64))
}
k := arg["k"]
var keyword string
if nil != k {
keyword = k.(string)
}
m := arg["mode"] // 0: 仅当前 ID1向上 2向下3上下都加载4加载末尾
mode := 0
if nil != m {
mode = int(m.(float64))
}
s := arg["size"]
size := 102400 // 默认最大加载块数
if nil != s {
size = int(s.(float64))
}
blockCount, content, parentID, parent2ID, rootID, typ, eof, boxID, docPath, err := model.GetDoc(id, index, keyword, mode, size)
if filesys.ErrUnableLockFile == err {
ret.Code = 2
ret.Data = id
return
}
if model.ErrBlockNotFound == err {
ret.Code = 3
return
}
if nil != err {
ret.Code = 1
ret.Msg = err.Error()
return
}
ret.Data = map[string]interface{}{
"id": id,
"mode": mode,
"parentID": parentID,
"parent2ID": parent2ID,
"rootID": rootID,
"type": typ,
"content": content,
"blockCount": blockCount,
"eof": eof,
"box": boxID,
"path": docPath,
}
}
func pushCreate(box *model.Box, p, treeID string, arg map[string]interface{}) {
evt := util.NewCmdResult("create", 0, util.PushModeBroadcast, util.PushModeNone)
name := path.Base(p)
files, _, _ := model.ListDocTree(box.ID, path.Dir(p), model.Conf.FileTree.Sort)
evt.Data = map[string]interface{}{
"box": box,
"path": p,
"files": files,
"name": name,
"id": treeID,
}
evt.Callback = arg["callback"]
util.PushEvent(evt)
}

64
kernel/api/format.go Normal file
View file

@ -0,0 +1,64 @@
// 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 api
import (
"net/http"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func netImg2LocalAssets(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
err := model.NetImg2LocalAssets(id)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 5000}
return
}
}
func autoSpace(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
err := model.AutoSpace(id)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 5000}
return
}
}

131
kernel/api/graph.go Normal file
View file

@ -0,0 +1,131 @@
// 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 api
import (
"net/http"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/conf"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func resetGraph(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
graph := conf.NewGlobalGraph()
model.Conf.Graph.Global = graph
model.Conf.Save()
ret.Data = map[string]interface{}{
"conf": graph,
}
}
func resetLocalGraph(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
graph := conf.NewLocalGraph()
model.Conf.Graph.Local = graph
model.Conf.Save()
ret.Data = map[string]interface{}{
"conf": graph,
}
}
func getGraph(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
query := arg["k"].(string)
graphConf, err := gulu.JSON.MarshalJSON(arg["conf"])
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
global := conf.NewGlobalGraph()
if err = gulu.JSON.UnmarshalJSON(graphConf, global); nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
model.Conf.Graph.Global = global
model.Conf.Save()
boxID, nodes, links := model.BuildGraph(query)
ret.Data = map[string]interface{}{
"nodes": nodes,
"links": links,
"conf": global,
"box": boxID,
"reqId": arg["reqId"],
}
util.RandomSleep(200, 500)
}
func getLocalGraph(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
keyword := arg["k"].(string)
id := arg["id"].(string)
graphConf, err := gulu.JSON.MarshalJSON(arg["conf"])
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
local := conf.NewLocalGraph()
if err = gulu.JSON.UnmarshalJSON(graphConf, local); nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
model.Conf.Graph.Local = local
model.Conf.Save()
boxID, nodes, links := model.BuildTreeGraph(id, keyword)
ret.Data = map[string]interface{}{
"id": id,
"box": boxID,
"nodes": nodes,
"links": links,
"conf": local,
"reqId": arg["reqId"],
}
util.RandomSleep(200, 500)
}

178
kernel/api/history.go Normal file
View file

@ -0,0 +1,178 @@
// 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 api
import (
"net/http"
"time"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func getNotebookHistory(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
histories, err := model.GetNotebookHistory()
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = map[string]interface{}{
"histories": histories,
}
}
func getAssetsHistory(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
histories, err := model.GetAssetsHistory()
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = map[string]interface{}{
"histories": histories,
}
}
func clearWorkspaceHistory(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
util.PushMsg(model.Conf.Language(100), 1000*60*15)
time.Sleep(3 * time.Second)
err := model.ClearWorkspaceHistory()
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
util.PushMsg(model.Conf.Language(99), 1000*5)
}
func getDocHistory(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
notebook := arg["notebook"].(string)
histories, err := model.GetDocHistory(notebook)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = map[string]interface{}{
"box": notebook,
"histories": histories,
}
}
func getDocHistoryContent(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
historyPath := arg["historyPath"].(string)
content, err := model.GetDocHistoryContent(historyPath)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = map[string]interface{}{
"content": content,
}
}
func rollbackDocHistory(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
notebook := arg["notebook"].(string)
historyPath := arg["historyPath"].(string)
err := model.RollbackDocHistory(notebook, historyPath)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = map[string]interface{}{
"box": notebook,
}
}
func rollbackAssetsHistory(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
historyPath := arg["historyPath"].(string)
err := model.RollbackAssetsHistory(historyPath)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
}
func rollbackNotebookHistory(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
historyPath := arg["historyPath"].(string)
err := model.RollbackNotebookHistory(historyPath)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
}

179
kernel/api/import.go Normal file
View file

@ -0,0 +1,179 @@
// 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 api
import (
"io"
"net/http"
"os"
"path/filepath"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func importSY(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(200, ret)
form, err := c.MultipartForm()
if nil != err {
util.LogErrorf("parse import .sy.zip failed: %s", err)
ret.Code = -1
ret.Msg = err.Error()
return
}
files := form.File["file"]
if 1 > len(files) {
util.LogErrorf("parse import .sy.zip failed: %s", err)
ret.Code = -1
ret.Msg = err.Error()
return
}
file := files[0]
reader, err := file.Open()
if nil != err {
util.LogErrorf("read import .sy.zip failed: %s", err)
ret.Code = -1
ret.Msg = err.Error()
return
}
importDir := filepath.Join(util.TempDir, "import")
if err = os.MkdirAll(importDir, 0755); nil != err {
util.LogErrorf("make import dir [%s] failed: %s", importDir, err)
ret.Code = -1
ret.Msg = err.Error()
return
}
writePath := filepath.Join(util.TempDir, "import", file.Filename)
defer os.RemoveAll(writePath)
writer, err := os.OpenFile(writePath, os.O_RDWR|os.O_CREATE, 0644)
if nil != err {
util.LogErrorf("open import .sy.zip [%s] failed: %s", writePath, err)
ret.Code = -1
ret.Msg = err.Error()
return
}
if _, err = io.Copy(writer, reader); nil != err {
util.LogErrorf("write import .sy.zip failed: %s", err)
ret.Code = -1
ret.Msg = err.Error()
return
}
writer.Close()
reader.Close()
notebook := form.Value["notebook"][0]
toPath := form.Value["toPath"][0]
err = model.ImportSY(writePath, notebook, toPath)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
}
func importData(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
form, err := c.MultipartForm()
if nil != err {
util.LogErrorf("import data failed: %s", err)
ret.Code = -1
ret.Msg = err.Error()
return
}
if 1 > len(form.File["file"]) {
util.LogErrorf("import data failed: %s", err)
ret.Code = -1
ret.Msg = "file not found"
return
}
tmpImport := filepath.Join(util.TempDir, "import")
err = os.MkdirAll(tmpImport, 0755)
if nil != err {
ret.Code = -1
ret.Msg = "create temp import dir failed"
return
}
dataZipPath := filepath.Join(tmpImport, util.CurrentTimeSecondsStr()+".zip")
defer os.RemoveAll(dataZipPath)
dataZipFile, err := os.Create(dataZipPath)
if nil != err {
util.LogErrorf("create temp file failed: %s", err)
ret.Code = -1
ret.Msg = "create temp file failed"
return
}
file := form.File["file"][0]
fileReader, err := file.Open()
if nil != err {
util.LogErrorf("open upload file failed: %s", err)
ret.Code = -1
ret.Msg = "open file failed"
return
}
_, err = io.Copy(dataZipFile, fileReader)
if nil != err {
util.LogErrorf("read upload file failed: %s", err)
ret.Code = -1
ret.Msg = "read file failed"
return
}
if err = dataZipFile.Close(); nil != err {
util.LogErrorf("close file failed: %s", err)
ret.Code = -1
ret.Msg = "close file failed"
return
}
fileReader.Close()
err = model.ImportData(dataZipPath)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
}
func importStdMd(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
notebook := arg["notebook"].(string)
localPath := arg["localPath"].(string)
toPath := arg["toPath"].(string)
err := model.ImportFromLocalPath(notebook, localPath, toPath)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
}

68
kernel/api/inbox.go Normal file
View file

@ -0,0 +1,68 @@
// 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 api
import (
"net/http"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func removeShorthands(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
idsArg := arg["ids"].([]interface{})
var ids []string
for _, id := range idsArg {
ids = append(ids, id.(string))
}
err := model.RemoveCloudShorthands(ids)
if nil != err {
ret.Code = 1
ret.Msg = err.Error()
return
}
}
func getShorthands(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
page := int(arg["page"].(float64))
data, err := model.GetCloudShorthands(page)
if nil != err {
ret.Code = 1
ret.Msg = err.Error()
return
}
ret.Data = data
}

144
kernel/api/lute.go Normal file
View file

@ -0,0 +1,144 @@
// 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 api
import (
"net/http"
"path/filepath"
"strings"
"github.com/88250/gulu"
"github.com/88250/lute/ast"
"github.com/88250/lute/parse"
"github.com/88250/lute/render"
"github.com/88250/protyle"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func copyStdMarkdown(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
ret.Data = model.CopyStdMarkdown(id)
}
func html2BlockDOM(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
dom := arg["dom"].(string)
luteEngine := model.NewLute()
markdown, err := luteEngine.HTML2Markdown(dom)
if nil != err {
ret.Data = "Failed to convert"
return
}
var unlinks []*ast.Node
tree := parse.Parse("", []byte(markdown), luteEngine.ParseOptions)
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
if !entering {
return ast.WalkContinue
}
if ast.NodeListItem == n.Type && nil == n.FirstChild {
newNode := protyle.NewParagraph()
n.AppendChild(newNode)
n.SetIALAttr("updated", util.TimeFromID(newNode.ID))
return ast.WalkSkipChildren
} else if ast.NodeBlockquote == n.Type && nil == n.FirstChild.Next {
unlinks = append(unlinks, n)
}
return ast.WalkContinue
})
for _, n := range unlinks {
n.Unlink()
}
if "std" == model.Conf.System.Container {
// 处理本地资源文件复制
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
if !entering || ast.NodeLinkDest != n.Type {
return ast.WalkContinue
}
if "" == n.TokensStr() {
return ast.WalkContinue
}
localPath := n.TokensStr()
if strings.HasPrefix(localPath, "http") {
return ast.WalkContinue
}
localPath = strings.TrimPrefix(localPath, "file://")
if gulu.OS.IsWindows() {
localPath = strings.TrimPrefix(localPath, "/")
}
if !gulu.File.IsExist(localPath) {
return ast.WalkContinue
}
name := filepath.Base(localPath)
ext := filepath.Ext(name)
name = name[0 : len(name)-len(ext)]
name = name + "-" + ast.NewNodeID() + ext
targetPath := filepath.Join(util.DataDir, "assets", name)
if err = gulu.File.CopyFile(localPath, targetPath); nil != err {
util.LogErrorf("copy asset from [%s] to [%s] failed: %s", localPath, targetPath, err)
return ast.WalkStop
}
n.Tokens = gulu.Str.ToBytes("assets/" + name)
return ast.WalkContinue
})
}
renderer := render.NewBlockRenderer(tree, luteEngine.RenderOptions)
output := renderer.Render()
ret.Data = gulu.Str.FromBytes(output)
}
func spinBlockDOM(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
dom := arg["dom"].(string)
luteEngine := model.NewLute()
dom = luteEngine.SpinBlockDOM(dom)
ret.Data = map[string]interface{}{
"dom": dom,
}
}

277
kernel/api/notebook.go Normal file
View file

@ -0,0 +1,277 @@
// 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 api
import (
"net/http"
"strings"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func setNotebookIcon(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
boxID := arg["notebook"].(string)
icon := arg["icon"].(string)
model.SetBoxIcon(boxID, icon)
}
func changeSortNotebook(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
idsArg := arg["notebooks"].([]interface{})
var ids []string
for _, p := range idsArg {
ids = append(ids, p.(string))
}
model.ChangeBoxSort(ids)
}
func renameNotebook(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
notebook := arg["notebook"].(string)
name := arg["name"].(string)
err := model.RenameBox(notebook, name)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 5000}
return
}
evt := util.NewCmdResult("renamenotebook", 0, util.PushModeBroadcast, util.PushModeNone)
evt.Data = map[string]interface{}{
"box": notebook,
"name": name,
}
util.PushEvent(evt)
}
func removeNotebook(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
notebook := arg["notebook"].(string)
err := model.RemoveBox(notebook)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
evt := util.NewCmdResult("unmount", 0, util.PushModeBroadcast, 0)
evt.Data = map[string]interface{}{
"box": notebook,
}
evt.Callback = arg["callback"]
util.PushEvent(evt)
}
func createNotebook(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
name := arg["name"].(string)
id, err := model.CreateBox(name)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
existed, err := model.Mount(id)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = map[string]interface{}{
"notebook": model.Conf.Box(id),
}
evt := util.NewCmdResult("createnotebook", 0, util.PushModeBroadcast, util.PushModeNone)
evt.Data = map[string]interface{}{
"box": model.Conf.Box(id),
"existed": existed,
}
util.PushEvent(evt)
}
func openNotebook(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
notebook := arg["notebook"].(string)
util.PushMsg(model.Conf.Language(45), 1000*60*15)
existed, err := model.Mount(notebook)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
evt := util.NewCmdResult("mount", 0, util.PushModeBroadcast, util.PushModeNone)
evt.Data = map[string]interface{}{
"box": model.Conf.Box(notebook),
"existed": existed,
}
evt.Callback = arg["callback"]
util.PushEvent(evt)
}
func closeNotebook(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
notebook := arg["notebook"].(string)
model.Unmount(notebook)
}
func getNotebookConf(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
notebook := arg["notebook"].(string)
box := model.Conf.Box(notebook)
ret.Data = map[string]interface{}{
"box": box.ID,
"name": box.Name,
"conf": box.GetConf(),
}
}
func setNotebookConf(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
notebook := arg["notebook"].(string)
box := model.Conf.Box(notebook)
param, err := gulu.JSON.MarshalJSON(arg["conf"])
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
boxConf := box.GetConf()
if err = gulu.JSON.UnmarshalJSON(param, boxConf); nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
boxConf.RefCreateSavePath = strings.TrimSpace(boxConf.RefCreateSavePath)
if "" != boxConf.RefCreateSavePath {
if !strings.HasSuffix(boxConf.RefCreateSavePath, "/") {
boxConf.RefCreateSavePath += "/"
}
}
boxConf.DailyNoteSavePath = strings.TrimSpace(boxConf.DailyNoteSavePath)
if "" != boxConf.DailyNoteSavePath {
if !strings.HasPrefix(boxConf.DailyNoteSavePath, "/") {
boxConf.DailyNoteSavePath = "/" + boxConf.DailyNoteSavePath
}
}
if "/" == boxConf.DailyNoteSavePath {
ret.Code = -1
ret.Msg = model.Conf.Language(49)
return
}
boxConf.DailyNoteTemplatePath = strings.TrimSpace(boxConf.DailyNoteTemplatePath)
if "" != boxConf.DailyNoteTemplatePath {
if !strings.HasSuffix(boxConf.DailyNoteTemplatePath, ".md") {
boxConf.DailyNoteTemplatePath += ".md"
}
if !strings.HasPrefix(boxConf.DailyNoteTemplatePath, "/") {
boxConf.DailyNoteTemplatePath = "/" + boxConf.DailyNoteTemplatePath
}
}
box.SaveConf(boxConf)
ret.Data = boxConf
}
func lsNotebooks(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
notebooks, err := model.ListNotebooks()
if nil != err {
return
}
ret.Data = map[string]interface{}{
"notebooks": notebooks,
}
}

49
kernel/api/outline.go Normal file
View file

@ -0,0 +1,49 @@
// 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 api
import (
"net/http"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func getDocOutline(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
if nil == arg["id"] {
return
}
rootID := arg["id"].(string)
headings, err := model.Outline(rootID)
if nil != err {
ret.Code = 1
ret.Msg = err.Error()
return
}
ret.Data = headings
}

96
kernel/api/ref.go Normal file
View file

@ -0,0 +1,96 @@
// 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 api
import (
"net/http"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func refreshBacklink(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
model.RefreshBacklink(id)
}
func getBacklink(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
if nil == arg["id"] {
return
}
id := arg["id"].(string)
keyword := arg["k"].(string)
mentionKeyword := arg["mk"].(string)
beforeLen := arg["beforeLen"].(float64)
boxID, backlinks, backmentions, linkRefsCount, mentionsCount := model.BuildTreeBacklink(id, keyword, mentionKeyword, int(beforeLen))
ret.Data = map[string]interface{}{
"backlinks": backlinks,
"linkRefsCount": linkRefsCount,
"backmentions": backmentions,
"mentionsCount": mentionsCount,
"k": keyword,
"mk": mentionKeyword,
"box": boxID,
}
util.RandomSleep(200, 500)
}
func createBacklink(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
defID := arg["defID"].(string)
refID := arg["refID"].(string)
refText := arg["refText"].(string)
isDynamic := arg["isDynamic"].(bool)
refRootID, err := model.CreateBacklink(defID, refID, refText, isDynamic)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = map[string]interface{}{
"defID": defID,
"refID": refID,
"refRootID": refRootID,
"refText": refText,
}
}

246
kernel/api/router.go Normal file
View file

@ -0,0 +1,246 @@
// 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 api
import (
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
)
func ServeAPI(ginServer *gin.Engine) {
// 不需要鉴权
ginServer.Handle("GET", "/api/system/bootProgress", bootProgress)
ginServer.Handle("POST", "/api/system/bootProgress", bootProgress)
ginServer.Handle("GET", "/api/system/version", version)
ginServer.Handle("POST", "/api/system/version", version)
ginServer.Handle("POST", "/api/system/currentTime", currentTime)
ginServer.Handle("POST", "/api/system/uiproc", addUIProcess)
ginServer.Handle("POST", "/api/system/loginAuth", model.LoginAuth)
ginServer.Handle("POST", "/api/system/logoutAuth", model.LogoutAuth)
// 需要鉴权
ginServer.Handle("POST", "/api/system/getEmojiConf", model.CheckAuth, getEmojiConf)
ginServer.Handle("POST", "/api/system/setAccessAuthCode", model.CheckAuth, setAccessAuthCode)
ginServer.Handle("POST", "/api/system/setNetworkServe", model.CheckAuth, setNetworkServe)
ginServer.Handle("POST", "/api/system/setUploadErrLog", model.CheckAuth, setUploadErrLog)
ginServer.Handle("POST", "/api/system/setNetworkProxy", model.CheckAuth, setNetworkProxy)
ginServer.Handle("POST", "/api/system/setWorkspaceDir", model.CheckAuth, setWorkspaceDir)
ginServer.Handle("POST", "/api/system/listWorkspaceDirs", model.CheckAuth, listWorkspaceDirs)
ginServer.Handle("POST", "/api/system/setAppearanceMode", model.CheckAuth, setAppearanceMode)
ginServer.Handle("POST", "/api/system/getSysFonts", model.CheckAuth, getSysFonts)
ginServer.Handle("POST", "/api/system/setE2EEPasswd", model.CheckAuth, setE2EEPasswd)
ginServer.Handle("POST", "/api/system/exit", model.CheckAuth, exit)
ginServer.Handle("POST", "/api/system/setUILayout", model.CheckAuth, setUILayout)
ginServer.Handle("POST", "/api/system/getConf", model.CheckAuth, getConf)
ginServer.Handle("POST", "/api/system/checkUpdate", model.CheckAuth, checkUpdate)
ginServer.Handle("POST", "/api/account/login", model.CheckAuth, login)
ginServer.Handle("POST", "/api/account/checkActivationcode", model.CheckAuth, checkActivationcode)
ginServer.Handle("POST", "/api/account/useActivationcode", model.CheckAuth, useActivationcode)
ginServer.Handle("POST", "/api/account/deactivate", model.CheckAuth, deactivateUser)
ginServer.Handle("POST", "/api/notebook/lsNotebooks", model.CheckAuth, lsNotebooks)
ginServer.Handle("POST", "/api/notebook/openNotebook", model.CheckAuth, openNotebook)
ginServer.Handle("POST", "/api/notebook/closeNotebook", model.CheckAuth, closeNotebook)
ginServer.Handle("POST", "/api/notebook/getNotebookConf", model.CheckAuth, getNotebookConf)
ginServer.Handle("POST", "/api/notebook/setNotebookConf", model.CheckAuth, setNotebookConf)
ginServer.Handle("POST", "/api/notebook/createNotebook", model.CheckAuth, createNotebook)
ginServer.Handle("POST", "/api/notebook/removeNotebook", model.CheckAuth, removeNotebook)
ginServer.Handle("POST", "/api/notebook/renameNotebook", model.CheckAuth, renameNotebook)
ginServer.Handle("POST", "/api/notebook/changeSortNotebook", model.CheckAuth, changeSortNotebook)
ginServer.Handle("POST", "/api/notebook/setNotebookIcon", model.CheckAuth, setNotebookIcon)
ginServer.Handle("POST", "/api/filetree/searchDocs", model.CheckAuth, searchDocs)
ginServer.Handle("POST", "/api/filetree/listDocsByPath", model.CheckAuth, listDocsByPath)
ginServer.Handle("POST", "/api/filetree/getDoc", model.CheckAuth, getDoc)
ginServer.Handle("POST", "/api/filetree/getDocNameTemplate", model.CheckAuth, getDocNameTemplate)
ginServer.Handle("POST", "/api/filetree/changeSort", model.CheckAuth, changeSort)
ginServer.Handle("POST", "/api/filetree/lockFile", model.CheckAuth, lockFile)
ginServer.Handle("POST", "/api/filetree/createDocWithMd", model.CheckAuth, model.CheckReadonly, createDocWithMd)
ginServer.Handle("POST", "/api/filetree/createDailyNote", model.CheckAuth, model.CheckReadonly, createDailyNote)
ginServer.Handle("POST", "/api/filetree/createDoc", model.CheckAuth, model.CheckReadonly, createDoc)
ginServer.Handle("POST", "/api/filetree/renameDoc", model.CheckAuth, model.CheckReadonly, renameDoc)
ginServer.Handle("POST", "/api/filetree/removeDoc", model.CheckAuth, model.CheckReadonly, removeDoc)
ginServer.Handle("POST", "/api/filetree/moveDoc", model.CheckAuth, model.CheckReadonly, moveDoc)
ginServer.Handle("POST", "/api/filetree/duplicateDoc", model.CheckAuth, model.CheckReadonly, duplicateDoc)
ginServer.Handle("POST", "/api/filetree/getHPathByPath", model.CheckAuth, getHPathByPath)
ginServer.Handle("POST", "/api/filetree/getHPathByID", model.CheckAuth, getHPathByID)
ginServer.Handle("POST", "/api/filetree/getFullHPathByID", model.CheckAuth, getFullHPathByID)
ginServer.Handle("POST", "/api/filetree/doc2Heading", model.CheckAuth, model.CheckReadonly, doc2Heading)
ginServer.Handle("POST", "/api/filetree/heading2Doc", model.CheckAuth, model.CheckReadonly, heading2Doc)
ginServer.Handle("POST", "/api/filetree/li2Doc", model.CheckAuth, model.CheckReadonly, li2Doc)
ginServer.Handle("POST", "/api/filetree/refreshFiletree", model.CheckAuth, model.CheckReadonly, refreshFiletree)
ginServer.Handle("POST", "/api/format/autoSpace", model.CheckAuth, model.CheckReadonly, autoSpace)
ginServer.Handle("POST", "/api/format/netImg2LocalAssets", model.CheckAuth, model.CheckReadonly, netImg2LocalAssets)
ginServer.Handle("POST", "/api/history/getNotebookHistory", model.CheckAuth, getNotebookHistory)
ginServer.Handle("POST", "/api/history/rollbackNotebookHistory", model.CheckAuth, rollbackNotebookHistory)
ginServer.Handle("POST", "/api/history/getAssetsHistory", model.CheckAuth, getAssetsHistory)
ginServer.Handle("POST", "/api/history/rollbackAssetsHistory", model.CheckAuth, rollbackAssetsHistory)
ginServer.Handle("POST", "/api/history/getDocHistory", model.CheckAuth, getDocHistory)
ginServer.Handle("POST", "/api/history/getDocHistoryContent", model.CheckAuth, getDocHistoryContent)
ginServer.Handle("POST", "/api/history/rollbackDocHistory", model.CheckAuth, model.CheckReadonly, rollbackDocHistory)
ginServer.Handle("POST", "/api/history/clearWorkspaceHistory", model.CheckAuth, model.CheckReadonly, clearWorkspaceHistory)
ginServer.Handle("POST", "/api/outline/getDocOutline", model.CheckAuth, getDocOutline)
ginServer.Handle("POST", "/api/bookmark/getBookmark", model.CheckAuth, getBookmark)
ginServer.Handle("POST", "/api/bookmark/renameBookmark", model.CheckAuth, renameBookmark)
ginServer.Handle("POST", "/api/tag/getTag", model.CheckAuth, getTag)
ginServer.Handle("POST", "/api/tag/renameTag", model.CheckAuth, renameTag)
ginServer.Handle("POST", "/api/tag/removeTag", model.CheckAuth, removeTag)
ginServer.Handle("POST", "/api/lute/spinBlockDOM", model.CheckAuth, spinBlockDOM) // 未测试
ginServer.Handle("POST", "/api/lute/html2BlockDOM", model.CheckAuth, html2BlockDOM)
ginServer.Handle("POST", "/api/lute/copyStdMarkdown", model.CheckAuth, copyStdMarkdown)
ginServer.Handle("POST", "/api/query/sql", model.CheckAuth, SQL)
ginServer.Handle("POST", "/api/search/searchTag", model.CheckAuth, searchTag)
ginServer.Handle("POST", "/api/search/searchTemplate", model.CheckAuth, searchTemplate)
ginServer.Handle("POST", "/api/search/searchWidget", model.CheckAuth, searchWidget)
ginServer.Handle("POST", "/api/search/searchRefBlock", model.CheckAuth, searchRefBlock)
ginServer.Handle("POST", "/api/search/searchEmbedBlock", model.CheckAuth, searchEmbedBlock)
ginServer.Handle("POST", "/api/search/fullTextSearchBlock", model.CheckAuth, fullTextSearchBlock)
ginServer.Handle("POST", "/api/search/searchAsset", model.CheckAuth, searchAsset)
ginServer.Handle("POST", "/api/search/findReplace", model.CheckAuth, findReplace)
ginServer.Handle("POST", "/api/block/getBlockInfo", model.CheckAuth, getBlockInfo)
ginServer.Handle("POST", "/api/block/getBlockDOM", model.CheckAuth, getBlockDOM)
ginServer.Handle("POST", "/api/block/getBlockBreadcrumb", model.CheckAuth, getBlockBreadcrumb)
ginServer.Handle("POST", "/api/block/getRefIDs", model.CheckAuth, getRefIDs)
ginServer.Handle("POST", "/api/block/getRefIDsByFileAnnotationID", model.CheckAuth, getRefIDsByFileAnnotationID)
ginServer.Handle("POST", "/api/block/getBlockDefIDsByRefText", model.CheckAuth, getBlockDefIDsByRefText)
ginServer.Handle("POST", "/api/block/getRefText", model.CheckAuth, getRefText)
ginServer.Handle("POST", "/api/block/getBlockWordCount", model.CheckAuth, getBlockWordCount)
ginServer.Handle("POST", "/api/block/getRecentUpdatedBlocks", model.CheckAuth, getRecentUpdatedBlocks)
ginServer.Handle("POST", "/api/block/getDocInfo", model.CheckAuth, getDocInfo)
ginServer.Handle("POST", "/api/block/checkBlockExist", model.CheckAuth, checkBlockExist)
ginServer.Handle("POST", "/api/block/checkBlockFold", model.CheckAuth, checkBlockFold)
ginServer.Handle("POST", "/api/block/insertBlock", model.CheckAuth, insertBlock)
ginServer.Handle("POST", "/api/block/prependBlock", model.CheckAuth, prependBlock)
ginServer.Handle("POST", "/api/block/appendBlock", model.CheckAuth, appendBlock)
ginServer.Handle("POST", "/api/block/updateBlock", model.CheckAuth, updateBlock)
ginServer.Handle("POST", "/api/block/deleteBlock", model.CheckAuth, deleteBlock)
ginServer.Handle("POST", "/api/block/setBlockReminder", model.CheckAuth, setBlockReminder)
ginServer.Handle("POST", "/api/file/getFile", model.CheckAuth, getFile)
ginServer.Handle("POST", "/api/file/putFile", model.CheckAuth, putFile)
ginServer.Handle("POST", "/api/ref/refreshBacklink", model.CheckAuth, refreshBacklink)
ginServer.Handle("POST", "/api/ref/getBacklink", model.CheckAuth, getBacklink)
ginServer.Handle("POST", "/api/ref/createBacklink", model.CheckAuth, model.CheckReadonly, createBacklink)
ginServer.Handle("POST", "/api/attr/getBookmarkLabels", model.CheckAuth, getBookmarkLabels)
ginServer.Handle("POST", "/api/attr/resetBlockAttrs", model.CheckAuth, model.CheckReadonly, resetBlockAttrs)
ginServer.Handle("POST", "/api/attr/setBlockAttrs", model.CheckAuth, model.CheckReadonly, setBlockAttrs)
ginServer.Handle("POST", "/api/attr/getBlockAttrs", model.CheckAuth, getBlockAttrs)
ginServer.Handle("POST", "/api/cloud/getCloudSpace", model.CheckAuth, getCloudSpace)
ginServer.Handle("POST", "/api/backup/getLocalBackup", model.CheckAuth, getLocalBackup)
ginServer.Handle("POST", "/api/backup/createLocalBackup", model.CheckAuth, model.CheckReadonly, createLocalBackup)
ginServer.Handle("POST", "/api/backup/recoverLocalBackup", model.CheckAuth, model.CheckReadonly, recoverLocalBackup)
ginServer.Handle("POST", "/api/backup/uploadLocalBackup", model.CheckAuth, model.CheckReadonly, uploadLocalBackup)
ginServer.Handle("POST", "/api/backup/downloadCloudBackup", model.CheckAuth, model.CheckReadonly, downloadCloudBackup)
ginServer.Handle("POST", "/api/backup/removeCloudBackup", model.CheckAuth, model.CheckReadonly, removeCloudBackup)
ginServer.Handle("POST", "/api/sync/setSyncEnable", model.CheckAuth, setSyncEnable)
ginServer.Handle("POST", "/api/sync/setCloudSyncDir", model.CheckAuth, setCloudSyncDir)
ginServer.Handle("POST", "/api/sync/createCloudSyncDir", model.CheckAuth, model.CheckReadonly, createCloudSyncDir)
ginServer.Handle("POST", "/api/sync/removeCloudSyncDir", model.CheckAuth, model.CheckReadonly, removeCloudSyncDir)
ginServer.Handle("POST", "/api/sync/listCloudSyncDir", model.CheckAuth, listCloudSyncDir)
ginServer.Handle("POST", "/api/sync/performSync", model.CheckAuth, performSync)
ginServer.Handle("POST", "/api/sync/performBootSync", model.CheckAuth, performBootSync)
ginServer.Handle("POST", "/api/sync/getBootSync", model.CheckAuth, getBootSync)
ginServer.Handle("POST", "/api/sync/getSyncDirection", model.CheckAuth, getSyncDirection)
ginServer.Handle("POST", "/api/inbox/getShorthands", model.CheckAuth, getShorthands)
ginServer.Handle("POST", "/api/inbox/removeShorthands", model.CheckAuth, removeShorthands)
ginServer.Handle("POST", "/api/extension/copy", model.CheckAuth, extensionCopy)
ginServer.Handle("POST", "/api/clipboard/readFilePaths", model.CheckAuth, readFilePaths)
ginServer.Handle("POST", "/api/asset/uploadCloud", model.CheckAuth, model.CheckReadonly, uploadCloud)
ginServer.Handle("POST", "/api/asset/insertLocalAssets", model.CheckAuth, model.CheckReadonly, insertLocalAssets)
ginServer.Handle("POST", "/api/asset/resolveAssetPath", model.CheckAuth, resolveAssetPath)
ginServer.Handle("POST", "/api/asset/upload", model.CheckAuth, model.CheckReadonly, model.Upload)
ginServer.Handle("POST", "/api/asset/setFileAnnotation", model.CheckAuth, model.CheckReadonly, setFileAnnotation)
ginServer.Handle("POST", "/api/asset/getFileAnnotation", model.CheckAuth, getFileAnnotation)
ginServer.Handle("POST", "/api/asset/getUnusedAssets", model.CheckAuth, getUnusedAssets)
ginServer.Handle("POST", "/api/asset/removeUnusedAsset", model.CheckAuth, model.CheckReadonly, removeUnusedAsset)
ginServer.Handle("POST", "/api/asset/removeUnusedAssets", model.CheckAuth, model.CheckReadonly, removeUnusedAssets)
ginServer.Handle("POST", "/api/asset/getDocImageAssets", model.CheckAuth, model.CheckReadonly, getDocImageAssets)
ginServer.Handle("POST", "/api/export/batchExportMd", model.CheckAuth, batchExportMd)
ginServer.Handle("POST", "/api/export/exportMd", model.CheckAuth, exportMd)
ginServer.Handle("POST", "/api/export/exportSY", model.CheckAuth, exportSY)
ginServer.Handle("POST", "/api/export/exportMdContent", model.CheckAuth, exportMdContent)
ginServer.Handle("POST", "/api/export/exportHTML", model.CheckAuth, exportHTML)
ginServer.Handle("POST", "/api/export/exportMdHTML", model.CheckAuth, exportMdHTML)
ginServer.Handle("POST", "/api/export/exportDocx", model.CheckAuth, exportDocx)
ginServer.Handle("POST", "/api/export/addPDFOutline", model.CheckAuth, addPDFOutline)
ginServer.Handle("POST", "/api/export/preview", model.CheckAuth, exportPreview)
ginServer.Handle("POST", "/api/export/exportData", model.CheckAuth, exportData)
ginServer.Handle("POST", "/api/export/exportDataInFolder", model.CheckAuth, exportDataInFolder)
ginServer.Handle("POST", "/api/import/importStdMd", model.CheckAuth, model.CheckReadonly, importStdMd)
ginServer.Handle("POST", "/api/import/importData", model.CheckAuth, model.CheckReadonly, importData)
ginServer.Handle("POST", "/api/import/importSY", model.CheckAuth, model.CheckReadonly, importSY)
ginServer.Handle("POST", "/api/template/render", model.CheckAuth, renderTemplate)
ginServer.Handle("POST", "/api/template/docSaveAsTemplate", model.CheckAuth, docSaveAsTemplate)
ginServer.Handle("POST", "/api/transactions", model.CheckAuth, model.CheckReadonly, performTransactions)
ginServer.Handle("POST", "/api/setting/setAccount", model.CheckAuth, setAccount)
ginServer.Handle("POST", "/api/setting/setEditor", model.CheckAuth, setEditor)
ginServer.Handle("POST", "/api/setting/setExport", model.CheckAuth, setExport)
ginServer.Handle("POST", "/api/setting/setFiletree", model.CheckAuth, setFiletree)
ginServer.Handle("POST", "/api/setting/setSearch", model.CheckAuth, setSearch)
ginServer.Handle("POST", "/api/setting/setKeymap", model.CheckAuth, setKeymap)
ginServer.Handle("POST", "/api/setting/setAppearance", model.CheckAuth, setAppearance)
ginServer.Handle("POST", "/api/setting/getCloudUser", model.CheckAuth, getCloudUser)
ginServer.Handle("POST", "/api/setting/logoutCloudUser", model.CheckAuth, logoutCloudUser)
ginServer.Handle("POST", "/api/setting/login2faCloudUser", model.CheckAuth, login2faCloudUser)
ginServer.Handle("POST", "/api/setting/getCustomCSS", model.CheckAuth, getCustomCSS)
ginServer.Handle("POST", "/api/setting/setCustomCSS", model.CheckAuth, setCustomCSS)
ginServer.Handle("POST", "/api/setting/setEmoji", model.CheckAuth, setEmoji)
ginServer.Handle("POST", "/api/setting/setSearchCaseSensitive", model.CheckAuth, setSearchCaseSensitive)
ginServer.Handle("POST", "/api/graph/resetGraph", model.CheckAuth, resetGraph)
ginServer.Handle("POST", "/api/graph/resetLocalGraph", model.CheckAuth, resetLocalGraph)
ginServer.Handle("POST", "/api/graph/getGraph", model.CheckAuth, getGraph)
ginServer.Handle("POST", "/api/graph/getLocalGraph", model.CheckAuth, getLocalGraph)
ginServer.Handle("POST", "/api/bazaar/getBazaarWidget", model.CheckAuth, getBazaarWidget)
ginServer.Handle("POST", "/api/bazaar/installBazaarWidget", model.CheckAuth, installBazaarWidget)
ginServer.Handle("POST", "/api/bazaar/uninstallBazaarWidget", model.CheckAuth, uninstallBazaarWidget)
ginServer.Handle("POST", "/api/bazaar/getBazaarIcon", model.CheckAuth, getBazaarIcon)
ginServer.Handle("POST", "/api/bazaar/installBazaarIcon", model.CheckAuth, installBazaarIcon)
ginServer.Handle("POST", "/api/bazaar/uninstallBazaarIcon", model.CheckAuth, uninstallBazaarIcon)
ginServer.Handle("POST", "/api/bazaar/getBazaarTemplate", model.CheckAuth, getBazaarTemplate)
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/installBazaarTheme", model.CheckAuth, installBazaarTheme)
ginServer.Handle("POST", "/api/bazaar/uninstallBazaarTheme", model.CheckAuth, uninstallBazaarTheme)
ginServer.Handle("POST", "/api/bazaar/getBazaarPackageREAME", model.CheckAuth, getBazaarPackageREAME)
}

205
kernel/api/search.go Normal file
View file

@ -0,0 +1,205 @@
// 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 api
import (
"net/http"
"strings"
"github.com/88250/gulu"
"github.com/88250/lute/html"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func findReplace(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
k := arg["k"].(string)
r := arg["r"].(string)
idsArg := arg["ids"].([]interface{})
var ids []string
for _, id := range idsArg {
ids = append(ids, id.(string))
}
err := model.FindReplace(k, r, ids)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 3000}
return
}
return
}
func searchAsset(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
k := arg["k"].(string)
ret.Data = model.SearchAssetsByName(k)
return
}
func searchTag(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
k := arg["k"].(string)
tags := model.SearchTags(k)
ret.Data = map[string]interface{}{
"tags": tags,
"k": k,
}
}
func searchWidget(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
keyword := arg["k"].(string)
blocks := model.SearchWidget(keyword)
ret.Data = map[string]interface{}{
"blocks": blocks,
"k": keyword,
}
}
func searchTemplate(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
keyword := arg["k"].(string)
blocks := model.SearchTemplate(keyword)
ret.Data = map[string]interface{}{
"blocks": blocks,
"k": keyword,
}
}
func searchEmbedBlock(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
stmt := arg["stmt"].(string)
excludeIDsArg := arg["excludeIDs"].([]interface{})
var excludeIDs []string
for _, excludeID := range excludeIDsArg {
excludeIDs = append(excludeIDs, excludeID.(string))
}
headingMode := 0 // 0带标题下方块
headingModeArg := arg["headingMode"]
if nil != headingModeArg {
headingMode = int(headingModeArg.(float64))
}
blocks := model.SearchEmbedBlock(stmt, excludeIDs, headingMode)
ret.Data = map[string]interface{}{
"blocks": blocks,
}
}
func searchRefBlock(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
rootID := arg["rootID"].(string)
id := arg["id"].(string)
keyword := arg["k"].(string)
beforeLen := int(arg["beforeLen"].(float64))
blocks, newDoc := model.SearchRefBlock(id, rootID, keyword, beforeLen)
ret.Data = map[string]interface{}{
"blocks": blocks,
"newDoc": newDoc,
"k": html.EscapeHTMLStr(keyword),
"reqId": arg["reqId"],
}
}
func fullTextSearchBlock(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
query := arg["query"].(string)
pathArg := arg["path"]
var path string
if nil != pathArg {
path = pathArg.(string)
}
var box string
if "" != path {
box = strings.Split(path, "/")[0]
path = strings.TrimPrefix(path, box)
}
var types map[string]bool
if nil != arg["types"] {
typesArg := arg["types"].(map[string]interface{})
types = map[string]bool{}
for t, b := range typesArg {
types[t] = b.(bool)
}
}
querySyntaxArg := arg["querySyntax"]
var querySyntax bool
if nil != querySyntaxArg {
querySyntax = querySyntaxArg.(bool)
}
blocks := model.FullTextSearchBlock(query, box, path, types, querySyntax)
ret.Data = blocks
}

384
kernel/api/setting.go Normal file
View file

@ -0,0 +1,384 @@
// 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 api
import (
"fmt"
"net/http"
"strings"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/conf"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/sql"
"github.com/siyuan-note/siyuan/kernel/util"
)
func setAccount(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
param, err := gulu.JSON.MarshalJSON(arg)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
account := &conf.Account{}
if err = gulu.JSON.UnmarshalJSON(param, account); nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
model.Conf.Account = account
model.Conf.Save()
ret.Data = model.Conf.Account
}
func setEditor(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
param, err := gulu.JSON.MarshalJSON(arg)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
oldGenerateHistoryInterval := model.Conf.Editor.GenerateHistoryInterval
editor := conf.NewEditor()
if err = gulu.JSON.UnmarshalJSON(param, editor); nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
if "" == editor.PlantUMLServePath {
editor.PlantUMLServePath = "https://www.plantuml.com/plantuml/svg/~1"
}
model.Conf.Editor = editor
model.Conf.Save()
if oldGenerateHistoryInterval != model.Conf.Editor.GenerateHistoryInterval {
model.ChangeHistoryTick(editor.GenerateHistoryInterval)
}
ret.Data = model.Conf.Editor
}
func setExport(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
param, err := gulu.JSON.MarshalJSON(arg)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
export := &conf.Export{}
if err = gulu.JSON.UnmarshalJSON(param, export); nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 5000}
return
}
if "" != export.PandocBin {
if !util.IsValidPandocBin(export.PandocBin) {
ret.Code = -1
ret.Msg = fmt.Sprintf(model.Conf.Language(117), export.PandocBin)
ret.Data = map[string]interface{}{"closeTimeout": 5000}
return
}
}
model.Conf.Export = export
model.Conf.Save()
ret.Data = model.Conf.Export
}
func setFiletree(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
param, err := gulu.JSON.MarshalJSON(arg)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
fileTree := &conf.FileTree{}
if err = gulu.JSON.UnmarshalJSON(param, fileTree); nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
fileTree.RefCreateSavePath = strings.TrimSpace(fileTree.RefCreateSavePath)
if "" != fileTree.RefCreateSavePath {
if !strings.HasSuffix(fileTree.RefCreateSavePath, "/") {
fileTree.RefCreateSavePath += "/"
}
}
model.Conf.FileTree = fileTree
model.Conf.Save()
ret.Data = model.Conf.FileTree
}
func setSearch(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
param, err := gulu.JSON.MarshalJSON(arg)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
s := &conf.Search{}
if err = gulu.JSON.UnmarshalJSON(param, s); nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
if 1 > s.Limit {
s.Limit = 32
}
model.Conf.Search = s
model.Conf.Save()
sql.SetCaseSensitive(s.CaseSensitive)
sql.ClearVirtualRefKeywords()
ret.Data = s
}
func setKeymap(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
param, err := gulu.JSON.MarshalJSON(arg["data"])
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
keymap := &conf.Keymap{}
if err = gulu.JSON.UnmarshalJSON(param, keymap); nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
model.Conf.Keymap = keymap
model.Conf.Save()
}
func setAppearance(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
param, err := gulu.JSON.MarshalJSON(arg)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
appearance := &conf.Appearance{}
if err = gulu.JSON.UnmarshalJSON(param, appearance); nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
model.Conf.Appearance = appearance
model.Conf.Lang = appearance.Lang
model.Conf.Save()
model.InitAppearance()
ret.Data = model.Conf.Appearance
}
func getCloudUser(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
t := arg["token"]
var token string
if nil != t {
token = t.(string)
}
if err := model.RefreshUser(token); nil != err {
ret.Code = 1
ret.Msg = err.Error()
return
}
ret.Data = model.Conf.User
}
func logoutCloudUser(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
model.LogoutUser()
}
func login2faCloudUser(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
token := arg["token"].(string)
code := arg["code"].(string)
data, err := model.Login2fa(token, code)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = data
}
func getCustomCSS(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
themeName := arg["theme"].(string)
customCSS, err := model.ReadCustomCSS(themeName)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
ret.Data = customCSS
}
func setCustomCSS(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
themeName := arg["theme"].(string)
css := arg["css"].(map[string]interface{})
if err := model.WriteCustomCSS(themeName, css); nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
}
func setEmoji(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
argEmoji := arg["emoji"].([]interface{})
var emoji []string
for _, ae := range argEmoji {
emoji = append(emoji, ae.(string))
}
model.Conf.Editor.Emoji = emoji
}
func setSearchCaseSensitive(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
caseSensitive := arg["caseSensitive"].(bool)
model.Conf.Search.CaseSensitive = caseSensitive
model.Conf.Save()
sql.SetCaseSensitive(caseSensitive)
}

46
kernel/api/sql.go Normal file
View file

@ -0,0 +1,46 @@
// 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 api
import (
"net/http"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/sql"
"github.com/siyuan-note/siyuan/kernel/util"
)
func SQL(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
stmt := arg["stmt"].(string)
result, err := sql.Query(stmt)
if nil != err {
ret.Code = 1
ret.Msg = err.Error()
return
}
ret.Data = result
}

154
kernel/api/sync.go Normal file
View file

@ -0,0 +1,154 @@
// 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 api
import (
"net/http"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func getSyncDirection(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
cloudDirName := arg["name"].(string)
ret.Code, ret.Msg = model.GetSyncDirection(cloudDirName)
}
func getBootSync(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
if 1 == model.BootSyncSucc {
ret.Code = 1
ret.Msg = model.Conf.Language(17)
return
}
}
func performSync(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
model.SyncData(false, false, true)
}
func performBootSync(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
model.SyncData(true, false, true)
ret.Code = model.BootSyncSucc
}
func listCloudSyncDir(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
syncDirs, hSize, err := model.ListCloudSyncDir()
if nil != err {
ret.Code = 1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 5000}
return
}
ret.Data = map[string]interface{}{
"syncDirs": syncDirs,
"hSize": hSize,
"checkedSyncDir": model.Conf.Sync.CloudName,
}
}
func removeCloudSyncDir(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
name := arg["name"].(string)
err := model.RemoveCloudSyncDir(name)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 5000}
return
}
ret.Data = model.Conf.Sync.CloudName
}
func createCloudSyncDir(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
name := arg["name"].(string)
err := model.CreateCloudSyncDir(name)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 5000}
return
}
}
func setSyncEnable(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
enabled := arg["enabled"].(bool)
err := model.SetSyncEnable(enabled)
if nil != err {
ret.Code = 1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 5000}
return
}
}
func setCloudSyncDir(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
name := arg["name"].(string)
model.SetCloudSyncDir(name)
}

397
kernel/api/system.go Normal file
View file

@ -0,0 +1,397 @@
// 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 api
import (
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/conf"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func getEmojiConf(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
builtConfPath := filepath.Join(util.AppearancePath, "emojis", "conf.json")
data, err := os.ReadFile(builtConfPath)
if nil != err {
util.LogErrorf("read emojis conf.json failed: %s", err)
ret.Code = -1
ret.Msg = err.Error()
return
}
var conf []map[string]interface{}
if err = gulu.JSON.UnmarshalJSON(data, &conf); nil != err {
util.LogErrorf("unmarshal emojis conf.json failed: %s", err)
ret.Code = -1
ret.Msg = err.Error()
return
}
customConfDir := filepath.Join(util.DataDir, "emojis")
custom := map[string]interface{}{
"id": "custom",
"title": "Custom",
"title_zh_cn": "自定义",
}
items := []map[string]interface{}{}
custom["items"] = items
if gulu.File.IsDir(customConfDir) {
model.CustomEmojis = sync.Map{}
customEmojis, err := os.ReadDir(customConfDir)
if nil != err {
util.LogErrorf("read custom emojis failed: %s", err)
} else {
for _, customEmoji := range customEmojis {
name := customEmoji.Name()
if strings.HasPrefix(name, ".") {
continue
}
if customEmoji.IsDir() {
// 子级
subCustomEmojis, err := os.ReadDir(filepath.Join(customConfDir, name))
if nil != err {
util.LogErrorf("read custom emojis failed: %s", err)
continue
}
for _, subCustomEmoji := range subCustomEmojis {
name = subCustomEmoji.Name()
if strings.HasPrefix(name, ".") {
continue
}
addCustomEmoji(customEmoji.Name()+"/"+name, &items)
}
continue
}
addCustomEmoji(name, &items)
}
}
}
custom["items"] = items
conf = append([]map[string]interface{}{custom}, conf...)
ret.Data = conf
return
}
func addCustomEmoji(name string, items *[]map[string]interface{}) {
ext := filepath.Ext(name)
nameWithoutExt := strings.TrimSuffix(name, ext)
emoji := map[string]interface{}{
"unicode": name,
"description": nameWithoutExt,
"description_zh_cn": nameWithoutExt,
"keywords": nameWithoutExt,
}
*items = append(*items, emoji)
imgSrc := "/emojis/" + name
model.CustomEmojis.Store(nameWithoutExt, imgSrc)
}
func checkUpdate(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
showMsg := arg["showMsg"].(bool)
model.CheckUpdate(showMsg)
}
func getConf(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
ret.Data = model.Conf
}
func setUILayout(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
param, err := gulu.JSON.MarshalJSON(arg["layout"])
if nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
uiLayout := &conf.UILayout{}
if err = gulu.JSON.UnmarshalJSON(param, uiLayout); nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
model.Conf.UILayout = uiLayout
model.Conf.Save()
}
func setAccessAuthCode(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
aac := arg["accessAuthCode"].(string)
model.Conf.AccessAuthCode = aac
model.Conf.Save()
session := util.GetSession(c)
session.AccessAuthCode = aac
session.Save(c)
go func() {
time.Sleep(200 * time.Millisecond)
util.ReloadUI()
}()
return
}
func getSysFonts(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
ret.Data = util.GetSysFonts(model.Conf.Lang)
}
func version(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
ret.Data = util.Ver
}
func currentTime(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
ret.Data = util.CurrentTimeMillis()
}
func bootProgress(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
progress, details := util.GetBootProgressDetails()
ret.Data = map[string]interface{}{"progress": progress, "details": details}
}
func setAppearanceMode(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
mode := int(arg["mode"].(float64))
model.Conf.Appearance.Mode = mode
if 0 == mode {
model.Conf.Appearance.ThemeJS = gulu.File.IsExist(filepath.Join(util.ThemesPath, model.Conf.Appearance.ThemeLight, "theme.js"))
} else {
model.Conf.Appearance.ThemeJS = gulu.File.IsExist(filepath.Join(util.ThemesPath, model.Conf.Appearance.ThemeDark, "theme.js"))
}
model.Conf.Save()
ret.Data = map[string]interface{}{
"appearance": model.Conf.Appearance,
}
}
func setNetworkServe(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
networkServe := arg["networkServe"].(bool)
model.Conf.System.NetworkServe = networkServe
model.Conf.Save()
util.PushMsg(model.Conf.Language(42), 1000*15)
time.Sleep(time.Second * 3)
}
func setUploadErrLog(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
uploadErrLog := arg["uploadErrLog"].(bool)
model.Conf.System.UploadErrLog = uploadErrLog
model.Conf.Save()
util.PushMsg(model.Conf.Language(42), 1000*15)
time.Sleep(time.Second * 3)
}
func setNetworkProxy(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
scheme := arg["scheme"].(string)
host := arg["host"].(string)
port := arg["port"].(string)
model.Conf.System.NetworkProxy = &conf.NetworkProxy{
Scheme: scheme,
Host: host,
Port: port,
}
model.Conf.Save()
util.PushMsg(model.Conf.Language(42), 1000*15)
time.Sleep(time.Second * 3)
}
func setE2EEPasswd(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
var passwd string
mode := int(arg["mode"].(float64))
if 0 == mode { // 使用内建的密码生成
passwd = model.GetBuiltInE2EEPasswd()
} else { // 使用自定义密码
passwd = arg["e2eePasswd"].(string)
passwd = strings.TrimSpace(passwd)
}
if "" == passwd {
ret.Code = -1
ret.Msg = model.Conf.Language(39)
ret.Data = map[string]interface{}{"closeTimeout": 5000}
return
}
newPasswd := util.AESEncrypt(passwd)
if model.Conf.E2EEPasswd == newPasswd {
util.PushMsg(model.Conf.Language(92), 3000)
return
}
util.PushMsg(model.Conf.Language(102), 60*1000)
if err := os.RemoveAll(model.Conf.Backup.GetSaveDir()); nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
if err := os.MkdirAll(model.Conf.Backup.GetSaveDir(), 0755); nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
if err := os.RemoveAll(model.Conf.Sync.GetSaveDir()); nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
if err := os.MkdirAll(model.Conf.Sync.GetSaveDir(), 0755); nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
if err := os.RemoveAll(filepath.Join(util.WorkspaceDir, "incremental")); nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
if err := os.MkdirAll(filepath.Join(util.WorkspaceDir, "incremental"), 0755); nil != err {
ret.Code = -1
ret.Msg = err.Error()
return
}
time.Sleep(1 * time.Second)
model.Conf.E2EEPasswd = newPasswd
model.Conf.E2EEPasswdMode = mode
model.Conf.Save()
util.PushMsg(model.Conf.Language(92), 3000)
time.Sleep(1 * time.Second)
model.SyncData(false, false, true)
}
func addUIProcess(c *gin.Context) {
pid := c.Query("pid")
util.UIProcessIDs.Store(pid, true)
}
func exit(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
forceArg := arg["force"]
var force bool
if nil != forceArg {
force = forceArg.(bool)
}
err := model.Close(force)
if nil != err {
ret.Code = 1
ret.Msg = err.Error() + "<div class=\"fn__space\"></div><button class=\"b3-button b3-button--white\">" + model.Conf.Language(97) + "</button>"
ret.Data = map[string]interface{}{"closeTimeout": 0}
return
}
}

84
kernel/api/tag.go Normal file
View file

@ -0,0 +1,84 @@
// 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 api
import (
"net/http"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func getTag(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
sortParam := arg["sort"]
sortMode := model.Conf.Tag.Sort
if nil != sortParam {
sortMode = int(sortParam.(float64))
}
model.Conf.Tag.Sort = sortMode
model.Conf.Save()
ret.Data = model.BuildTags()
}
func renameTag(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
oldLabel := arg["oldLabel"].(string)
newLabel := arg["newLabel"].(string)
if err := model.RenameTag(oldLabel, newLabel); nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 5000}
return
}
}
func removeTag(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
label := arg["label"].(string)
if err := model.RemoveTag(label); nil != err {
ret.Code = -1
ret.Msg = err.Error()
ret.Data = map[string]interface{}{"closeTimeout": 5000}
return
}
}

71
kernel/api/template.go Normal file
View file

@ -0,0 +1,71 @@
// 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 api
import (
"net/http"
"github.com/88250/gulu"
"github.com/88250/lute/html"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func docSaveAsTemplate(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
id := arg["id"].(string)
overwrite := arg["overwrite"].(bool)
code, err := model.DocSaveAsTemplate(id, overwrite)
if nil != err {
ret.Code = -1
ret.Msg = html.EscapeString(err.Error())
return
}
ret.Code = code
}
func renderTemplate(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
p := arg["path"].(string)
id := arg["id"].(string)
content, err := model.RenderTemplate(p, id)
if nil != err {
ret.Code = -1
ret.Msg = html.EscapeString(err.Error())
return
}
ret.Data = map[string]interface{}{
"path": p,
"content": content,
}
}

108
kernel/api/transaction.go Normal file
View file

@ -0,0 +1,108 @@
// 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 api
import (
"fmt"
"net/http"
"time"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/filesys"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/sql"
"github.com/siyuan-note/siyuan/kernel/util"
)
func performTransactions(c *gin.Context) {
start := time.Now()
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
trans := arg["transactions"]
data, err := gulu.JSON.MarshalJSON(trans)
if nil != err {
ret.Code = -1
ret.Msg = "parses request failed"
return
}
var transactions []*model.Transaction
if err = gulu.JSON.UnmarshalJSON(data, &transactions); nil != err {
ret.Code = -1
ret.Msg = "parses request failed"
return
}
if op := model.IsSetAttrs(&transactions); nil != op {
attrs := map[string]string{}
if err = gulu.JSON.UnmarshalJSON([]byte(op.Data.(string)), &attrs); nil != err {
return
}
err = model.SetBlockAttrs(op.ID, attrs)
} else {
err = model.PerformTransactions(&transactions)
}
if filesys.ErrUnableLockFile == err {
ret.Code = 1
return
}
if model.ErrNotFullyBoot == err {
ret.Code = -1
ret.Msg = fmt.Sprintf(model.Conf.Language(74), int(util.GetBootProgress()))
ret.Data = map[string]interface{}{"closeTimeout": 5000}
return
}
if nil != err {
tx, txErr := sql.BeginTx()
if nil != txErr {
util.LogFatalf("transaction failed: %s", txErr)
return
}
sql.ClearBoxHash(tx)
sql.CommitTx(tx)
util.LogFatalf("transaction failed: %s", err)
return
}
ret.Data = transactions
app := arg["app"].(string)
session := arg["session"].(string)
if model.IsFoldHeading(&transactions) || model.IsUnfoldHeading(&transactions) {
model.WaitForWritingFiles()
}
pushTransactions(app, session, transactions)
elapsed := time.Now().Sub(start).Milliseconds()
c.Header("Server-Timing", fmt.Sprintf("total;dur=%d", elapsed))
}
func pushTransactions(app, session string, transactions []*model.Transaction) {
evt := util.NewCmdResult("transactions", 0, util.PushModeBroadcastExcludeSelf, util.PushModeBroadcastExcludeSelf)
evt.AppId = app
evt.SessionId = session
evt.Data = transactions
util.PushEvent(evt)
}

107
kernel/api/workspace.go Normal file
View file

@ -0,0 +1,107 @@
// 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 api
import (
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/88250/gulu"
"github.com/gin-gonic/gin"
"github.com/siyuan-note/siyuan/kernel/model"
"github.com/siyuan-note/siyuan/kernel/util"
)
func listWorkspaceDirs(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
userHomeConfDir := filepath.Join(util.HomeDir, ".config", "siyuan")
workspaceConf := filepath.Join(userHomeConfDir, "workspace.json")
data, err := os.ReadFile(workspaceConf)
var workspacePaths []string
if err = gulu.JSON.UnmarshalJSON(data, &workspacePaths); nil != err {
util.LogErrorf("unmarshal workspace conf [%s] failed: %s", workspaceConf, err)
return
}
ret.Data = workspacePaths
}
func setWorkspaceDir(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret)
if !ok {
return
}
path := arg["path"].(string)
if util.WorkspaceDir == path {
ret.Code = -1
ret.Msg = model.Conf.Language(78)
ret.Data = map[string]interface{}{"closeTimeout": 3000}
return
}
if gulu.OS.IsWindows() {
installDir := filepath.Dir(util.WorkingDir)
if strings.HasPrefix(path, installDir) {
ret.Code = -1
ret.Msg = model.Conf.Language(98)
ret.Data = map[string]interface{}{"closeTimeout": 5000}
return
}
}
var workspacePaths []string
workspaceConf := filepath.Join(util.HomeDir, ".config", "siyuan", "workspace.json")
data, err := os.ReadFile(workspaceConf)
if nil != err {
util.LogErrorf("read workspace conf failed: %s", err)
} else {
if err = gulu.JSON.UnmarshalJSON(data, &workspacePaths); nil != err {
util.LogErrorf("unmarshal workspace conf failed: %s", err)
}
}
workspacePaths = append(workspacePaths, path)
workspacePaths = util.RemoveDuplicatedElem(workspacePaths)
workspacePaths = util.RemoveElem(workspacePaths, path)
workspacePaths = append(workspacePaths, path) // 切换的工作空间固定放在最后一个
if data, err = gulu.JSON.MarshalJSON(workspacePaths); nil != err {
msg := fmt.Sprintf("marshal workspace conf [%s] failed: %s", workspaceConf, err)
ret.Code = -1
ret.Msg = msg
return
} else {
if err = gulu.File.WriteFileSafer(workspaceConf, data, 0644); nil != err {
msg := fmt.Sprintf("create workspace conf [%s] failed: %s", workspaceConf, err)
ret.Code = -1
ret.Msg = msg
return
}
}
util.PushMsg(model.Conf.Language(42), 1000*15)
time.Sleep(time.Second * 3)
}