🎨 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
This commit is contained in:
Daniel 2024-09-11 17:22:16 +08:00
parent e2cb9fe453
commit 8b2c08439f
No known key found for this signature in database
GPG key ID: 86211BA83DF03017
10 changed files with 235 additions and 167 deletions

View file

@ -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,

View file

@ -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})
}

View file

@ -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
}

View file

@ -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()

View file

@ -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
kernel/model/push_reload.go Normal file
View file

@ -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})
}

View file

@ -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()

View file

@ -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 &&

View file

@ -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,
}

View file

@ -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)
}