diff --git a/kernel/api/block.go b/kernel/api/block.go index d6d584a76..aae2f72e2 100644 --- a/kernel/api/block.go +++ b/kernel/api/block.go @@ -385,7 +385,7 @@ func getRefIDs(c *gin.Context) { } id := arg["id"].(string) - refIDs, refTexts, defIDs := model.GetBlockRefIDs(id) + refIDs, refTexts, defIDs := model.GetBlockRefs(id) ret.Data = map[string][]string{ "refIDs": refIDs, "refTexts": refTexts, diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go index faf8fdcb2..524ddc6c6 100644 --- a/kernel/model/attribute_view.go +++ b/kernel/model/attribute_view.go @@ -19,7 +19,6 @@ package model import ( "bytes" "fmt" - "github.com/siyuan-note/siyuan/kernel/task" "os" "path/filepath" "slices" @@ -3580,12 +3579,3 @@ func updateBoundBlockAvsAttribute(avIDs []string) { av.BatchUpsertBlockRel(avNodes) } } - -func ReloadAttrView(avID string) { - task.AppendAsyncTaskWithDelay(task.ReloadAttributeView, 200*time.Millisecond, pushReloadAttrView, avID) - -} - -func pushReloadAttrView(avID string) { - util.BroadcastByType("protyle", "refreshAttributeView", 0, "", map[string]interface{}{"id": avID}) -} diff --git a/kernel/model/blockinfo.go b/kernel/model/blockinfo.go index b1231844e..8e368181c 100644 --- a/kernel/model/blockinfo.go +++ b/kernel/model/blockinfo.go @@ -215,21 +215,21 @@ func getNodeRefText0(node *ast.Node) string { return ret } -func GetBlockRefIDs(id string) (refIDs, refTexts, defIDs []string) { +func GetBlockRefs(defID string) (refIDs, refTexts, defIDs []string) { refIDs = []string{} refTexts = []string{} defIDs = []string{} - bt := treenode.GetBlockTree(id) + bt := treenode.GetBlockTree(defID) if nil == bt { return } isDoc := bt.ID == bt.RootID - refIDs, refTexts = sql.QueryRefIDsByDefID(id, isDoc) + refIDs, refTexts = sql.QueryRefIDsByDefID(defID, isDoc) if isDoc { - defIDs = sql.QueryChildDefIDsByRootDefID(id) + defIDs = sql.QueryChildDefIDsByRootDefID(defID) } else { - defIDs = append(defIDs, id) + defIDs = append(defIDs, defID) } return } diff --git a/kernel/model/format.go b/kernel/model/format.go index 856ca9242..e08332e01 100644 --- a/kernel/model/format.go +++ b/kernel/model/format.go @@ -31,7 +31,7 @@ func AutoSpace(rootID string) (err error) { logging.LogInfof("formatting tree [%s]...", rootID) util.PushProtyleLoading(rootID, Conf.Language(116)) - defer util.PushProtyleReload(rootID) + defer util.PushReloadProtyle(rootID) WaitForWritingFiles() diff --git a/kernel/model/history.go b/kernel/model/history.go index f2dd72478..44b270213 100644 --- a/kernel/model/history.go +++ b/kernel/model/history.go @@ -284,7 +284,7 @@ func RollbackDocHistory(boxID, historyPath string) (err error) { sql.RemoveTreeQueue(id) sql.IndexTreeQueue(tree) util.PushReloadFiletree() - util.PushProtyleReload(id) + util.PushReloadProtyle(id) util.PushMsg(Conf.Language(102), 3000) IncSync() @@ -302,8 +302,7 @@ func RollbackDocHistory(boxID, historyPath string) (err error) { return } - // 刷新关联的动态锚文本 https://github.com/siyuan-note/siyuan/issues/11575 - refreshDynamicRefText(tree.Root, tree) + refreshProtyle(id) // 刷新页签名 refText := getNodeRefText(tree.Root) diff --git a/kernel/model/push_reload.go b/kernel/model/push_reload.go new file mode 100644 index 000000000..6b015b758 --- /dev/null +++ b/kernel/model/push_reload.go @@ -0,0 +1,215 @@ +// SiYuan - Refactor your thinking +// Copyright (c) 2020-present, b3log.org +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package model + +import ( + "strings" + "time" + + "github.com/88250/gulu" + "github.com/88250/lute/ast" + "github.com/88250/lute/parse" + "github.com/emirpasic/gods/sets/hashset" + "github.com/siyuan-note/siyuan/kernel/av" + "github.com/siyuan-note/siyuan/kernel/sql" + "github.com/siyuan-note/siyuan/kernel/task" + "github.com/siyuan-note/siyuan/kernel/treenode" + "github.com/siyuan-note/siyuan/kernel/util" +) + +func refreshProtyle(rootID string) { + // 刷新关联的引用 + defTree, _ := LoadTreeByBlockID(rootID) + if nil != defTree { + defIDs := sql.QueryChildDefIDsByRootDefID(rootID) + + var defNodes []*ast.Node + ast.Walk(defTree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { + if !entering || !n.IsBlock() { + return ast.WalkContinue + } + + if gulu.Str.Contains(n.ID, defIDs) { + defNodes = append(defNodes, n) + } + return ast.WalkContinue + }) + + for _, def := range defNodes { + refreshDynamicRefText(def, defTree) + } + } + + // 刷新关联的嵌入块 + refIDs, _ := sql.QueryRefIDsByDefID(rootID, true) + var rootIDs []string + bts := treenode.GetBlockTrees(refIDs) + for _, bt := range bts { + rootIDs = append(rootIDs, bt.RootID) + } + rootIDs = gulu.Str.RemoveDuplicatedElem(rootIDs) + for _, id := range rootIDs { + task.AppendAsyncTaskWithDelay(task.ReloadProtyle, 200*time.Millisecond, util.PushReloadProtyle, id) + } +} + +// refreshRefCount 用于刷新定义块处的引用计数。 +func refreshRefCount(rootID, blockID string) { + sql.WaitForWritingDatabase() + + bt := treenode.GetBlockTree(blockID) + if nil == bt { + return + } + + refCounts := sql.QueryRootChildrenRefCount(bt.RootID) + refCount := refCounts[blockID] + var rootRefCount int + for _, count := range refCounts { + rootRefCount += count + } + refIDs, _, _ := GetBlockRefs(blockID) + util.PushSetDefRefCount(rootID, blockID, refIDs, refCount, rootRefCount) +} + +// refreshDynamicRefText 用于刷新块引用的动态锚文本。 +// 该实现依赖了数据库缓存,导致外部调用时可能需要阻塞等待数据库写入后才能获取到 refs +func refreshDynamicRefText(updatedDefNode *ast.Node, updatedTree *parse.Tree) { + changedDefs := map[string]*ast.Node{updatedDefNode.ID: updatedDefNode} + changedTrees := map[string]*parse.Tree{updatedTree.ID: updatedTree} + refreshDynamicRefTexts(changedDefs, changedTrees) +} + +// refreshDynamicRefTexts 用于批量刷新块引用的动态锚文本。 +// 该实现依赖了数据库缓存,导致外部调用时可能需要阻塞等待数据库写入后才能获取到 refs +func refreshDynamicRefTexts(updatedDefNodes map[string]*ast.Node, updatedTrees map[string]*parse.Tree) { + // 1. 更新引用的动态锚文本 + treeRefNodeIDs := map[string]*hashset.Set{} + var changedParentNodes []*ast.Node + for _, updateNode := range updatedDefNodes { + refs, parentNodes := getRefsCacheByDefNode(updateNode) + for _, ref := range refs { + if refIDs, ok := treeRefNodeIDs[ref.RootID]; !ok { + refIDs = hashset.New() + refIDs.Add(ref.BlockID) + treeRefNodeIDs[ref.RootID] = refIDs + } else { + refIDs.Add(ref.BlockID) + } + } + if 0 < len(parentNodes) { + changedParentNodes = append(changedParentNodes, parentNodes...) + } + } + if 0 < len(changedParentNodes) { + for _, parent := range changedParentNodes { + updatedDefNodes[parent.ID] = parent + } + } + + changedRefTree := map[string]*parse.Tree{} + + for refTreeID, refNodeIDs := range treeRefNodeIDs { + refTree, ok := updatedTrees[refTreeID] + if !ok { + var err error + refTree, err = LoadTreeByBlockID(refTreeID) + if err != nil { + continue + } + } + + var refTreeChanged bool + ast.Walk(refTree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { + if !entering { + return ast.WalkContinue + } + + if n.IsBlock() && refNodeIDs.Contains(n.ID) { + changed, changedDefNodes := updateRefText(n, updatedDefNodes) + if !refTreeChanged && changed { + refTreeChanged = true + } + + // 推送动态锚文本节点刷新 + for _, defNode := range changedDefNodes { + if "ref-d" == defNode.refType { + task.AppendAsyncTaskWithDelay(task.SetRefDynamicText, 200*time.Millisecond, util.PushSetRefDynamicText, refTreeID, n.ID, defNode.id, defNode.refText) + } + } + return ast.WalkContinue + } + return ast.WalkContinue + }) + + if refTreeChanged { + changedRefTree[refTreeID] = refTree + sql.UpdateRefsTreeQueue(refTree) + } + } + + // 2. 更新属性视图主键内容 + for _, updatedDefNode := range updatedDefNodes { + avs := updatedDefNode.IALAttr(av.NodeAttrNameAvs) + if "" == avs { + continue + } + + avIDs := strings.Split(avs, ",") + for _, avID := range avIDs { + attrView, parseErr := av.ParseAttributeView(avID) + if nil != parseErr { + continue + } + + changedAv := false + blockValues := attrView.GetBlockKeyValues() + if nil == blockValues { + continue + } + + for _, blockValue := range blockValues.Values { + if blockValue.Block.ID == updatedDefNode.ID { + newContent := getNodeRefText(updatedDefNode) + if newContent != blockValue.Block.Content { + blockValue.Block.Content = newContent + changedAv = true + } + break + } + } + if changedAv { + av.SaveAttributeView(attrView) + ReloadAttrView(avID) + } + } + } + + // 3. 保存变更 + for _, tree := range changedRefTree { + indexWriteTreeUpsertQueue(tree) + } +} + +// ReloadAttrView 用于重新加载属性视图。 +func ReloadAttrView(avID string) { + task.AppendAsyncTaskWithDelay(task.ReloadAttributeView, 200*time.Millisecond, pushReloadAttrView, avID) +} + +func pushReloadAttrView(avID string) { + util.BroadcastByType("protyle", "refreshAttributeView", 0, "", map[string]interface{}{"id": avID}) +} diff --git a/kernel/model/search.go b/kernel/model/search.go index a38c5cbbb..cb8fcb1d2 100644 --- a/kernel/model/search.go +++ b/kernel/model/search.go @@ -795,7 +795,7 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids reloadTreeIDs = gulu.Str.RemoveDuplicatedElem(reloadTreeIDs) for _, id := range reloadTreeIDs { - util.PushProtyleReload(id) + refreshProtyle(id) } util.PushClearProgress() diff --git a/kernel/model/transaction.go b/kernel/model/transaction.go index 322c2a85e..6e93797f5 100644 --- a/kernel/model/transaction.go +++ b/kernel/model/transaction.go @@ -34,7 +34,6 @@ import ( "github.com/88250/lute/editor" "github.com/88250/lute/lex" "github.com/88250/lute/parse" - "github.com/emirpasic/gods/sets/hashset" "github.com/siyuan-note/filelock" "github.com/siyuan-note/logging" "github.com/siyuan-note/siyuan/kernel/av" @@ -384,8 +383,8 @@ func (tx *Transaction) doMove(operation *Operation) (ret *TxErr) { if err = tx.writeTree(targetTree); err != nil { return } - task.AppendAsyncTaskWithDelay(task.SetDefRefCount, 1*time.Second, pushSetDefRefCount, srcTree.ID, srcTree.ID) - task.AppendAsyncTaskWithDelay(task.SetDefRefCount, 1*time.Second, pushSetDefRefCount, targetTree.ID, srcNode.ID) + task.AppendAsyncTaskWithDelay(task.SetDefRefCount, 1*time.Second, refreshRefCount, srcTree.ID, srcTree.ID) + task.AppendAsyncTaskWithDelay(task.SetDefRefCount, 1*time.Second, refreshRefCount, targetTree.ID, srcNode.ID) } return } @@ -465,8 +464,8 @@ func (tx *Transaction) doMove(operation *Operation) (ret *TxErr) { if err = tx.writeTree(targetTree); err != nil { return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: id} } - task.AppendAsyncTaskWithDelay(task.SetDefRefCount, 1*time.Second, pushSetDefRefCount, srcTree.ID, srcTree.ID) - task.AppendAsyncTaskWithDelay(task.SetDefRefCount, 1*time.Second, pushSetDefRefCount, targetTree.ID, srcNode.ID) + task.AppendAsyncTaskWithDelay(task.SetDefRefCount, 1*time.Second, refreshRefCount, srcTree.ID, srcTree.ID) + task.AppendAsyncTaskWithDelay(task.SetDefRefCount, 1*time.Second, refreshRefCount, targetTree.ID, srcNode.ID) } return } @@ -770,7 +769,7 @@ func (tx *Transaction) doDelete(operation *Operation) (ret *TxErr) { if nil != defTree { defNode := treenode.GetNodeInTree(defTree, defID) if nil != defNode { - task.AppendAsyncTaskWithDelay(task.SetDefRefCount, 1*time.Second, pushSetDefRefCount, defTree.ID, defNode.ID) + task.AppendAsyncTaskWithDelay(task.SetDefRefCount, 1*time.Second, refreshRefCount, defTree.ID, defNode.ID) } } } @@ -1089,7 +1088,7 @@ func (tx *Transaction) doInsert(operation *Operation) (ret *TxErr) { if nil != defTree { defNode := treenode.GetNodeInTree(defTree, defID) if nil != defNode { - task.AppendAsyncTaskWithDelay(task.SetDefRefCount, 1*time.Second, pushSetDefRefCount, defTree.ID, defNode.ID) + task.AppendAsyncTaskWithDelay(task.SetDefRefCount, 1*time.Second, refreshRefCount, defTree.ID, defNode.ID) } } } @@ -1187,7 +1186,7 @@ func (tx *Transaction) doUpdate(operation *Operation) (ret *TxErr) { if nil != defTree { defNode := treenode.GetNodeInTree(defTree, defID) if nil != defNode { - task.AppendAsyncTaskWithDelay(task.SetDefRefCount, 1*time.Second, pushSetDefRefCount, defTree.ID, defNode.ID) + task.AppendAsyncTaskWithDelay(task.SetDefRefCount, 1*time.Second, refreshRefCount, defTree.ID, defNode.ID) } } } @@ -1240,24 +1239,6 @@ func getRefDefIDs(node *ast.Node) (refDefIDs []string) { return } -func pushSetDefRefCount(rootID, blockID string) { - sql.WaitForWritingDatabase() - - bt := treenode.GetBlockTree(blockID) - if nil == bt { - return - } - - refCounts := sql.QueryRootChildrenRefCount(bt.RootID) - refCount := refCounts[blockID] - var rootRefCount int - for _, count := range refCounts { - rootRefCount += count - } - refIDs, _, _ := GetBlockRefIDs(blockID) - util.PushSetDefRefCount(rootID, blockID, refIDs, refCount, rootRefCount) -} - func upsertAvBlockRel(node *ast.Node) { var avIDs []string ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus { @@ -1514,125 +1495,6 @@ func (tx *Transaction) writeTree(tree *parse.Tree) (err error) { return } -// refreshDynamicRefText 用于刷新块引用的动态锚文本。 -// 该实现依赖了数据库缓存,导致外部调用时可能需要阻塞等待数据库写入后才能获取到 refs -func refreshDynamicRefText(updatedDefNode *ast.Node, updatedTree *parse.Tree) { - changedDefs := map[string]*ast.Node{updatedDefNode.ID: updatedDefNode} - changedTrees := map[string]*parse.Tree{updatedTree.ID: updatedTree} - refreshDynamicRefTexts(changedDefs, changedTrees) -} - -// refreshDynamicRefTexts 用于批量刷新块引用的动态锚文本。 -// 该实现依赖了数据库缓存,导致外部调用时可能需要阻塞等待数据库写入后才能获取到 refs -func refreshDynamicRefTexts(updatedDefNodes map[string]*ast.Node, updatedTrees map[string]*parse.Tree) { - // 1. 更新引用的动态锚文本 - treeRefNodeIDs := map[string]*hashset.Set{} - var changedParentNodes []*ast.Node - for _, updateNode := range updatedDefNodes { - refs, parentNodes := getRefsCacheByDefNode(updateNode) - for _, ref := range refs { - if refIDs, ok := treeRefNodeIDs[ref.RootID]; !ok { - refIDs = hashset.New() - refIDs.Add(ref.BlockID) - treeRefNodeIDs[ref.RootID] = refIDs - } else { - refIDs.Add(ref.BlockID) - } - } - if 0 < len(parentNodes) { - changedParentNodes = append(changedParentNodes, parentNodes...) - } - } - if 0 < len(changedParentNodes) { - for _, parent := range changedParentNodes { - updatedDefNodes[parent.ID] = parent - } - } - - changedRefTree := map[string]*parse.Tree{} - - for refTreeID, refNodeIDs := range treeRefNodeIDs { - refTree, ok := updatedTrees[refTreeID] - if !ok { - var err error - refTree, err = LoadTreeByBlockID(refTreeID) - if err != nil { - continue - } - } - - var refTreeChanged bool - ast.Walk(refTree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { - if !entering { - return ast.WalkContinue - } - - if n.IsBlock() && refNodeIDs.Contains(n.ID) { - changed, changedDefNodes := updateRefText(n, updatedDefNodes) - if !refTreeChanged && changed { - refTreeChanged = true - } - - // 推送动态锚文本节点刷新 - for _, defNode := range changedDefNodes { - if "ref-d" == defNode.refType { - task.AppendAsyncTaskWithDelay(task.SetRefDynamicText, 200*time.Millisecond, util.PushSetRefDynamicText, refTreeID, n.ID, defNode.id, defNode.refText) - } - } - return ast.WalkContinue - } - return ast.WalkContinue - }) - - if refTreeChanged { - changedRefTree[refTreeID] = refTree - sql.UpdateRefsTreeQueue(refTree) - } - } - - // 2. 更新属性视图主键内容 - for _, updatedDefNode := range updatedDefNodes { - avs := updatedDefNode.IALAttr(av.NodeAttrNameAvs) - if "" == avs { - continue - } - - avIDs := strings.Split(avs, ",") - for _, avID := range avIDs { - attrView, parseErr := av.ParseAttributeView(avID) - if nil != parseErr { - continue - } - - changedAv := false - blockValues := attrView.GetBlockKeyValues() - if nil == blockValues { - continue - } - - for _, blockValue := range blockValues.Values { - if blockValue.Block.ID == updatedDefNode.ID { - newContent := getNodeRefText(updatedDefNode) - if newContent != blockValue.Block.Content { - blockValue.Block.Content = newContent - changedAv = true - } - break - } - } - if changedAv { - av.SaveAttributeView(attrView) - ReloadAttrView(avID) - } - } - } - - // 3. 保存变更 - for _, tree := range changedRefTree { - indexWriteTreeUpsertQueue(tree) - } -} - func getRefsCacheByDefNode(updateNode *ast.Node) (ret []*sql.Ref, changedParentNodes []*ast.Node) { ret = sql.GetRefsCacheByDefID(updateNode.ID) if nil != updateNode.Parent && ast.NodeDocument != updateNode.Parent.Type && diff --git a/kernel/task/queue.go b/kernel/task/queue.go index b9f5099fa..93a792ee3 100644 --- a/kernel/task/queue.go +++ b/kernel/task/queue.go @@ -134,6 +134,7 @@ const ( AssetContentDatabaseIndexCommit = "task.asset.database.index.commit" // 资源文件数据库索引提交 CacheVirtualBlockRef = "task.cache.virtualBlockRef" // 缓存虚拟块引用 ReloadAttributeView = "task.reload.attributeView" // 重新加载属性视图 + ReloadProtyle = "task.reload.protyle" // 重新加载编辑器 SetRefDynamicText = "task.ref.setDynamicText" // 设置引用的动态锚文本 SetDefRefCount = "task.def.setRefCount" // 设置定义的引用计数 PushMsg = "task.push.msg" // 推送消息 @@ -151,6 +152,7 @@ var uniqueActions = []string{ AssetContentDatabaseIndexFull, AssetContentDatabaseIndexCommit, ReloadAttributeView, + ReloadProtyle, SetRefDynamicText, SetDefRefCount, } diff --git a/kernel/util/websocket.go b/kernel/util/websocket.go index 8ac67673e..fcb9443fc 100644 --- a/kernel/util/websocket.go +++ b/kernel/util/websocket.go @@ -254,7 +254,7 @@ func PushSaveDoc(rootID, typ string, sources interface{}) { PushEvent(evt) } -func PushProtyleReload(rootID string) { +func PushReloadProtyle(rootID string) { BroadcastByType("protyle", "reload", 0, "", rootID) }