Skip to content

Commit ba639fc

Browse files
committed
⚡ Optimize deletion checks for database-bound blocks #13396
1 parent fbe0bc5 commit ba639fc

5 files changed

Lines changed: 149 additions & 89 deletions

File tree

app/src/protyle/wysiwyg/removeRange.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ class TestElement {
1212
parentElement: TestElement | null = null;
1313
children: TestElement[] = [];
1414
private attributes = new Map<string, string>();
15+
classList = {
16+
contains: (name: string) => this.attributes.get("class")?.split(/\s+/).includes(name) || false,
17+
};
1518

1619
constructor(public name: string, type?: string) {
1720
if (type) {
@@ -28,6 +31,11 @@ class TestElement {
2831
return this;
2932
}
3033

34+
addClass(name: string) {
35+
this.attributes.set("class", name);
36+
return this;
37+
}
38+
3139
get nextElementSibling() {
3240
if (!this.parentElement) {
3341
return null;
@@ -126,6 +134,17 @@ describe("getDeletedBlockElements", () => {
126134
assert.deepEqual(result.elements, [asHTMLElement(root), asHTMLElement(deletedChild)]);
127135
assert.deepEqual(Array.from(result.expansionStopIDs), ["root"]);
128136
});
137+
138+
it("排除查询嵌入块的渲染结果", () => {
139+
const renderedBlock = block("renderedBlock", "NodeParagraph");
140+
const renderedResult = new TestElement("renderedResult").addClass("protyle-wysiwyg__embed")
141+
.append(renderedBlock);
142+
const embed = block("embed", "NodeBlockQueryEmbed", renderedResult);
143+
144+
const result = getDeletedBlockElements([asHTMLElement(embed)], []);
145+
146+
assert.deepEqual(result.elements, [asHTMLElement(embed)]);
147+
});
129148
});
130149

131150
describe("getCrossBlockMergeRemoveElement", () => {

app/src/protyle/wysiwyg/removeRange.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,13 @@ export const getDeletedBlockElements = (removedElements: HTMLElement[], retained
274274
const expansionStopIDs = new Set<string>();
275275
removedElements.forEach(item => {
276276
[item, ...Array.from(item.querySelectorAll<HTMLElement>("[data-node-id]"))].forEach(element => {
277+
let currentElement: HTMLElement | null = element;
278+
while (currentElement && !currentElement.classList.contains("protyle-wysiwyg__embed")) {
279+
currentElement = currentElement.parentElement;
280+
}
281+
if (currentElement) {
282+
return;
283+
}
277284
if (retainedElements.some(retainedElement =>
278285
retainedElement === element || retainedElement.contains(element))) {
279286
return;

kernel/model/block.go

Lines changed: 9 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package model
1818

1919
import (
2020
"bytes"
21-
"errors"
2221
"fmt"
2322
"html"
2423
"path"
@@ -307,55 +306,22 @@ func existBoundBlockGroup(group *blockRefCheckGroup) (ret bool, err error) {
307306
return false, err
308307
}
309308

310-
validBoundAVIDs := map[string][]string{}
311-
for blockID, avIDs := range boundAVIDs {
312-
for _, avID := range avIDs {
313-
if !ast.IsNodeIDPattern(avID) {
314-
continue
315-
}
316-
attrView, parseErr := av.ParseAttributeView(avID)
317-
if errors.Is(parseErr, av.ErrViewNotFound) {
318-
continue
319-
}
320-
if nil != parseErr {
321-
return false, parseErr
322-
}
323-
if nil == attrView {
324-
return false, fmt.Errorf("attribute view [%s] is unavailable", avID)
325-
}
326-
blockValues := attrView.GetBlockKeyValues()
327-
if nil == blockValues {
328-
continue
329-
}
330-
for _, blockValue := range blockValues.Values {
331-
if nil != blockValue && !blockValue.IsDetached && nil != blockValue.Block &&
332-
blockID == blockValue.Block.ID {
333-
validBoundAVIDs[blockID] = append(validBoundAVIDs[blockID], avID)
334-
break
335-
}
336-
}
337-
}
338-
}
339-
if 0 == len(validBoundAVIDs) {
340-
return false, nil
341-
}
342-
343-
validAVIDSet := map[string]struct{}{}
344-
for _, avIDs := range validBoundAVIDs {
309+
avIDSet := map[string]struct{}{}
310+
for _, avIDs := range boundAVIDs {
345311
for _, avID := range avIDs {
346-
validAVIDSet[avID] = struct{}{}
312+
avIDSet[avID] = struct{}{}
347313
}
348314
}
349-
validAVIDs := make([]string, 0, len(validAVIDSet))
350-
for avID := range validAVIDSet {
351-
validAVIDs = append(validAVIDs, avID)
315+
avIDs := make([]string, 0, len(avIDSet))
316+
for avID := range avIDSet {
317+
avIDs = append(avIDs, avID)
352318
}
353-
avBlockRels, err := av.GetBlockRelsByAVIDs(validAVIDs)
319+
avBlockRels, err := av.GetBlockRelsByAVIDs(avIDs)
354320
if nil != err {
355321
return false, err
356322
}
357323
avBlockIDSet := map[string]struct{}{}
358-
for _, avIDs := range validBoundAVIDs {
324+
for _, avIDs := range boundAVIDs {
359325
for _, avID := range avIDs {
360326
for _, blockID := range avBlockRels[avID] {
361327
avBlockIDSet[blockID] = struct{}{}
@@ -366,7 +332,7 @@ func existBoundBlockGroup(group *blockRefCheckGroup) (ret bool, err error) {
366332
for id := range avBlockIDSet {
367333
avBlockIDs = append(avBlockIDs, id)
368334
}
369-
return hasSurvivingAttributeViewBlock(group, validBoundAVIDs, avBlockRels, treenode.GetBlockTrees(avBlockIDs)), nil
335+
return hasSurvivingAttributeViewBlock(group, boundAVIDs, avBlockRels, treenode.GetBlockTrees(avBlockIDs)), nil
370336
}
371337

372338
func hasSurvivingAttributeViewBlock(group *blockRefCheckGroup, boundAVIDs, avBlockRels map[string][]string,

kernel/model/transaction.go

Lines changed: 58 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,10 @@ func performTx(tx *Transaction) (ret *TxErr) {
454454
tx.rollback()
455455
return
456456
}
457+
if "delete" != op.Action || operationIndex == len(tx.DoOperations)-1 ||
458+
"delete" != tx.DoOperations[operationIndex+1].Action {
459+
tx.flushDeletedAttributeViewBlocks()
460+
}
457461
}
458462
}
459463

@@ -503,6 +507,7 @@ func (tx *Transaction) processLargeDelete() bool {
503507
}
504508

505509
tx.doLargeDelete(deleteOps)
510+
tx.flushDeletedAttributeViewBlocks()
506511
return true
507512
}
508513

@@ -539,10 +544,12 @@ func (tx *Transaction) processLargeInsert() bool {
539544

540545
if nil != firstDeleteOp {
541546
tx.doDelete(firstDeleteOp)
547+
tx.flushDeletedAttributeViewBlocks()
542548
}
543549
tx.doLargeInsert(insertOps)
544550
if nil != lastDeleteOp {
545551
tx.doDelete(lastDeleteOp)
552+
tx.flushDeletedAttributeViewBlocks()
546553
}
547554
return true
548555
}
@@ -1214,12 +1221,8 @@ func (tx *Transaction) doDelete0(operation *Operation, tree *parse.Tree) (delete
12141221
}
12151222

12161223
func syncDelete2AvBlock(node *ast.Node, nodeTree *parse.Tree, delChildrenWhenDelParent bool, tx *Transaction) {
1217-
changedAvIDs := syncDelete2AttributeView(node, delChildrenWhenDelParent)
1218-
avIDs := tx.syncDelete2Block(node, nodeTree)
1219-
changedAvIDs = append(changedAvIDs, avIDs...)
1220-
changedAvIDs = gulu.Str.RemoveDuplicatedElem(changedAvIDs)
1221-
1222-
for _, avID := range changedAvIDs {
1224+
tx.collectDeletedAttributeViewBlocks(node, delChildrenWhenDelParent)
1225+
for _, avID := range tx.syncDelete2Block(node, nodeTree) {
12231226
ReloadAttrView(avID)
12241227
}
12251228
}
@@ -1276,63 +1279,69 @@ func (tx *Transaction) syncDelete2Block(node *ast.Node, nodeTree *parse.Tree) (c
12761279
return
12771280
}
12781281

1279-
func syncDelete2AttributeView(node *ast.Node, delChildrenWhenDelParent bool) (changedAvIDs []string) {
1282+
func (tx *Transaction) collectDeletedAttributeViewBlocks(node *ast.Node, delChildrenWhenDelParent bool) {
1283+
collect := func(n *ast.Node) {
1284+
avs := n.IALAttr(av.NodeAttrNameAvs)
1285+
if "" == avs {
1286+
return
1287+
}
1288+
for avID := range strings.SplitSeq(avs, ",") {
1289+
blockIDs := tx.deletedAttrViewBlockIDs[avID]
1290+
if nil == blockIDs {
1291+
blockIDs = map[string]struct{}{}
1292+
tx.deletedAttrViewBlockIDs[avID] = blockIDs
1293+
}
1294+
blockIDs[n.ID] = struct{}{}
1295+
}
1296+
}
12801297
if !delChildrenWhenDelParent {
1281-
changedAvIDs = deleteAttrView(node, changedAvIDs)
1298+
collect(node)
12821299
return
12831300
}
1284-
12851301
ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus {
1286-
if !entering || !n.IsBlock() {
1287-
return ast.WalkContinue
1302+
if entering && n.IsBlock() {
1303+
collect(n)
12881304
}
1289-
1290-
changedAvIDs = append(changedAvIDs, deleteAttrView(n, changedAvIDs)...)
12911305
return ast.WalkContinue
12921306
})
1293-
1294-
changedAvIDs = gulu.Str.RemoveDuplicatedElem(changedAvIDs)
1295-
return
12961307
}
12971308

1298-
func deleteAttrView(n *ast.Node, changedAvIDs []string) []string {
1299-
avs := n.IALAttr(av.NodeAttrNameAvs)
1300-
if "" == avs {
1301-
return nil
1302-
}
1303-
1304-
avIDs := strings.SplitSeq(avs, ",")
1305-
for avID := range avIDs {
1306-
attrView, parseErr := av.ParseAttributeView(avID)
1307-
if nil != parseErr {
1309+
func (tx *Transaction) flushDeletedAttributeViewBlocks() {
1310+
for avID, deletedBlockIDs := range tx.deletedAttrViewBlockIDs {
1311+
attrView, err := av.ParseAttributeView(avID)
1312+
if nil != err || !removeAttributeViewBoundBlocks(attrView, deletedBlockIDs) {
13081313
continue
13091314
}
1315+
regenAttrViewGroups(attrView)
1316+
av.SaveAttributeView(attrView)
1317+
ReloadAttrView(avID)
1318+
}
1319+
tx.deletedAttrViewBlockIDs = map[string]map[string]struct{}{}
1320+
}
13101321

1311-
changedAv := false
1312-
blockValues := attrView.GetBlockKeyValues()
1313-
if nil == blockValues {
1314-
continue
1315-
}
1322+
func removeAttributeViewBoundBlocks(attrView *av.AttributeView, deletedBlockIDs map[string]struct{}) (changed bool) {
1323+
if nil == attrView {
1324+
return false
1325+
}
13161326

1317-
for i, blockValue := range blockValues.Values {
1318-
if nil == blockValue.Block {
1327+
blockValues := attrView.GetBlockKeyValues()
1328+
if nil == blockValues {
1329+
return false
1330+
}
1331+
values := make([]*av.Value, 0, len(blockValues.Values))
1332+
for _, blockValue := range blockValues.Values {
1333+
if nil != blockValue && nil != blockValue.Block {
1334+
if _, deleted := deletedBlockIDs[blockValue.Block.ID]; deleted {
1335+
changed = true
13191336
continue
13201337
}
1321-
1322-
if blockValue.Block.ID == n.ID {
1323-
blockValues.Values = append(blockValues.Values[:i], blockValues.Values[i+1:]...)
1324-
changedAv = true
1325-
break
1326-
}
1327-
}
1328-
1329-
if changedAv {
1330-
regenAttrViewGroups(attrView)
1331-
av.SaveAttributeView(attrView)
1332-
changedAvIDs = append(changedAvIDs, avID)
13331338
}
1339+
values = append(values, blockValue)
13341340
}
1335-
return changedAvIDs
1341+
if changed {
1342+
blockValues.Values = values
1343+
}
1344+
return
13361345
}
13371346

13381347
func (tx *Transaction) doLargeInsert(operations []*Operation) {
@@ -2180,6 +2189,7 @@ type Transaction struct {
21802189

21812190
listItemFoldCandidates []listItemFoldCandidate
21822191
listItemFoldCandidateIDs map[string]struct{}
2192+
deletedAttrViewBlockIDs map[string]map[string]struct{}
21832193

21842194
luteEngine *lute.Lute
21852195
m *sync.Mutex
@@ -2236,6 +2246,7 @@ func (tx *Transaction) begin() (err error) {
22362246
tx.restoredCreatedDocs = nil
22372247
tx.listItemFoldCandidates = nil
22382248
tx.listItemFoldCandidateIDs = map[string]struct{}{}
2249+
tx.deletedAttrViewBlockIDs = map[string]map[string]struct{}{}
22392250
tx.luteEngine = util.NewLute()
22402251
tx.m.Lock()
22412252
tx.state.Store(1)
@@ -2310,6 +2321,7 @@ func (tx *Transaction) commit() (err error) {
23102321
func (tx *Transaction) rollback() {
23112322
tx.trees, tx.nodes, tx.boxIcons, tx.removedCreatedDocs, tx.restoredCreatedDocs = nil, nil, nil, nil, nil
23122323
tx.listItemFoldCandidates, tx.listItemFoldCandidateIDs = nil, nil
2324+
tx.deletedAttrViewBlockIDs = nil
23132325
tx.state.Store(3)
23142326
tx.m.Unlock()
23152327
return
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// SiYuan - Refactor your thinking
2+
// Copyright (c) 2020-present, b3log.org
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
17+
package model
18+
19+
import (
20+
"testing"
21+
22+
"github.com/siyuan-note/siyuan/kernel/av"
23+
)
24+
25+
func TestRemoveAttributeViewBoundBlocks(t *testing.T) {
26+
deletedValue1 := &av.Value{Block: &av.ValueBlock{ID: "20260805000000-deleted1"}}
27+
keptValue := &av.Value{Block: &av.ValueBlock{ID: "20260805000000-kept"}}
28+
deletedValue2 := &av.Value{Block: &av.ValueBlock{ID: "20260805000000-deleted2"}}
29+
nilBlockValue := &av.Value{}
30+
attrView := &av.AttributeView{KeyValues: []*av.KeyValues{{
31+
Key: &av.Key{Type: av.KeyTypeBlock},
32+
Values: []*av.Value{
33+
deletedValue1,
34+
keptValue,
35+
deletedValue2,
36+
nil,
37+
nilBlockValue,
38+
},
39+
}}}
40+
41+
changed := removeAttributeViewBoundBlocks(attrView, map[string]struct{}{
42+
"20260805000000-deleted1": {},
43+
"20260805000000-deleted2": {},
44+
})
45+
46+
if !changed {
47+
t.Fatal("expected bound blocks to be removed")
48+
}
49+
values := attrView.GetBlockKeyValues().Values
50+
if 3 != len(values) || keptValue != values[0] || nil != values[1] || nilBlockValue != values[2] {
51+
t.Fatalf("unexpected remaining values: %#v", values)
52+
}
53+
if removeAttributeViewBoundBlocks(attrView, map[string]struct{}{"20260805000000-missing": {}}) {
54+
t.Fatal("an unrelated block ID should not change the attribute view")
55+
}
56+
}

0 commit comments

Comments
 (0)