// SiYuan - Build Your Eternal Digital Garden // Copyright (c) 2020-present, b3log.org // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package api import ( "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: 仅当前 ID,1:向上 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) }