Просмотр исходного кода

:art: Refresh associated blocks after find-replacing and rolling back doc
https://github.com/siyuan-note/siyuan/issues/12439
https://github.com/siyuan-note/siyuan/issues/12438

Daniel 10 месяцев назад
Родитель
Сommit
8b2c08439f

+ 1 - 1
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,

+ 0 - 10
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})
-}

+ 5 - 5
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
 }

+ 1 - 1
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()
 

+ 2 - 3
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)

+ 215 - 0
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 <https://www.gnu.org/licenses/>.
+
+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})
+}

+ 1 - 1
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()

+ 7 - 145
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 &&

+ 2 - 0
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,
 }

+ 1 - 1
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)
 }