From be233229a939255d8b25eddae4abebaf44e1297f Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Tue, 23 Sep 2025 23:22:18 +0800 Subject: [PATCH] :art: Improve fold heading editing https://github.com/siyuan-note/siyuan/issues/15848 Signed-off-by: Daniel <845765@qq.com> --- app/src/protyle/wysiwyg/transaction.ts | 6 +--- kernel/model/heading.go | 25 +++++++++++++++- kernel/model/transaction.go | 41 ++++++++++++++++++++------ kernel/treenode/heading.go | 30 ++++++++++--------- 4 files changed, 73 insertions(+), 29 deletions(-) diff --git a/app/src/protyle/wysiwyg/transaction.ts b/app/src/protyle/wysiwyg/transaction.ts index 86e5f13de..0eee0314a 100644 --- a/app/src/protyle/wysiwyg/transaction.ts +++ b/app/src/protyle/wysiwyg/transaction.ts @@ -6,7 +6,7 @@ import {blockRender} from "../render/blockRender"; import {processRender} from "../util/processCode"; import {highlightRender} from "../render/highlightRender"; import {hasClosestBlock, hasClosestByAttribute, hasTopClosestByAttribute, isInEmbedBlock} from "../util/hasClosest"; -import {setFold, zoomOut} from "../../menus/protyle"; +import {zoomOut} from "../../menus/protyle"; import {disabledProtyle, enableProtyle, onGet} from "../util/onGet"; /// #if !MOBILE import {getAllModels} from "../../layout/getAll"; @@ -1061,10 +1061,6 @@ export const turnsIntoTransaction = (options: { const undoOperations: IOperation[] = []; let previousId: string; selectsElement.forEach((item, index) => { - if ((options.type === "Blocks2Ps" || options.type === "Blocks2Hs") && - item.getAttribute("data-type") === "NodeHeading" && item.getAttribute("fold") === "1") { - setFold(options.protyle, item, undefined, undefined, false); - } item.classList.remove("protyle-wysiwyg--select"); item.removeAttribute("select-start"); item.removeAttribute("select-end"); diff --git a/kernel/model/heading.go b/kernel/model/heading.go index f9e3cd006..89d94e5dc 100644 --- a/kernel/model/heading.go +++ b/kernel/model/heading.go @@ -88,6 +88,30 @@ func (tx *Transaction) doUnfoldHeading(operation *Operation) (ret *TxErr) { return &TxErr{code: TxErrCodeBlockNotFound, id: headingID} } + luteEngine := NewLute() + parentFoldedHeading := treenode.GetParentFoldedHeading(heading) + if nil != parentFoldedHeading { + // 如果当前标题在上方某个折叠的标题下方,则展开上方那个折叠标题以保持一致性 + children := treenode.HeadingChildren(parentFoldedHeading) + for _, child := range children { + ast.Walk(child, func(n *ast.Node, entering bool) ast.WalkStatus { + if !entering || !n.IsBlock() { + return ast.WalkContinue + } + + n.RemoveIALAttr("heading-fold") + n.RemoveIALAttr("fold") + return ast.WalkContinue + }) + } + parentFoldedHeading.RemoveIALAttr("fold") + parentFoldedHeading.RemoveIALAttr("heading-fold") + go func() { + tx.WaitForCommit() + ReloadProtyle(tree.ID) + }() + } + children := treenode.HeadingChildren(heading) for _, child := range children { ast.Walk(child, func(n *ast.Node, entering bool) ast.WalkStatus { @@ -116,7 +140,6 @@ func (tx *Transaction) doUnfoldHeading(operation *Operation) (ret *TxErr) { // 展开折叠的标题后显示块引用计数 Display reference counts after unfolding headings https://github.com/siyuan-note/siyuan/issues/13618 fillBlockRefCount(children) - luteEngine := NewLute() operation.RetData = renderBlockDOMByNodes(children, luteEngine) return } diff --git a/kernel/model/transaction.go b/kernel/model/transaction.go index 6e03a1bff..1690fc9ae 100644 --- a/kernel/model/transaction.go +++ b/kernel/model/transaction.go @@ -1255,10 +1255,16 @@ func (tx *Transaction) doInsert(operation *Operation) (ret *TxErr) { } node.InsertAfter(insertedNode) - if treenode.IsUnderFoldedHeading(insertedNode) { - // 保持在标题下的折叠状态 - insertedNode.SetIALAttr("fold", "1") - insertedNode.SetIALAttr("heading-fold", "1") + if parentFoldedHeading := treenode.GetParentFoldedHeading(insertedNode); nil != parentFoldedHeading { + ast.Walk(insertedNode, func(n *ast.Node, entering bool) ast.WalkStatus { + if !entering || !n.IsBlock() { + return ast.WalkContinue + } + + n.SetIALAttr("fold", "1") + n.SetIALAttr("heading-fold", "1") + return ast.WalkContinue + }) } } else { node = treenode.GetNodeInTree(tree, operation.ParentID) @@ -1510,14 +1516,23 @@ func (tx *Transaction) doUpdate(operation *Operation) (ret *TxErr) { oldNode.InsertAfter(updatedNode) oldNode.Unlink() - if treenode.IsUnderFoldedHeading(updatedNode) { - // 保持在标题下的折叠状态 - updatedNode.SetIALAttr("fold", "1") - updatedNode.SetIALAttr("heading-fold", "1") + parentFoldedHeading := treenode.GetParentFoldedHeading(updatedNode) + if nil != parentFoldedHeading { + children := treenode.HeadingChildren(parentFoldedHeading) + for _, child := range children { + ast.Walk(child, func(n *ast.Node, entering bool) ast.WalkStatus { + if !entering || !n.IsBlock() { + return ast.WalkContinue + } + + n.SetIALAttr("fold", "1") + n.SetIALAttr("heading-fold", "1") + return ast.WalkContinue + }) + } } createdUpdated(updatedNode) - tx.nodes[updatedNode.ID] = updatedNode if err = tx.writeTree(tree); err != nil { return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: id} @@ -1544,6 +1559,14 @@ func (tx *Transaction) doUpdate(operation *Operation) (ret *TxErr) { } } } + + if oldNode.HeadingLevel != updatedNode.HeadingLevel { + // 编辑折叠标题下方块,并且这个块的标题层级发生了变化(比如从 H2 变为 H3 或者从标题块变成段落块),则刷新所有编辑器以保持一致性 + go func() { + tx.WaitForCommit() + ReloadProtyle(tree.ID) + }() + } return } diff --git a/kernel/treenode/heading.go b/kernel/treenode/heading.go index 26ebd8102..d48b3aca0 100644 --- a/kernel/treenode/heading.go +++ b/kernel/treenode/heading.go @@ -85,29 +85,31 @@ func GetHeadingFold(nodes []*ast.Node) (ret []*ast.Node) { return } -func IsUnderFoldedHeading(node *ast.Node) bool { +func GetParentFoldedHeading(node *ast.Node) (parentFoldedHeading *ast.Node) { currentLevel := 7 if ast.NodeHeading == node.Type { currentLevel = node.HeadingLevel } for n := node.Previous; nil != n; n = n.Previous { - if ast.NodeHeading == n.Type { - if n.HeadingLevel >= currentLevel { - break - } - currentLevel = n.HeadingLevel + if ast.NodeHeading != n.Type { + continue + } - if "1" == n.IALAttr("fold") { - if ast.NodeHeading != node.Type { - return true - } - if n.HeadingLevel > node.HeadingLevel { - return true - } + if n.HeadingLevel >= currentLevel { + break + } + currentLevel = n.HeadingLevel + + if "1" == n.IALAttr("fold") { + if ast.NodeHeading != node.Type { + parentFoldedHeading = n + } + if n.HeadingLevel < node.HeadingLevel { + parentFoldedHeading = n } } } - return false + return } func HeadingChildren(heading *ast.Node) (ret []*ast.Node) {