diff --git a/kernel/api/block.go b/kernel/api/block.go index aae2f72e2..d2c34ed96 100644 --- a/kernel/api/block.go +++ b/kernel/api/block.go @@ -278,6 +278,30 @@ func getDocInfo(c *gin.Context) { ret.Data = info } +func getDocsInfo(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)) + } + queryRefCount := arg["refCount"].(bool) + queryAv := arg["av"].(bool) + info := model.GetDocsInfo(ids, queryRefCount, queryAv) + if nil == info { + ret.Code = -1 + ret.Msg = fmt.Sprintf(model.Conf.Language(15), ids) + return + } + ret.Data = info +} + func getRecentUpdatedBlocks(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret) diff --git a/kernel/api/router.go b/kernel/api/router.go index 5463e6cd9..8632aca40 100644 --- a/kernel/api/router.go +++ b/kernel/api/router.go @@ -182,6 +182,7 @@ func ServeAPI(ginServer *gin.Engine) { ginServer.Handle("POST", "/api/block/getContentWordCount", model.CheckAuth, getContentWordCount) ginServer.Handle("POST", "/api/block/getRecentUpdatedBlocks", model.CheckAuth, getRecentUpdatedBlocks) ginServer.Handle("POST", "/api/block/getDocInfo", model.CheckAuth, getDocInfo) + ginServer.Handle("POST", "/api/block/getDocsInfo", model.CheckAuth, getDocsInfo) 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, model.CheckAdminRole, model.CheckReadonly, insertBlock) diff --git a/kernel/filesys/tree.go b/kernel/filesys/tree.go index 641574746..eb0293e54 100644 --- a/kernel/filesys/tree.go +++ b/kernel/filesys/tree.go @@ -21,10 +21,6 @@ import ( "encoding/json" "errors" "fmt" - "os" - "path/filepath" - "strings" - "github.com/88250/lute" "github.com/88250/lute/parse" "github.com/88250/lute/render" @@ -34,23 +30,39 @@ import ( "github.com/siyuan-note/siyuan/kernel/cache" "github.com/siyuan-note/siyuan/kernel/treenode" "github.com/siyuan-note/siyuan/kernel/util" + "os" + "path/filepath" + "strings" + "sync" ) func LoadTrees(ids []string) (ret map[string]*parse.Tree) { - ret, tmpCache := map[string]*parse.Tree{}, map[string]*parse.Tree{} + ret = map[string]*parse.Tree{} bts := treenode.GetBlockTrees(ids) luteEngine := util.NewLute() - for id, bt := range bts { - tree := tmpCache[bt.RootID] - if nil == tree { - tree, _ = LoadTree(bt.BoxID, bt.Path, luteEngine) - if nil == tree { - logging.LogWarnf("load tree [%s] failed: %s", id, bt.Path) - continue - } - tmpCache[bt.RootID] = tree + var boxIDs []string + var paths []string + seen := make(map[string]bool) + for _, bt := range bts { + key := bt.BoxID + bt.Path + if !seen[key] { + seen[key] = true + boxIDs = append(boxIDs, bt.BoxID) + paths = append(paths, bt.Path) } - ret[id] = tree + } + + trees, errs := BatchLoadTrees(boxIDs, paths, luteEngine) + + for i := range trees { + tree := trees[i] + err := errs[i] + if err != nil || tree == nil { + logging.LogErrorf("load tree failed: %s", err) + continue + } + + ret[tree.ID] = tree } return } @@ -67,6 +79,31 @@ func LoadTree(boxID, p string, luteEngine *lute.Lute) (ret *parse.Tree, err erro return } +func BatchLoadTrees(boxIDs, paths []string, luteEngine *lute.Lute) ([]*parse.Tree, []error) { + var wg sync.WaitGroup + results := make([]*parse.Tree, len(paths)) + errors := make([]error, len(paths)) + + for i := range paths { + wg.Add(1) + go func(i int) { + defer wg.Done() + + boxID := boxIDs[i] + path := paths[i] + + tree, err := LoadTree(boxID, path, luteEngine) + + results[i] = tree + errors[i] = err + }(i) + } + + wg.Wait() + + return results, errors +} + func LoadTreeByData(data []byte, boxID, p string, luteEngine *lute.Lute) (ret *parse.Tree, err error) { ret = parseJSON2Tree(boxID, p, data, luteEngine) if nil == ret { diff --git a/kernel/model/blockinfo.go b/kernel/model/blockinfo.go index 8e368181c..5b9fe270a 100644 --- a/kernel/model/blockinfo.go +++ b/kernel/model/blockinfo.go @@ -17,6 +17,7 @@ package model import ( + "github.com/siyuan-note/siyuan/kernel/filesys" "os" "path/filepath" "sort" @@ -123,6 +124,85 @@ func GetDocInfo(blockID string) (ret *BlockInfo) { return } +func GetDocsInfo(blockIDs []string, queryRefCount bool, queryAv bool) (rets []*BlockInfo) { + WaitForWritingFiles() + + trees := filesys.LoadTrees(blockIDs) + for _, blockID := range blockIDs { + tree := trees[blockID] + if nil == tree { + continue + } + title := tree.Root.IALAttr("title") + ret := &BlockInfo{ID: blockID, RootID: tree.Root.ID, Name: title} + ret.IAL = parse.IAL2Map(tree.Root.KramdownIAL) + scrollData := ret.IAL["scroll"] + if 0 < len(scrollData) { + scroll := map[string]interface{}{} + if parseErr := gulu.JSON.UnmarshalJSON([]byte(scrollData), &scroll); nil != parseErr { + logging.LogWarnf("parse scroll data [%s] failed: %s", scrollData, parseErr) + delete(ret.IAL, "scroll") + } else { + if zoomInId := scroll["zoomInId"]; nil != zoomInId { + if !treenode.ExistBlockTree(zoomInId.(string)) { + delete(ret.IAL, "scroll") + } + } else { + if startId := scroll["startId"]; nil != startId { + if !treenode.ExistBlockTree(startId.(string)) { + delete(ret.IAL, "scroll") + } + } + if endId := scroll["endId"]; nil != endId { + if !treenode.ExistBlockTree(endId.(string)) { + delete(ret.IAL, "scroll") + } + } + } + } + } + if queryRefCount { + ret.RefIDs, _ = sql.QueryRefIDsByDefID(blockID, false) + ret.RefCount = len(ret.RefIDs) // 填充块引计数 + } + + if queryAv { + // 填充属性视图角标 Display the database title on the block superscript https://github.com/siyuan-note/siyuan/issues/10545 + avIDs := strings.Split(ret.IAL[av.NodeAttrNameAvs], ",") + for _, avID := range avIDs { + avName, getErr := av.GetAttributeViewName(avID) + if nil != getErr { + continue + } + + if "" == avName { + avName = Conf.language(105) + } + + attrView := &AttrView{ID: avID, Name: avName} + ret.AttrViews = append(ret.AttrViews, attrView) + } + } + + var subFileCount int + boxLocalPath := filepath.Join(util.DataDir, tree.Box) + subFiles, err := os.ReadDir(filepath.Join(boxLocalPath, strings.TrimSuffix(tree.Path, ".sy"))) + if err == nil { + for _, subFile := range subFiles { + if strings.HasSuffix(subFile.Name(), ".sy") { + subFileCount++ + } + } + } + ret.SubFileCount = subFileCount + ret.Icon = tree.Root.IALAttr("icon") + + rets = append(rets, ret) + + } + return +} + func GetBlockRefText(id string) string { bt := treenode.GetBlockTree(id) if nil == bt {