From 70e31c71cc938e578cd1b8822f9fcadcbac41a7c Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Tue, 15 Jul 2025 23:39:59 +0800 Subject: [PATCH 1/6] :technologist: Improve kernel API `/api/block/updateBlock` and `/api/block/batchUpdateBlock` https://github.com/siyuan-note/siyuan/issues/15301 --- kernel/api/block_op.go | 82 ++++++++++++++++++++++--------------- kernel/model/assets.go | 2 +- kernel/model/bookmark.go | 4 +- kernel/model/export.go | 2 +- kernel/model/file.go | 4 +- kernel/model/format.go | 2 +- kernel/model/search.go | 2 +- kernel/model/tag.go | 4 +- kernel/model/transaction.go | 2 +- 9 files changed, 59 insertions(+), 45 deletions(-) diff --git a/kernel/api/block_op.go b/kernel/api/block_op.go index c88bdb50e..9c6ebe2f2 100644 --- a/kernel/api/block_op.go +++ b/kernel/api/block_op.go @@ -575,7 +575,6 @@ func updateBlock(c *gin.Context) { return } - var transactions []*model.Transaction if "NodeDocument" == block.Type { oldTree, err := filesys.LoadTree(block.Box, block.Path, luteEngine) if err != nil { @@ -584,39 +583,46 @@ func updateBlock(c *gin.Context) { 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 && ast.NodeList == tree.Root.FirstChild.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, - }, + var toAppends []*ast.Node + for n := tree.Root.FirstChild; nil != n; n = n.Next { + toAppends = append(toAppends, n) + } + for _, n := range toAppends { + oldTree.Root.AppendChild(n) + } + + model.WriteTreeUpsertQueue(oldTree) + model.ReloadProtyle(id) + return + } + + var transactions []*model.Transaction + if "NodeListItem" == block.Type && ast.NodeList == tree.Root.FirstChild.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, }, }, - } + }, } model.PerformTransactions(&transactions) @@ -704,15 +710,22 @@ func batchUpdateBlock(c *gin.Context) { return } var toRemoves []*ast.Node - 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}) + var toAppends []*ast.Node + for n := tree.Root.FirstChild; nil != n; n = n.Next { + toAppends = append(toAppends, n) + } + for _, n := range toAppends { + oldTree.Root.AppendChild(n) + } + + model.WriteTreeUpsertQueue(oldTree) + model.ReloadProtyle(id) } else { if "NodeListItem" == block.Type && ast.NodeList == tree.Root.FirstChild.Type { // 使用 API `api/block/updateBlock` 更新列表项时渲染错误 https://github.com/siyuan-note/siyuan/issues/4658 @@ -731,13 +744,14 @@ func batchUpdateBlock(c *gin.Context) { } } - tx.DoOperations = ops - model.PerformTransactions(&transactions) - model.FlushTxQueue() - - ret.Data = transactions - broadcastTransactions(transactions) + if 0 < len(ops) { + tx.DoOperations = ops + model.PerformTransactions(&transactions) + model.FlushTxQueue() + ret.Data = transactions + broadcastTransactions(transactions) + } } func deleteBlock(c *gin.Context) { diff --git a/kernel/model/assets.go b/kernel/model/assets.go index 6dd1eb21a..207dc48e5 100644 --- a/kernel/model/assets.go +++ b/kernel/model/assets.go @@ -321,7 +321,7 @@ func NetAssets2LocalAssets(rootID string, onlyImg bool, originalURL string) (err util.PushClearMsg(msgId) if 0 < files { msgId = util.PushMsg(Conf.Language(113), 7000) - if err = writeTreeUpsertQueue(tree); err != nil { + if err = WriteTreeUpsertQueue(tree); err != nil { return } util.PushUpdateMsg(msgId, fmt.Sprintf(Conf.Language(120), files), 5000) diff --git a/kernel/model/bookmark.go b/kernel/model/bookmark.go index 80ef3e136..aa38bfe3e 100644 --- a/kernel/model/bookmark.go +++ b/kernel/model/bookmark.go @@ -66,7 +66,7 @@ func RemoveBookmark(bookmark string) (err error) { } util.PushEndlessProgress(fmt.Sprintf(Conf.Language(111), util.EscapeHTML(tree.Root.IALAttr("title")))) - if err = writeTreeUpsertQueue(tree); err != nil { + if err = WriteTreeUpsertQueue(tree); err != nil { util.ClearPushProgress(100) return } @@ -124,7 +124,7 @@ func RenameBookmark(oldBookmark, newBookmark string) (err error) { } util.PushEndlessProgress(fmt.Sprintf(Conf.Language(111), util.EscapeHTML(tree.Root.IALAttr("title")))) - if err = writeTreeUpsertQueue(tree); err != nil { + if err = WriteTreeUpsertQueue(tree); err != nil { util.ClearPushProgress(100) return } diff --git a/kernel/model/export.go b/kernel/model/export.go index ccf92e8ba..2b2b23777 100644 --- a/kernel/model/export.go +++ b/kernel/model/export.go @@ -310,7 +310,7 @@ func Export2Liandi(id string) (err error) { articleId = result.Data.(string) tree, _ = LoadTreeByBlockID(id) // 这里必须重新加载,因为前面导出时已经修改了树结构 tree.Root.SetIALAttr(liandiArticleIdAttrName, articleId) - if err = writeTreeUpsertQueue(tree); err != nil { + if err = WriteTreeUpsertQueue(tree); err != nil { return } } diff --git a/kernel/model/file.go b/kernel/model/file.go index bd3c51ddf..c0fcf4e3e 100644 --- a/kernel/model/file.go +++ b/kernel/model/file.go @@ -922,7 +922,7 @@ func loadNodesByMode(node *ast.Node, inputIndex, mode, size int, isDoc, isHeadin return } -func writeTreeUpsertQueue(tree *parse.Tree) (err error) { +func WriteTreeUpsertQueue(tree *parse.Tree) (err error) { size, err := filesys.WriteTree(tree) if err != nil { return @@ -944,7 +944,7 @@ func indexWriteTreeIndexQueue(tree *parse.Tree) (err error) { func indexWriteTreeUpsertQueue(tree *parse.Tree) (err error) { treenode.UpsertBlockTree(tree) - return writeTreeUpsertQueue(tree) + return WriteTreeUpsertQueue(tree) } func renameWriteJSONQueue(tree *parse.Tree) (err error) { diff --git a/kernel/model/format.go b/kernel/model/format.go index 2a324f47c..35e99d0bb 100644 --- a/kernel/model/format.go +++ b/kernel/model/format.go @@ -68,7 +68,7 @@ func AutoSpace(rootID string) (err error) { newTree.Path = tree.Path newTree.HPath = tree.HPath newTree.Box = tree.Box - err = writeTreeUpsertQueue(newTree) + err = WriteTreeUpsertQueue(newTree) if err != nil { return } diff --git a/kernel/model/search.go b/kernel/model/search.go index e9168a273..19d60e623 100644 --- a/kernel/model/search.go +++ b/kernel/model/search.go @@ -932,7 +932,7 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids } } - if err = writeTreeUpsertQueue(tree); err != nil { + if err = WriteTreeUpsertQueue(tree); err != nil { return } updateNodes[id] = node diff --git a/kernel/model/tag.go b/kernel/model/tag.go index d0d652ce6..ba453ed40 100644 --- a/kernel/model/tag.go +++ b/kernel/model/tag.go @@ -96,7 +96,7 @@ func RemoveTag(label string) (err error) { n.Unlink() } util.PushEndlessProgress(fmt.Sprintf(Conf.Language(111), util.EscapeHTML(tree.Root.IALAttr("title")))) - if err = writeTreeUpsertQueue(tree); err != nil { + if err = WriteTreeUpsertQueue(tree); err != nil { util.ClearPushProgress(100) return } @@ -195,7 +195,7 @@ func RenameTag(oldLabel, newLabel string) (err error) { updateNodes[node.ID] = node } util.PushEndlessProgress(fmt.Sprintf(Conf.Language(111), util.EscapeHTML(tree.Root.IALAttr("title")))) - if err = writeTreeUpsertQueue(tree); err != nil { + if err = WriteTreeUpsertQueue(tree); err != nil { util.ClearPushProgress(100) return } diff --git a/kernel/model/transaction.go b/kernel/model/transaction.go index 0f943951d..5b827807c 100644 --- a/kernel/model/transaction.go +++ b/kernel/model/transaction.go @@ -1553,7 +1553,7 @@ func (tx *Transaction) begin() (err error) { func (tx *Transaction) commit() (err error) { for _, tree := range tx.trees { - if err = writeTreeUpsertQueue(tree); err != nil { + if err = WriteTreeUpsertQueue(tree); err != nil { return } From 56a5f13728aa5b4404abb1ba2ff4c071f4b84efd Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Wed, 16 Jul 2025 00:05:51 +0800 Subject: [PATCH 2/6] :technologist: Improve kernel API `/api/block/updateBlock` and `/api/block/batchUpdateBlock` https://github.com/siyuan-note/siyuan/issues/15301 --- API.md | 16 +---- API_zh_CN.md | 16 +---- kernel/api/block_op.go | 136 ++++++++++++++--------------------------- kernel/model/export.go | 27 +++----- kernel/model/tree.go | 11 ++++ 5 files changed, 66 insertions(+), 140 deletions(-) diff --git a/API.md b/API.md index 14c548c35..45e0ad65d 100644 --- a/API.md +++ b/API.md @@ -782,21 +782,7 @@ Move documents by `id`: { "code": 0, "msg": "", - "data": [ - { - "doOperations": [ - { - "action": "update", - "data": "
foobarbaz
", - "id": "20211230161520-querkps", - "parentID": "", - "previousID": "", - "retData": null - } - ], - "undoOperations": null - } - ] + "data": null } ``` diff --git a/API_zh_CN.md b/API_zh_CN.md index c9694fb25..cbf5ec36f 100644 --- a/API_zh_CN.md +++ b/API_zh_CN.md @@ -776,21 +776,7 @@ { "code": 0, "msg": "", - "data": [ - { - "doOperations": [ - { - "action": "update", - "data": "
foobarbaz
", - "id": "20211230161520-querkps", - "parentID": "", - "previousID": "", - "retData": null - } - ], - "undoOperations": null - } - ] + "data": null } ``` diff --git a/kernel/api/block_op.go b/kernel/api/block_op.go index 9c6ebe2f2..7bd20fa02 100644 --- a/kernel/api/block_op.go +++ b/kernel/api/block_op.go @@ -25,7 +25,6 @@ import ( "github.com/88250/lute/ast" "github.com/88250/lute/parse" "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/treenode" "github.com/siyuan-note/siyuan/kernel/util" @@ -568,20 +567,21 @@ func updateBlock(c *gin.Context) { return } - block, err := model.GetBlock(id, nil) + oldTree, err := model.LoadTreeByBlockID(id) if err != nil { ret.Code = -1 - ret.Msg = "get block failed: " + err.Error() + ret.Msg = "load tree failed: " + err.Error() return } - if "NodeDocument" == block.Type { - oldTree, err := filesys.LoadTree(block.Box, block.Path, luteEngine) - if err != nil { - ret.Code = -1 - ret.Msg = "load tree failed: " + err.Error() - return - } + node := treenode.GetNodeInTree(oldTree, id) + if nil == node { + ret.Code = -1 + ret.Msg = "block not found [id=" + id + "]" + return + } + + if ast.NodeDocument == node.Type { var toRemoves []*ast.Node for n := oldTree.Root.FirstChild; nil != n; n = n.Next { toRemoves = append(toRemoves, n) @@ -600,36 +600,21 @@ func updateBlock(c *gin.Context) { model.WriteTreeUpsertQueue(oldTree) model.ReloadProtyle(id) - return + } else { + if ast.NodeListItem == node.Type && ast.NodeList == tree.Root.FirstChild.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) + tree.Root.FirstChild.ID = id + node.InsertBefore(tree.Root.FirstChild) + node.Unlink() } - var transactions []*model.Transaction - if "NodeListItem" == block.Type && ast.NodeList == tree.Root.FirstChild.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, - }, - }, - }, - } - - model.PerformTransactions(&transactions) - model.FlushTxQueue() - - ret.Data = transactions - broadcastTransactions(transactions) + model.WriteTreeUpsertQueue(oldTree) + model.ReloadProtyle(oldTree.ID) } func batchUpdateBlock(c *gin.Context) { @@ -642,16 +627,7 @@ func batchUpdateBlock(c *gin.Context) { } blocksArg := arg["blocks"].([]interface{}) - - type updateBlockArg struct { - ID string - Data string - DataType string - Block *model.Block - Tree *parse.Tree - } - - var blocks []*updateBlockArg + blocks := map[string]*parse.Tree{} luteEngine := util.NewLute() for _, blockArg := range blocksArg { blockMap := blockArg.(map[string]interface{}) @@ -678,37 +654,26 @@ func batchUpdateBlock(c *gin.Context) { return } - block, err := model.GetBlock(id, nil) + blocks[id] = tree + } + + trees := map[string]*parse.Tree{} + for id, tree := range blocks { + oldTree, err := model.LoadTreeWithCache(id, &trees) if err != nil { ret.Code = -1 - ret.Msg = "get block failed: " + err.Error() + ret.Msg = "load tree failed: " + err.Error() return } - blocks = append(blocks, &updateBlockArg{ - ID: id, - Data: data, - DataType: dataType, - Block: block, - Tree: tree, - }) - } + node := treenode.GetNodeInTree(oldTree, id) + if nil == node { + ret.Code = -1 + ret.Msg = "block not found [id=" + id + "]" + return + } - var ops []*model.Operation - tx := &model.Transaction{} - transactions := []*model.Transaction{tx} - for _, upBlock := range blocks { - block := upBlock.Block - data := upBlock.Data - tree := upBlock.Tree - id := upBlock.ID - if "NodeDocument" == block.Type { - oldTree, err := filesys.LoadTree(block.Box, block.Path, luteEngine) - if err != nil { - ret.Code = -1 - ret.Msg = "load tree failed: " + err.Error() - return - } + if ast.NodeDocument == node.Type { var toRemoves []*ast.Node for n := oldTree.Root.FirstChild; nil != n; n = n.Next { toRemoves = append(toRemoves, n) @@ -723,34 +688,23 @@ func batchUpdateBlock(c *gin.Context) { for _, n := range toAppends { oldTree.Root.AppendChild(n) } - - model.WriteTreeUpsertQueue(oldTree) - model.ReloadProtyle(id) } else { - if "NodeListItem" == block.Type && ast.NodeList == tree.Root.FirstChild.Type { + if ast.NodeListItem == node.Type && ast.NodeList == tree.Root.FirstChild.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) - ops = append(ops, &model.Operation{ - Action: "update", - ID: id, - Data: data, - }) + tree.Root.FirstChild.ID = id + node.InsertBefore(tree.Root.FirstChild) + node.Unlink() } } - if 0 < len(ops) { - tx.DoOperations = ops - model.PerformTransactions(&transactions) - model.FlushTxQueue() - - ret.Data = transactions - broadcastTransactions(transactions) + for _, tree := range trees { + model.WriteTreeUpsertQueue(tree) + model.ReloadProtyle(tree.ID) } } diff --git a/kernel/model/export.go b/kernel/model/export.go index 2b2b23777..e6bb4acfe 100644 --- a/kernel/model/export.go +++ b/kernel/model/export.go @@ -1994,7 +1994,7 @@ func ExportMarkdownContent(id string, refMode, embedMode int, addYfm, fillCSSVar } func exportMarkdownContent(id, ext string, exportRefMode int, defBlockIDs []string, singleFile bool, treeCache *map[string]*parse.Tree) (tree *parse.Tree, exportedMd string, isEmpty bool) { - tree, err := loadTreeWithCache(id, treeCache) + tree, err := LoadTreeWithCache(id, treeCache) if err != nil { logging.LogErrorf("load tree by block id [%s] failed: %s", id, err) return @@ -2788,7 +2788,7 @@ func resolveFootnotesDefs(refFootnotes *[]*refAsFootnotes, currentTree *parse.Tr footnotesDefBlock = &ast.Node{Type: ast.NodeFootnotesDefBlock} var rendered []string for _, foot := range *refFootnotes { - t, err := loadTreeWithCache(foot.defID, treeCache) + t, err := LoadTreeWithCache(foot.defID, treeCache) if nil != err { return } @@ -2924,7 +2924,7 @@ func blockLink2Ref(currentTree *parse.Tree, id string, treeCache *map[string]*pa if nil == b { return } - t, err := loadTreeWithCache(b.RootID, treeCache) + t, err := LoadTreeWithCache(b.RootID, treeCache) if nil != err { return } @@ -2974,7 +2974,7 @@ func collectFootnotesDefs(currentTree *parse.Tree, id string, refFootnotes *[]*r if nil == b { return } - t, err := loadTreeWithCache(b.RootID, treeCache) + t, err := LoadTreeWithCache(b.RootID, treeCache) if nil != err { return } @@ -3265,7 +3265,7 @@ func prepareExportTrees(docPaths []string) (defBlockIDs []string, trees *map[str continue } - tree, err := loadTreeWithCache(rootID, treeCache) + tree, err := LoadTreeWithCache(rootID, treeCache) if err != nil { continue } @@ -3307,7 +3307,7 @@ func exportRefTrees(tree *parse.Tree, defBlockIDs *[]string, retTrees, treeCache if (*treeCache)[defBlock.RootID] != nil { defTree = (*treeCache)[defBlock.RootID] } else { - defTree, err = loadTreeWithCache(defBlock.RootID, treeCache) + defTree, err = LoadTreeWithCache(defBlock.RootID, treeCache) if err != nil { return ast.WalkSkipChildren } @@ -3331,7 +3331,7 @@ func exportRefTrees(tree *parse.Tree, defBlockIDs *[]string, retTrees, treeCache if (*treeCache)[defBlock.RootID] != nil { defTree = (*treeCache)[defBlock.RootID] } else { - defTree, err = loadTreeWithCache(defBlock.RootID, treeCache) + defTree, err = LoadTreeWithCache(defBlock.RootID, treeCache) if err != nil { return ast.WalkSkipChildren } @@ -3370,7 +3370,7 @@ func exportRefTrees(tree *parse.Tree, defBlockIDs *[]string, retTrees, treeCache if (*treeCache)[defBlock.RootID] != nil { defTree = (*treeCache)[defBlock.RootID] } else { - defTree, err = loadTreeWithCache(defBlock.RootID, treeCache) + defTree, err = LoadTreeWithCache(defBlock.RootID, treeCache) if err != nil { continue } @@ -3387,17 +3387,6 @@ func exportRefTrees(tree *parse.Tree, defBlockIDs *[]string, retTrees, treeCache *defBlockIDs = gulu.Str.RemoveDuplicatedElem(*defBlockIDs) } -func loadTreeWithCache(id string, treeCache *map[string]*parse.Tree) (tree *parse.Tree, err error) { - if tree = (*treeCache)[id]; nil != tree { - return - } - tree, err = LoadTreeByBlockID(id) - if nil == err && nil != tree { - (*treeCache)[id] = tree - } - return -} - func getAttrViewTable(attrView *av.AttributeView, view *av.View, query string) (ret *av.Table) { switch view.LayoutType { case av.LayoutTypeGallery: diff --git a/kernel/model/tree.go b/kernel/model/tree.go index cd26fb03b..5200f9303 100644 --- a/kernel/model/tree.go +++ b/kernel/model/tree.go @@ -207,6 +207,17 @@ func LoadTreeByBlockIDWithReindex(id string) (ret *parse.Tree, err error) { return } +func LoadTreeWithCache(id string, treeCache *map[string]*parse.Tree) (tree *parse.Tree, err error) { + if tree = (*treeCache)[id]; nil != tree { + return + } + tree, err = LoadTreeByBlockID(id) + if nil == err && nil != tree { + (*treeCache)[id] = tree + } + return +} + func LoadTreeByBlockID(id string) (ret *parse.Tree, err error) { if !ast.IsNodeIDPattern(id) { stack := logging.ShortStack() From de67799c8f8e676bc849857abadb5c1089ef3b95 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Wed, 16 Jul 2025 00:06:36 +0800 Subject: [PATCH 3/6] :technologist: Improve kernel API `/api/block/updateBlock` and `/api/block/batchUpdateBlock` https://github.com/siyuan-note/siyuan/issues/15301 --- kernel/api/block_op.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/kernel/api/block_op.go b/kernel/api/block_op.go index 7bd20fa02..dc60f2cf9 100644 --- a/kernel/api/block_op.go +++ b/kernel/api/block_op.go @@ -597,9 +597,6 @@ func updateBlock(c *gin.Context) { for _, n := range toAppends { oldTree.Root.AppendChild(n) } - - model.WriteTreeUpsertQueue(oldTree) - model.ReloadProtyle(id) } else { if ast.NodeListItem == node.Type && ast.NodeList == tree.Root.FirstChild.Type { // 使用 API `api/block/updateBlock` 更新列表项时渲染错误 https://github.com/siyuan-note/siyuan/issues/4658 From 858c7e7618cdcfd3c0671d1239fce4abc5bcd2d1 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Wed, 16 Jul 2025 00:16:04 +0800 Subject: [PATCH 4/6] :technologist: Improve kernel API `/api/block/updateBlock` and `/api/block/batchUpdateBlock` https://github.com/siyuan-note/siyuan/issues/15301 --- kernel/api/block_op.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/kernel/api/block_op.go b/kernel/api/block_op.go index dc60f2cf9..353b4f7cb 100644 --- a/kernel/api/block_op.go +++ b/kernel/api/block_op.go @@ -610,7 +610,11 @@ func updateBlock(c *gin.Context) { node.Unlink() } - model.WriteTreeUpsertQueue(oldTree) + if err = model.WriteTreeUpsertQueue(oldTree); err != nil { + ret.Code = -1 + ret.Msg = "write tree upsert queue failed: " + err.Error() + return + } model.ReloadProtyle(oldTree.ID) } @@ -700,7 +704,11 @@ func batchUpdateBlock(c *gin.Context) { } for _, tree := range trees { - model.WriteTreeUpsertQueue(tree) + if err := model.WriteTreeUpsertQueue(tree); nil != err { + ret.Code = -1 + ret.Msg = "write tree upsert queue failed: " + err.Error() + return + } model.ReloadProtyle(tree.ID) } } From 0d3df5d449c04dbb21b41a828a4c634aff1ac6ef Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Wed, 16 Jul 2025 10:01:37 +0800 Subject: [PATCH 5/6] Revert ":technologist: Improve kernel API `/api/block/updateBlock` and `/api/block/batchUpdateBlock` https://github.com/siyuan-note/siyuan/issues/15301" This reverts commit d7c95e1b49f753ab5231d4ba98c2dcf3578067da. --- API.md | 16 +++- API_zh_CN.md | 16 +++- kernel/api/block_op.go | 151 +++++++++++++++++++++--------------- kernel/model/assets.go | 2 +- kernel/model/bookmark.go | 4 +- kernel/model/export.go | 29 ++++--- kernel/model/file.go | 4 +- kernel/model/format.go | 2 +- kernel/model/search.go | 2 +- kernel/model/tag.go | 4 +- kernel/model/transaction.go | 2 +- kernel/model/tree.go | 11 --- 12 files changed, 149 insertions(+), 94 deletions(-) diff --git a/API.md b/API.md index 45e0ad65d..14c548c35 100644 --- a/API.md +++ b/API.md @@ -782,7 +782,21 @@ Move documents by `id`: { "code": 0, "msg": "", - "data": null + "data": [ + { + "doOperations": [ + { + "action": "update", + "data": "
foobarbaz
", + "id": "20211230161520-querkps", + "parentID": "", + "previousID": "", + "retData": null + } + ], + "undoOperations": null + } + ] } ``` diff --git a/API_zh_CN.md b/API_zh_CN.md index cbf5ec36f..c9694fb25 100644 --- a/API_zh_CN.md +++ b/API_zh_CN.md @@ -776,7 +776,21 @@ { "code": 0, "msg": "", - "data": null + "data": [ + { + "doOperations": [ + { + "action": "update", + "data": "
foobarbaz
", + "id": "20211230161520-querkps", + "parentID": "", + "previousID": "", + "retData": null + } + ], + "undoOperations": null + } + ] } ``` diff --git a/kernel/api/block_op.go b/kernel/api/block_op.go index 353b4f7cb..c88bdb50e 100644 --- a/kernel/api/block_op.go +++ b/kernel/api/block_op.go @@ -25,6 +25,7 @@ import ( "github.com/88250/lute/ast" "github.com/88250/lute/parse" "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/treenode" "github.com/siyuan-note/siyuan/kernel/util" @@ -567,55 +568,62 @@ func updateBlock(c *gin.Context) { return } - oldTree, err := model.LoadTreeByBlockID(id) + block, err := model.GetBlock(id, nil) if err != nil { ret.Code = -1 - ret.Msg = "load tree failed: " + err.Error() + ret.Msg = "get block failed: " + err.Error() return } - node := treenode.GetNodeInTree(oldTree, id) - if nil == node { - ret.Code = -1 - ret.Msg = "block not found [id=" + id + "]" - return - } - - if ast.NodeDocument == node.Type { + var transactions []*model.Transaction + if "NodeDocument" == block.Type { + oldTree, err := filesys.LoadTree(block.Box, block.Path, luteEngine) + if err != nil { + 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() } - - var toAppends []*ast.Node - for n := tree.Root.FirstChild; nil != n; n = n.Next { - toAppends = append(toAppends, n) - } - for _, n := range toAppends { - oldTree.Root.AppendChild(n) - } + ops = append(ops, &model.Operation{Action: "appendInsert", Data: data, ParentID: id}) + transactions = append(transactions, &model.Transaction{ + DoOperations: ops, + }) } else { - if ast.NodeListItem == node.Type && ast.NodeList == tree.Root.FirstChild.Type { + if "NodeListItem" == block.Type && ast.NodeList == tree.Root.FirstChild.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) - tree.Root.FirstChild.ID = id - node.InsertBefore(tree.Root.FirstChild) - node.Unlink() + + data = luteEngine.Tree2BlockDOM(tree, luteEngine.RenderOptions) + transactions = []*model.Transaction{ + { + DoOperations: []*model.Operation{ + { + Action: "update", + ID: id, + Data: data, + }, + }, + }, + } } - if err = model.WriteTreeUpsertQueue(oldTree); err != nil { - ret.Code = -1 - ret.Msg = "write tree upsert queue failed: " + err.Error() - return - } - model.ReloadProtyle(oldTree.ID) + model.PerformTransactions(&transactions) + model.FlushTxQueue() + + ret.Data = transactions + broadcastTransactions(transactions) } func batchUpdateBlock(c *gin.Context) { @@ -628,7 +636,16 @@ func batchUpdateBlock(c *gin.Context) { } blocksArg := arg["blocks"].([]interface{}) - blocks := map[string]*parse.Tree{} + + type updateBlockArg struct { + ID string + Data string + DataType string + Block *model.Block + Tree *parse.Tree + } + + var blocks []*updateBlockArg luteEngine := util.NewLute() for _, blockArg := range blocksArg { blockMap := blockArg.(map[string]interface{}) @@ -655,62 +672,72 @@ func batchUpdateBlock(c *gin.Context) { return } - blocks[id] = tree - } - - trees := map[string]*parse.Tree{} - for id, tree := range blocks { - oldTree, err := model.LoadTreeWithCache(id, &trees) + block, err := model.GetBlock(id, nil) if err != nil { ret.Code = -1 - ret.Msg = "load tree failed: " + err.Error() + ret.Msg = "get block failed: " + err.Error() return } - node := treenode.GetNodeInTree(oldTree, id) - if nil == node { - ret.Code = -1 - ret.Msg = "block not found [id=" + id + "]" - return - } + blocks = append(blocks, &updateBlockArg{ + ID: id, + Data: data, + DataType: dataType, + Block: block, + Tree: tree, + }) + } - if ast.NodeDocument == node.Type { + var ops []*model.Operation + tx := &model.Transaction{} + transactions := []*model.Transaction{tx} + for _, upBlock := range blocks { + block := upBlock.Block + data := upBlock.Data + tree := upBlock.Tree + id := upBlock.ID + if "NodeDocument" == block.Type { + oldTree, err := filesys.LoadTree(block.Box, block.Path, luteEngine) + if err != nil { + ret.Code = -1 + ret.Msg = "load tree failed: " + err.Error() + return + } var toRemoves []*ast.Node + 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() } - var toAppends []*ast.Node - for n := tree.Root.FirstChild; nil != n; n = n.Next { - toAppends = append(toAppends, n) - } - for _, n := range toAppends { - oldTree.Root.AppendChild(n) - } + ops = append(ops, &model.Operation{Action: "appendInsert", Data: data, ParentID: id}) } else { - if ast.NodeListItem == node.Type && ast.NodeList == tree.Root.FirstChild.Type { + if "NodeListItem" == block.Type && ast.NodeList == tree.Root.FirstChild.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) - tree.Root.FirstChild.ID = id - node.InsertBefore(tree.Root.FirstChild) - node.Unlink() + + data = luteEngine.Tree2BlockDOM(tree, luteEngine.RenderOptions) + ops = append(ops, &model.Operation{ + Action: "update", + ID: id, + Data: data, + }) } } - for _, tree := range trees { - if err := model.WriteTreeUpsertQueue(tree); nil != err { - ret.Code = -1 - ret.Msg = "write tree upsert queue failed: " + err.Error() - return - } - model.ReloadProtyle(tree.ID) - } + tx.DoOperations = ops + model.PerformTransactions(&transactions) + model.FlushTxQueue() + + ret.Data = transactions + broadcastTransactions(transactions) + } func deleteBlock(c *gin.Context) { diff --git a/kernel/model/assets.go b/kernel/model/assets.go index 207dc48e5..6dd1eb21a 100644 --- a/kernel/model/assets.go +++ b/kernel/model/assets.go @@ -321,7 +321,7 @@ func NetAssets2LocalAssets(rootID string, onlyImg bool, originalURL string) (err util.PushClearMsg(msgId) if 0 < files { msgId = util.PushMsg(Conf.Language(113), 7000) - if err = WriteTreeUpsertQueue(tree); err != nil { + if err = writeTreeUpsertQueue(tree); err != nil { return } util.PushUpdateMsg(msgId, fmt.Sprintf(Conf.Language(120), files), 5000) diff --git a/kernel/model/bookmark.go b/kernel/model/bookmark.go index aa38bfe3e..80ef3e136 100644 --- a/kernel/model/bookmark.go +++ b/kernel/model/bookmark.go @@ -66,7 +66,7 @@ func RemoveBookmark(bookmark string) (err error) { } util.PushEndlessProgress(fmt.Sprintf(Conf.Language(111), util.EscapeHTML(tree.Root.IALAttr("title")))) - if err = WriteTreeUpsertQueue(tree); err != nil { + if err = writeTreeUpsertQueue(tree); err != nil { util.ClearPushProgress(100) return } @@ -124,7 +124,7 @@ func RenameBookmark(oldBookmark, newBookmark string) (err error) { } util.PushEndlessProgress(fmt.Sprintf(Conf.Language(111), util.EscapeHTML(tree.Root.IALAttr("title")))) - if err = WriteTreeUpsertQueue(tree); err != nil { + if err = writeTreeUpsertQueue(tree); err != nil { util.ClearPushProgress(100) return } diff --git a/kernel/model/export.go b/kernel/model/export.go index e6bb4acfe..ccf92e8ba 100644 --- a/kernel/model/export.go +++ b/kernel/model/export.go @@ -310,7 +310,7 @@ func Export2Liandi(id string) (err error) { articleId = result.Data.(string) tree, _ = LoadTreeByBlockID(id) // 这里必须重新加载,因为前面导出时已经修改了树结构 tree.Root.SetIALAttr(liandiArticleIdAttrName, articleId) - if err = WriteTreeUpsertQueue(tree); err != nil { + if err = writeTreeUpsertQueue(tree); err != nil { return } } @@ -1994,7 +1994,7 @@ func ExportMarkdownContent(id string, refMode, embedMode int, addYfm, fillCSSVar } func exportMarkdownContent(id, ext string, exportRefMode int, defBlockIDs []string, singleFile bool, treeCache *map[string]*parse.Tree) (tree *parse.Tree, exportedMd string, isEmpty bool) { - tree, err := LoadTreeWithCache(id, treeCache) + tree, err := loadTreeWithCache(id, treeCache) if err != nil { logging.LogErrorf("load tree by block id [%s] failed: %s", id, err) return @@ -2788,7 +2788,7 @@ func resolveFootnotesDefs(refFootnotes *[]*refAsFootnotes, currentTree *parse.Tr footnotesDefBlock = &ast.Node{Type: ast.NodeFootnotesDefBlock} var rendered []string for _, foot := range *refFootnotes { - t, err := LoadTreeWithCache(foot.defID, treeCache) + t, err := loadTreeWithCache(foot.defID, treeCache) if nil != err { return } @@ -2924,7 +2924,7 @@ func blockLink2Ref(currentTree *parse.Tree, id string, treeCache *map[string]*pa if nil == b { return } - t, err := LoadTreeWithCache(b.RootID, treeCache) + t, err := loadTreeWithCache(b.RootID, treeCache) if nil != err { return } @@ -2974,7 +2974,7 @@ func collectFootnotesDefs(currentTree *parse.Tree, id string, refFootnotes *[]*r if nil == b { return } - t, err := LoadTreeWithCache(b.RootID, treeCache) + t, err := loadTreeWithCache(b.RootID, treeCache) if nil != err { return } @@ -3265,7 +3265,7 @@ func prepareExportTrees(docPaths []string) (defBlockIDs []string, trees *map[str continue } - tree, err := LoadTreeWithCache(rootID, treeCache) + tree, err := loadTreeWithCache(rootID, treeCache) if err != nil { continue } @@ -3307,7 +3307,7 @@ func exportRefTrees(tree *parse.Tree, defBlockIDs *[]string, retTrees, treeCache if (*treeCache)[defBlock.RootID] != nil { defTree = (*treeCache)[defBlock.RootID] } else { - defTree, err = LoadTreeWithCache(defBlock.RootID, treeCache) + defTree, err = loadTreeWithCache(defBlock.RootID, treeCache) if err != nil { return ast.WalkSkipChildren } @@ -3331,7 +3331,7 @@ func exportRefTrees(tree *parse.Tree, defBlockIDs *[]string, retTrees, treeCache if (*treeCache)[defBlock.RootID] != nil { defTree = (*treeCache)[defBlock.RootID] } else { - defTree, err = LoadTreeWithCache(defBlock.RootID, treeCache) + defTree, err = loadTreeWithCache(defBlock.RootID, treeCache) if err != nil { return ast.WalkSkipChildren } @@ -3370,7 +3370,7 @@ func exportRefTrees(tree *parse.Tree, defBlockIDs *[]string, retTrees, treeCache if (*treeCache)[defBlock.RootID] != nil { defTree = (*treeCache)[defBlock.RootID] } else { - defTree, err = LoadTreeWithCache(defBlock.RootID, treeCache) + defTree, err = loadTreeWithCache(defBlock.RootID, treeCache) if err != nil { continue } @@ -3387,6 +3387,17 @@ func exportRefTrees(tree *parse.Tree, defBlockIDs *[]string, retTrees, treeCache *defBlockIDs = gulu.Str.RemoveDuplicatedElem(*defBlockIDs) } +func loadTreeWithCache(id string, treeCache *map[string]*parse.Tree) (tree *parse.Tree, err error) { + if tree = (*treeCache)[id]; nil != tree { + return + } + tree, err = LoadTreeByBlockID(id) + if nil == err && nil != tree { + (*treeCache)[id] = tree + } + return +} + func getAttrViewTable(attrView *av.AttributeView, view *av.View, query string) (ret *av.Table) { switch view.LayoutType { case av.LayoutTypeGallery: diff --git a/kernel/model/file.go b/kernel/model/file.go index c0fcf4e3e..bd3c51ddf 100644 --- a/kernel/model/file.go +++ b/kernel/model/file.go @@ -922,7 +922,7 @@ func loadNodesByMode(node *ast.Node, inputIndex, mode, size int, isDoc, isHeadin return } -func WriteTreeUpsertQueue(tree *parse.Tree) (err error) { +func writeTreeUpsertQueue(tree *parse.Tree) (err error) { size, err := filesys.WriteTree(tree) if err != nil { return @@ -944,7 +944,7 @@ func indexWriteTreeIndexQueue(tree *parse.Tree) (err error) { func indexWriteTreeUpsertQueue(tree *parse.Tree) (err error) { treenode.UpsertBlockTree(tree) - return WriteTreeUpsertQueue(tree) + return writeTreeUpsertQueue(tree) } func renameWriteJSONQueue(tree *parse.Tree) (err error) { diff --git a/kernel/model/format.go b/kernel/model/format.go index 35e99d0bb..2a324f47c 100644 --- a/kernel/model/format.go +++ b/kernel/model/format.go @@ -68,7 +68,7 @@ func AutoSpace(rootID string) (err error) { newTree.Path = tree.Path newTree.HPath = tree.HPath newTree.Box = tree.Box - err = WriteTreeUpsertQueue(newTree) + err = writeTreeUpsertQueue(newTree) if err != nil { return } diff --git a/kernel/model/search.go b/kernel/model/search.go index 19d60e623..e9168a273 100644 --- a/kernel/model/search.go +++ b/kernel/model/search.go @@ -932,7 +932,7 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids } } - if err = WriteTreeUpsertQueue(tree); err != nil { + if err = writeTreeUpsertQueue(tree); err != nil { return } updateNodes[id] = node diff --git a/kernel/model/tag.go b/kernel/model/tag.go index ba453ed40..d0d652ce6 100644 --- a/kernel/model/tag.go +++ b/kernel/model/tag.go @@ -96,7 +96,7 @@ func RemoveTag(label string) (err error) { n.Unlink() } util.PushEndlessProgress(fmt.Sprintf(Conf.Language(111), util.EscapeHTML(tree.Root.IALAttr("title")))) - if err = WriteTreeUpsertQueue(tree); err != nil { + if err = writeTreeUpsertQueue(tree); err != nil { util.ClearPushProgress(100) return } @@ -195,7 +195,7 @@ func RenameTag(oldLabel, newLabel string) (err error) { updateNodes[node.ID] = node } util.PushEndlessProgress(fmt.Sprintf(Conf.Language(111), util.EscapeHTML(tree.Root.IALAttr("title")))) - if err = WriteTreeUpsertQueue(tree); err != nil { + if err = writeTreeUpsertQueue(tree); err != nil { util.ClearPushProgress(100) return } diff --git a/kernel/model/transaction.go b/kernel/model/transaction.go index 5b827807c..0f943951d 100644 --- a/kernel/model/transaction.go +++ b/kernel/model/transaction.go @@ -1553,7 +1553,7 @@ func (tx *Transaction) begin() (err error) { func (tx *Transaction) commit() (err error) { for _, tree := range tx.trees { - if err = WriteTreeUpsertQueue(tree); err != nil { + if err = writeTreeUpsertQueue(tree); err != nil { return } diff --git a/kernel/model/tree.go b/kernel/model/tree.go index 5200f9303..cd26fb03b 100644 --- a/kernel/model/tree.go +++ b/kernel/model/tree.go @@ -207,17 +207,6 @@ func LoadTreeByBlockIDWithReindex(id string) (ret *parse.Tree, err error) { return } -func LoadTreeWithCache(id string, treeCache *map[string]*parse.Tree) (tree *parse.Tree, err error) { - if tree = (*treeCache)[id]; nil != tree { - return - } - tree, err = LoadTreeByBlockID(id) - if nil == err && nil != tree { - (*treeCache)[id] = tree - } - return -} - func LoadTreeByBlockID(id string) (ret *parse.Tree, err error) { if !ast.IsNodeIDPattern(id) { stack := logging.ShortStack() From c088ac533db76d4b22dfd745bb165380dd0409e6 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Wed, 16 Jul 2025 10:08:19 +0800 Subject: [PATCH 6/6] :technologist: Improve kernel API `/api/block/updateBlock` and `/api/block/batchUpdateBlock` https://github.com/siyuan-note/siyuan/issues/15301 --- kernel/api/block_op.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/kernel/api/block_op.go b/kernel/api/block_op.go index c88bdb50e..69bfae5b8 100644 --- a/kernel/api/block_op.go +++ b/kernel/api/block_op.go @@ -587,7 +587,9 @@ func updateBlock(c *gin.Context) { 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}) + ops = append(ops, &model.Operation{Action: "delete", ID: n.ID, Data: map[string]interface{}{ + "createEmptyParagraph": false, // 清空文档后前端不要创建空段落 + }}) } for _, n := range toRemoves { n.Unlink() @@ -707,7 +709,9 @@ func batchUpdateBlock(c *gin.Context) { for n := oldTree.Root.FirstChild; nil != n; n = n.Next { toRemoves = append(toRemoves, n) - ops = append(ops, &model.Operation{Action: "delete", ID: n.ID}) + ops = append(ops, &model.Operation{Action: "delete", ID: n.ID, Data: map[string]interface{}{ + "createEmptyParagraph": false, // 清空文档后前端不要创建空段落 + }}) } for _, n := range toRemoves { n.Unlink()