A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 2172 lines 66 kB view raw
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 17package model 18 19import ( 20 "bytes" 21 "errors" 22 "fmt" 23 "path/filepath" 24 "slices" 25 "strconv" 26 "strings" 27 "sync" 28 "sync/atomic" 29 "time" 30 31 "github.com/88250/gulu" 32 "github.com/88250/lute" 33 "github.com/88250/lute/ast" 34 "github.com/88250/lute/editor" 35 "github.com/88250/lute/lex" 36 "github.com/88250/lute/parse" 37 "github.com/siyuan-note/filelock" 38 "github.com/siyuan-note/logging" 39 "github.com/siyuan-note/siyuan/kernel/av" 40 "github.com/siyuan-note/siyuan/kernel/cache" 41 "github.com/siyuan-note/siyuan/kernel/filesys" 42 "github.com/siyuan-note/siyuan/kernel/sql" 43 "github.com/siyuan-note/siyuan/kernel/task" 44 "github.com/siyuan-note/siyuan/kernel/treenode" 45 "github.com/siyuan-note/siyuan/kernel/util" 46) 47 48func IsMoveOutlineHeading(transactions *[]*Transaction) bool { 49 for _, tx := range *transactions { 50 for _, op := range tx.DoOperations { 51 if "moveOutlineHeading" == op.Action { 52 return true 53 } 54 } 55 } 56 return false 57} 58 59func FlushTxQueue() { 60 time.Sleep(time.Duration(50) * time.Millisecond) 61 for 0 < len(txQueue) || isFlushing { 62 time.Sleep(10 * time.Millisecond) 63 } 64} 65 66var ( 67 txQueue = make(chan *Transaction, 7) 68 flushLock = sync.Mutex{} 69 isFlushing = false 70) 71 72func init() { 73 go flushQueue() 74} 75 76func flushQueue() { 77 for { 78 select { 79 case tx := <-txQueue: 80 flushTx(tx) 81 } 82 } 83} 84 85func flushTx(tx *Transaction) { 86 defer logging.Recover() 87 flushLock.Lock() 88 isFlushing = true 89 defer func() { 90 isFlushing = false 91 flushLock.Unlock() 92 }() 93 94 start := time.Now() 95 if txErr := performTx(tx); nil != txErr { 96 switch txErr.code { 97 case TxErrCodeBlockNotFound: 98 util.PushTxErr("Transaction failed", txErr.code, nil) 99 return 100 case TxErrCodeDataIsSyncing: 101 util.PushMsg(Conf.Language(222), 5000) 102 case TxErrHandleAttributeView: 103 util.PushMsg(Conf.language(258), 5000) 104 logging.LogErrorf("handle attribute view failed: %s", txErr.msg) 105 default: 106 txData, _ := gulu.JSON.MarshalJSON(tx) 107 logging.LogFatalf(logging.ExitCodeFatal, "transaction failed [%d]: %s\n tx [%s]", txErr.code, txErr.msg, txData) 108 } 109 } 110 elapsed := time.Now().Sub(start).Milliseconds() 111 if 0 < len(tx.DoOperations) { 112 if 2000 < elapsed { 113 logging.LogWarnf("op tx [%dms]", elapsed) 114 } 115 } 116} 117 118func PerformTransactions(transactions *[]*Transaction) { 119 for _, tx := range *transactions { 120 tx.m = &sync.Mutex{} 121 txQueue <- tx 122 } 123 return 124} 125 126const ( 127 TxErrCodeBlockNotFound = 0 128 TxErrCodeDataIsSyncing = 1 129 TxErrCodeWriteTree = 2 130 TxErrHandleAttributeView = 3 131) 132 133type TxErr struct { 134 code int 135 msg string 136 id string 137} 138 139func performTx(tx *Transaction) (ret *TxErr) { 140 if 1 > len(tx.DoOperations) { 141 return 142 } 143 144 //os.MkdirAll("pprof", 0755) 145 //cpuProfile, _ := os.Create("pprof/cpu_profile_tx") 146 //pprof.StartCPUProfile(cpuProfile) 147 //defer pprof.StopCPUProfile() 148 149 var err error 150 if err = tx.begin(); err != nil { 151 if strings.Contains(err.Error(), "database is closed") { 152 return 153 } 154 logging.LogErrorf("begin tx failed: %s", err) 155 ret = &TxErr{msg: err.Error()} 156 return 157 } 158 159 defer func() { 160 if e := recover(); nil != e { 161 msg := fmt.Sprintf("PANIC RECOVERED: %v\n\t%s\n", e, logging.ShortStack()) 162 logging.LogErrorf(msg) 163 164 if 1 == tx.state.Load() { 165 tx.rollback() 166 return 167 } 168 } 169 }() 170 171 isLargeInsert := tx.processLargeInsert() 172 if !isLargeInsert { 173 tx.processUndoInsertWithFoldedHeading() 174 for _, op := range tx.DoOperations { 175 switch op.Action { 176 case "create": 177 ret = tx.doCreate(op) 178 case "update": 179 ret = tx.doUpdate(op) 180 case "insert": 181 ret = tx.doInsert(op) 182 case "delete": 183 ret = tx.doDelete(op) 184 case "move": 185 ret = tx.doMove(op) 186 case "moveOutlineHeading": 187 ret = tx.doMoveOutlineHeading(op) 188 case "append": 189 ret = tx.doAppend(op) 190 case "appendInsert": 191 ret = tx.doAppendInsert(op) 192 case "prependInsert": 193 ret = tx.doPrependInsert(op) 194 case "foldHeading": 195 ret = tx.doFoldHeading(op) 196 case "unfoldHeading": 197 ret = tx.doUnfoldHeading(op) 198 case "setAttrs": 199 ret = tx.doSetAttrs(op) 200 case "doUpdateUpdated": 201 ret = tx.doUpdateUpdated(op) 202 case "addFlashcards": 203 ret = tx.doAddFlashcards(op) 204 case "removeFlashcards": 205 ret = tx.doRemoveFlashcards(op) 206 case "setAttrViewName": 207 ret = tx.doSetAttrViewName(op) 208 case "setAttrViewFilters": 209 ret = tx.doSetAttrViewFilters(op) 210 case "setAttrViewSorts": 211 ret = tx.doSetAttrViewSorts(op) 212 case "setAttrViewPageSize": 213 ret = tx.doSetAttrViewPageSize(op) 214 case "setAttrViewColWidth": 215 ret = tx.doSetAttrViewColumnWidth(op) 216 case "setAttrViewColWrap": 217 ret = tx.doSetAttrViewColumnWrap(op) 218 case "setAttrViewColHidden": 219 ret = tx.doSetAttrViewColumnHidden(op) 220 case "setAttrViewColPin": 221 ret = tx.doSetAttrViewColumnPin(op) 222 case "setAttrViewColIcon": 223 ret = tx.doSetAttrViewColumnIcon(op) 224 case "setAttrViewColDesc": 225 ret = tx.doSetAttrViewColumnDesc(op) 226 case "insertAttrViewBlock": 227 ret = tx.doInsertAttrViewBlock(op) 228 case "removeAttrViewBlock": 229 ret = tx.doRemoveAttrViewBlock(op) 230 case "addAttrViewCol": 231 ret = tx.doAddAttrViewColumn(op) 232 case "updateAttrViewCol": 233 ret = tx.doUpdateAttrViewColumn(op) 234 case "removeAttrViewCol": 235 ret = tx.doRemoveAttrViewColumn(op) 236 case "sortAttrViewRow": 237 ret = tx.doSortAttrViewRow(op) 238 case "sortAttrViewCol": 239 ret = tx.doSortAttrViewColumn(op) 240 case "sortAttrViewKey": 241 ret = tx.doSortAttrViewKey(op) 242 case "updateAttrViewCell": 243 ret = tx.doUpdateAttrViewCell(op) 244 case "updateAttrViewColOptions": 245 ret = tx.doUpdateAttrViewColOptions(op) 246 case "removeAttrViewColOption": 247 ret = tx.doRemoveAttrViewColOption(op) 248 case "updateAttrViewColOption": 249 ret = tx.doUpdateAttrViewColOption(op) 250 case "setAttrViewColOptionDesc": 251 ret = tx.doSetAttrViewColOptionDesc(op) 252 case "setAttrViewColCalc": 253 ret = tx.doSetAttrViewColCalc(op) 254 case "updateAttrViewColNumberFormat": 255 ret = tx.doUpdateAttrViewColNumberFormat(op) 256 case "replaceAttrViewBlock": 257 ret = tx.doReplaceAttrViewBlock(op) 258 case "updateAttrViewColTemplate": 259 ret = tx.doUpdateAttrViewColTemplate(op) 260 case "addAttrViewView": 261 ret = tx.doAddAttrViewView(op) 262 case "removeAttrViewView": 263 ret = tx.doRemoveAttrViewView(op) 264 case "setAttrViewViewName": 265 ret = tx.doSetAttrViewViewName(op) 266 case "setAttrViewViewIcon": 267 ret = tx.doSetAttrViewViewIcon(op) 268 case "setAttrViewViewDesc": 269 ret = tx.doSetAttrViewViewDesc(op) 270 case "duplicateAttrViewView": 271 ret = tx.doDuplicateAttrViewView(op) 272 case "sortAttrViewView": 273 ret = tx.doSortAttrViewView(op) 274 case "updateAttrViewColRelation": 275 ret = tx.doUpdateAttrViewColRelation(op) 276 case "updateAttrViewColRollup": 277 ret = tx.doUpdateAttrViewColRollup(op) 278 case "hideAttrViewName": 279 ret = tx.doHideAttrViewName(op) 280 case "setAttrViewColDateFillCreated": 281 ret = tx.doSetAttrViewColDateFillCreated(op) 282 case "setAttrViewColDateFillSpecificTime": 283 ret = tx.doSetAttrViewColDateFillSpecificTime(op) 284 case "duplicateAttrViewKey": 285 ret = tx.doDuplicateAttrViewKey(op) 286 case "setAttrViewCoverFrom": 287 ret = tx.doSetAttrViewCoverFrom(op) 288 case "setAttrViewCoverFromAssetKeyID": 289 ret = tx.doSetAttrViewCoverFromAssetKeyID(op) 290 case "setAttrViewCardSize": 291 ret = tx.doSetAttrViewCardSize(op) 292 case "setAttrViewFitImage": 293 ret = tx.doSetAttrViewFitImage(op) 294 case "setAttrViewDisplayFieldName": 295 ret = tx.doSetAttrViewDisplayFieldName(op) 296 case "setAttrViewShowIcon": 297 ret = tx.doSetAttrViewShowIcon(op) 298 case "setAttrViewWrapField": 299 ret = tx.doSetAttrViewWrapField(op) 300 case "changeAttrViewLayout": 301 ret = tx.doChangeAttrViewLayout(op) 302 case "setAttrViewBlockView": 303 ret = tx.doSetAttrViewBlockView(op) 304 case "setAttrViewCardAspectRatio": 305 ret = tx.doSetAttrViewCardAspectRatio(op) 306 case "setAttrViewGroup": 307 ret = tx.doSetAttrViewGroup(op) 308 case "hideAttrViewGroup": 309 ret = tx.doHideAttrViewGroup(op) 310 case "hideAttrViewAllGroups": 311 ret = tx.doHideAttrViewAllGroups(op) 312 case "foldAttrViewGroup": 313 ret = tx.doFoldAttrViewGroup(op) 314 case "syncAttrViewTableColWidth": 315 ret = tx.doSyncAttrViewTableColWidth(op) 316 case "removeAttrViewGroup": 317 ret = tx.doRemoveAttrViewGroup(op) 318 case "sortAttrViewGroup": 319 ret = tx.doSortAttrViewGroup(op) 320 } 321 322 if nil != ret { 323 tx.rollback() 324 return 325 } 326 } 327 } 328 329 if cr := tx.commit(); nil != cr { 330 logging.LogErrorf("commit tx failed: %s", cr) 331 return &TxErr{msg: cr.Error()} 332 } 333 return 334} 335 336func (tx *Transaction) processUndoInsertWithFoldedHeading() { 337 // 删除折叠标题后撤销,需要调整 insert 顺序和 previousID 338 // https://github.com/siyuan-note/siyuan/issues/16120 339 340 // 所有操作均为 insert 才处理 341 for _, op := range tx.DoOperations { 342 if "insert" != op.Action { 343 return 344 } 345 } 346 347 for i := 0; i < len(tx.DoOperations); i++ { 348 op := tx.DoOperations[i] 349 ignoreProcess := false 350 if nil != op.Context["ignoreProcess"] { 351 var convErr error 352 ignoreProcess, convErr = strconv.ParseBool(op.Context["ignoreProcess"].(string)) 353 if nil != convErr { 354 logging.LogErrorf("parse ignoreProcess failed: %s", convErr) 355 return 356 } 357 } 358 if ignoreProcess { 359 // 找到从当前 i 到下个 ignoreProcess=false 的区间,整体反转 360 j := i + 1 361 if j >= len(tx.DoOperations) { 362 return 363 } 364 365 for ; j < len(tx.DoOperations); j++ { 366 nextOp := tx.DoOperations[j] 367 nextIgnoreProcess := false 368 if nil != nextOp.Context["ignoreProcess"] { 369 var convErr error 370 nextIgnoreProcess, convErr = strconv.ParseBool(nextOp.Context["ignoreProcess"].(string)) 371 if nil != convErr { 372 logging.LogErrorf("parse ignoreProcess failed: %s", convErr) 373 return 374 } 375 } 376 if !nextIgnoreProcess { 377 break 378 } 379 } 380 for _, nextOp := range tx.DoOperations[i:j] { 381 nextOp.PreviousID = tx.DoOperations[j].ID 382 } 383 slices.Reverse(tx.DoOperations[i : j+1]) 384 i = j 385 } 386 if i >= len(tx.DoOperations) { 387 return 388 } 389 } 390} 391 392func (tx *Transaction) processLargeInsert() bool { 393 opSize := len(tx.DoOperations) 394 isLargeInsert := 128 < opSize 395 if isLargeInsert { 396 var previousID string 397 for i, op := range tx.DoOperations { 398 if i == opSize-1 { 399 if "delete" != op.Action { 400 // 最后一个是 delete 401 isLargeInsert = false 402 } 403 break 404 } 405 if "insert" != op.Action { 406 isLargeInsert = false 407 break 408 } 409 410 if "" == op.PreviousID { 411 isLargeInsert = false 412 break 413 } 414 if "" == previousID { 415 previousID = op.PreviousID 416 } else if previousID != op.PreviousID { 417 isLargeInsert = false 418 break 419 } 420 } 421 if isLargeInsert { 422 tx.doLargeInsert(previousID) 423 } 424 } 425 return isLargeInsert 426} 427 428func (tx *Transaction) doMove(operation *Operation) (ret *TxErr) { 429 var err error 430 id := operation.ID 431 srcTree, err := tx.loadTree(id) 432 if err != nil { 433 logging.LogErrorf("load tree [%s] failed: %s", id, err) 434 return &TxErr{code: TxErrCodeBlockNotFound, id: id} 435 } 436 437 srcNode := treenode.GetNodeInTree(srcTree, id) 438 if nil == srcNode { 439 logging.LogErrorf("get node [%s] in tree [%s] failed", id, srcTree.Root.ID) 440 return &TxErr{code: TxErrCodeBlockNotFound, id: id} 441 } 442 443 // 生成文档历史 https://github.com/siyuan-note/siyuan/issues/14359 444 generateOpTypeHistory(srcTree, HistoryOpUpdate) 445 446 var headingChildren []*ast.Node 447 if isMovingFoldHeading := ast.NodeHeading == srcNode.Type && "1" == srcNode.IALAttr("fold"); isMovingFoldHeading { 448 headingChildren = treenode.HeadingChildren(srcNode) 449 // Blocks below other non-folded headings are no longer moved when moving a folded heading https://github.com/siyuan-note/siyuan/issues/8321 450 headingChildren = treenode.GetHeadingFold(headingChildren) 451 } 452 453 var srcEmptyList *ast.Node 454 if ast.NodeListItem == srcNode.Type && srcNode.Parent.FirstChild == srcNode && srcNode.Parent.LastChild == srcNode { 455 // 列表中唯一的列表项被移除后,该列表就为空了 456 srcEmptyList = srcNode.Parent 457 } 458 459 targetPreviousID := operation.PreviousID 460 targetParentID := operation.ParentID 461 if "" != targetPreviousID { 462 if id == targetPreviousID { 463 return 464 } 465 466 var targetTree *parse.Tree 467 targetTree, err = tx.loadTree(targetPreviousID) 468 if err != nil { 469 logging.LogErrorf("load tree [%s] failed: %s", targetPreviousID, err) 470 return &TxErr{code: TxErrCodeBlockNotFound, id: targetPreviousID} 471 } 472 isSameTree := srcTree.ID == targetTree.ID 473 if isSameTree { 474 targetTree = srcTree 475 } 476 477 targetNode := treenode.GetNodeInTree(targetTree, targetPreviousID) 478 if nil == targetNode { 479 logging.LogErrorf("get node [%s] in tree [%s] failed", targetPreviousID, targetTree.Root.ID) 480 return &TxErr{code: TxErrCodeBlockNotFound, id: targetPreviousID} 481 } 482 483 if ast.NodeHeading == targetNode.Type && "1" == targetNode.IALAttr("fold") { 484 targetChildren := treenode.HeadingChildren(targetNode) 485 targetChildren = treenode.GetHeadingFold(targetChildren) 486 487 if l := len(targetChildren); 0 < l { 488 targetNode = targetChildren[l-1] 489 } 490 } 491 492 if isMovingFoldHeadingIntoSelf(targetNode, headingChildren) { 493 return 494 } 495 496 if isMovingParentIntoChild(srcNode, targetNode) { 497 return 498 } 499 500 if 0 < len(headingChildren) { 501 // 折叠标题再编辑形成外层列表(前面加上 * )时,前端给的 tx 序列会形成死循环,在这里解开 502 // Nested lists cause hang after collapsing headings https://github.com/siyuan-note/siyuan/issues/15943 503 lastChild := headingChildren[len(headingChildren)-1] 504 if "1" == lastChild.IALAttr("heading-fold") && ast.NodeList == lastChild.Type && 505 nil != lastChild.FirstChild && nil != lastChild.FirstChild.FirstChild && lastChild.FirstChild.FirstChild.ID == targetPreviousID { 506 ast.Walk(lastChild, func(n *ast.Node, entering bool) ast.WalkStatus { 507 if !entering || !n.IsBlock() { 508 return ast.WalkContinue 509 } 510 511 n.RemoveIALAttr("heading-fold") 512 n.RemoveIALAttr("fold") 513 return ast.WalkContinue 514 }) 515 headingChildren = headingChildren[:len(headingChildren)-1] 516 } 517 } 518 519 for i := len(headingChildren) - 1; -1 < i; i-- { 520 c := headingChildren[i] 521 targetNode.InsertAfter(c) 522 } 523 targetNode.InsertAfter(srcNode) 524 if nil != srcEmptyList { 525 srcEmptyList.Unlink() 526 } 527 528 refreshUpdated(srcNode) 529 tx.nodes[srcNode.ID] = srcNode 530 refreshUpdated(srcTree.Root) 531 if err = tx.writeTree(srcTree); err != nil { 532 return 533 } 534 if !isSameTree { 535 if err = tx.writeTree(targetTree); err != nil { 536 return 537 } 538 task.AppendAsyncTaskWithDelay(task.SetDefRefCount, util.SQLFlushInterval, refreshRefCount, srcTree.ID) 539 task.AppendAsyncTaskWithDelay(task.SetDefRefCount, util.SQLFlushInterval, refreshRefCount, srcNode.ID) 540 } 541 return 542 } 543 544 if id == targetParentID { 545 return 546 } 547 548 targetTree, err := tx.loadTree(targetParentID) 549 if err != nil { 550 logging.LogErrorf("load tree [%s] failed: %s", targetParentID, err) 551 return &TxErr{code: TxErrCodeBlockNotFound, id: targetParentID} 552 } 553 isSameTree := srcTree.ID == targetTree.ID 554 if isSameTree { 555 targetTree = srcTree 556 } 557 558 targetNode := treenode.GetNodeInTree(targetTree, targetParentID) 559 if nil == targetNode { 560 logging.LogErrorf("get node [%s] in tree [%s] failed", targetParentID, targetTree.Root.ID) 561 return &TxErr{code: TxErrCodeBlockNotFound, id: targetParentID} 562 } 563 564 if isMovingFoldHeadingIntoSelf(targetNode, headingChildren) { 565 return 566 } 567 568 if isMovingParentIntoChild(srcNode, targetNode) { 569 return 570 } 571 572 processed := false 573 if ast.NodeSuperBlock == targetNode.Type { 574 // 在布局节点后插入 575 targetNode = targetNode.FirstChild.Next 576 for i := len(headingChildren) - 1; -1 < i; i-- { 577 c := headingChildren[i] 578 targetNode.InsertAfter(c) 579 } 580 targetNode.InsertAfter(srcNode) 581 if nil != srcEmptyList { 582 srcEmptyList.Unlink() 583 } 584 processed = true 585 } else if ast.NodeListItem == targetNode.Type { 586 if 3 == targetNode.ListData.Typ { 587 // 在任务列表标记节点后插入 588 targetNode = targetNode.FirstChild 589 for i := len(headingChildren) - 1; -1 < i; i-- { 590 c := headingChildren[i] 591 targetNode.InsertAfter(c) 592 } 593 targetNode.InsertAfter(srcNode) 594 if nil != srcEmptyList { 595 srcEmptyList.Unlink() 596 } 597 processed = true 598 } 599 } 600 601 if !processed { 602 for i := len(headingChildren) - 1; -1 < i; i-- { 603 c := headingChildren[i] 604 targetNode.PrependChild(c) 605 } 606 607 targetNode.PrependChild(srcNode) 608 if nil != srcEmptyList { 609 srcEmptyList.Unlink() 610 } 611 } 612 613 refreshUpdated(srcNode) 614 tx.nodes[srcNode.ID] = srcNode 615 refreshUpdated(srcTree.Root) 616 if err = tx.writeTree(srcTree); err != nil { 617 return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: id} 618 } 619 if !isSameTree { 620 if err = tx.writeTree(targetTree); err != nil { 621 return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: id} 622 } 623 task.AppendAsyncTaskWithDelay(task.SetDefRefCount, util.SQLFlushInterval, refreshRefCount, srcTree.ID) 624 task.AppendAsyncTaskWithDelay(task.SetDefRefCount, util.SQLFlushInterval, refreshRefCount, srcNode.ID) 625 } 626 return 627} 628 629func isMovingFoldHeadingIntoSelf(targetNode *ast.Node, headingChildren []*ast.Node) bool { 630 for _, headingChild := range headingChildren { 631 if headingChild.ID == targetNode.ID { 632 // 不能将折叠标题移动到自己下方节点的前或后 https://github.com/siyuan-note/siyuan/issues/7163 633 return true 634 } 635 } 636 return false 637} 638 639func isMovingParentIntoChild(srcNode, targetNode *ast.Node) bool { 640 for parent := targetNode.Parent; nil != parent; parent = parent.Parent { 641 if parent.ID == srcNode.ID { 642 return true 643 } 644 } 645 return false 646} 647 648func (tx *Transaction) doPrependInsert(operation *Operation) (ret *TxErr) { 649 var err error 650 block := treenode.GetBlockTree(operation.ParentID) 651 if nil == block { 652 logging.LogWarnf("not found block [%s]", operation.ParentID) 653 util.ReloadUI() // 比如分屏后编辑器状态不一致,这里强制重新载入界面 654 return 655 } 656 tree, err := tx.loadTree(block.ID) 657 if err != nil { 658 msg := fmt.Sprintf("load tree [%s] failed: %s", block.ID, err) 659 logging.LogErrorf(msg) 660 return &TxErr{code: TxErrCodeBlockNotFound, id: block.ID} 661 } 662 663 data := strings.ReplaceAll(operation.Data.(string), editor.FrontEndCaret, "") 664 subTree := tx.luteEngine.BlockDOM2Tree(data) 665 insertedNode := subTree.Root.FirstChild 666 if nil == insertedNode { 667 return &TxErr{code: TxErrCodeBlockNotFound, msg: "invalid data tree", id: block.ID} 668 } 669 if "" == insertedNode.ID { 670 insertedNode.ID = ast.NewNodeID() 671 insertedNode.SetIALAttr("id", insertedNode.ID) 672 } 673 var toInserts []*ast.Node 674 for toInsert := insertedNode; nil != toInsert; toInsert = toInsert.Next { 675 if ast.NodeKramdownBlockIAL != toInsert.Type { 676 if "" == toInsert.ID { 677 toInsert.ID = ast.NewNodeID() 678 toInsert.SetIALAttr("id", toInsert.ID) 679 } 680 toInserts = append(toInserts, toInsert) 681 } 682 } 683 684 node := treenode.GetNodeInTree(tree, operation.ParentID) 685 if nil == node { 686 logging.LogErrorf("get node [%s] in tree [%s] failed", operation.ParentID, tree.Root.ID) 687 return &TxErr{code: TxErrCodeBlockNotFound, id: operation.ParentID} 688 } 689 isContainer := node.IsContainerBlock() 690 slices.Reverse(toInserts) 691 692 for _, toInsert := range toInserts { 693 if isContainer { 694 if ast.NodeList == node.Type { 695 // 列表下只能挂列表项,所以这里需要分情况处理 696 if ast.NodeList == toInsert.Type { 697 var childLis []*ast.Node 698 for childLi := toInsert.FirstChild; nil != childLi; childLi = childLi.Next { 699 childLis = append(childLis, childLi) 700 } 701 for i := len(childLis) - 1; -1 < i; i-- { 702 node.PrependChild(childLis[i]) 703 } 704 } else { 705 newLiID := ast.NewNodeID() 706 newLi := &ast.Node{ID: newLiID, Type: ast.NodeListItem, ListData: &ast.ListData{Typ: node.ListData.Typ}} 707 newLi.SetIALAttr("id", newLiID) 708 node.PrependChild(newLi) 709 newLi.AppendChild(toInsert) 710 } 711 } else if ast.NodeSuperBlock == node.Type { 712 layout := node.ChildByType(ast.NodeSuperBlockLayoutMarker) 713 if nil != layout { 714 layout.InsertAfter(toInsert) 715 } else { 716 node.FirstChild.InsertAfter(toInsert) 717 } 718 } else { 719 node.PrependChild(toInsert) 720 } 721 } else { 722 node.InsertAfter(toInsert) 723 } 724 725 createdUpdated(toInsert) 726 tx.nodes[toInsert.ID] = toInsert 727 } 728 729 createdUpdated(insertedNode) 730 tx.nodes[insertedNode.ID] = insertedNode 731 if err = tx.writeTree(tree); err != nil { 732 return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: block.ID} 733 } 734 735 operation.ID = insertedNode.ID 736 operation.ParentID = insertedNode.Parent.ID 737 738 // 将 prependInsert 转换为 insert 推送 739 operation.Action = "insert" 740 if nil != insertedNode.Previous { 741 operation.PreviousID = insertedNode.Previous.ID 742 } 743 return 744} 745 746func (tx *Transaction) doAppendInsert(operation *Operation) (ret *TxErr) { 747 var err error 748 block := treenode.GetBlockTree(operation.ParentID) 749 if nil == block { 750 logging.LogWarnf("not found block [%s]", operation.ParentID) 751 util.ReloadUI() // 比如分屏后编辑器状态不一致,这里强制重新载入界面 752 return 753 } 754 tree, err := tx.loadTree(block.ID) 755 if err != nil { 756 msg := fmt.Sprintf("load tree [%s] failed: %s", block.ID, err) 757 logging.LogErrorf(msg) 758 return &TxErr{code: TxErrCodeBlockNotFound, id: block.ID} 759 } 760 761 data := strings.ReplaceAll(operation.Data.(string), editor.FrontEndCaret, "") 762 subTree := tx.luteEngine.BlockDOM2Tree(data) 763 insertedNode := subTree.Root.FirstChild 764 if nil == insertedNode { 765 return &TxErr{code: TxErrCodeBlockNotFound, msg: "invalid data tree", id: block.ID} 766 } 767 if "" == insertedNode.ID { 768 insertedNode.ID = ast.NewNodeID() 769 insertedNode.SetIALAttr("id", insertedNode.ID) 770 } 771 var toInserts []*ast.Node 772 for toInsert := insertedNode; nil != toInsert; toInsert = toInsert.Next { 773 if ast.NodeKramdownBlockIAL != toInsert.Type { 774 if "" == toInsert.ID { 775 toInsert.ID = ast.NewNodeID() 776 toInsert.SetIALAttr("id", toInsert.ID) 777 } 778 toInserts = append(toInserts, toInsert) 779 } 780 } 781 782 node := treenode.GetNodeInTree(tree, operation.ParentID) 783 if nil == node { 784 logging.LogErrorf("get node [%s] in tree [%s] failed", operation.ParentID, tree.Root.ID) 785 return &TxErr{code: TxErrCodeBlockNotFound, id: operation.ParentID} 786 } 787 isContainer := node.IsContainerBlock() 788 if !isContainer { 789 slices.Reverse(toInserts) 790 } 791 var lastChildBelowHeading *ast.Node 792 if ast.NodeHeading == node.Type { 793 if children := treenode.HeadingChildren(node); 0 < len(children) { 794 lastChildBelowHeading = children[len(children)-1] 795 } 796 } 797 798 for _, toInsert := range toInserts { 799 if isContainer { 800 if ast.NodeList == node.Type { 801 // 列表下只能挂列表项,所以这里需要分情况处理 https://github.com/siyuan-note/siyuan/issues/9955 802 if ast.NodeList == toInsert.Type { 803 var childLis []*ast.Node 804 for childLi := toInsert.FirstChild; nil != childLi; childLi = childLi.Next { 805 childLis = append(childLis, childLi) 806 } 807 for _, childLi := range childLis { 808 node.AppendChild(childLi) 809 } 810 } else { 811 newLiID := ast.NewNodeID() 812 newLi := &ast.Node{ID: newLiID, Type: ast.NodeListItem, ListData: &ast.ListData{Typ: node.ListData.Typ}} 813 newLi.SetIALAttr("id", newLiID) 814 node.AppendChild(newLi) 815 newLi.AppendChild(toInsert) 816 } 817 } else if ast.NodeSuperBlock == node.Type { 818 node.LastChild.InsertBefore(toInsert) 819 } else { 820 node.AppendChild(toInsert) 821 } 822 } else { 823 if ast.NodeHeading == node.Type { 824 if nil != lastChildBelowHeading { 825 lastChildBelowHeading.InsertAfter(toInsert) 826 } else { 827 node.InsertAfter(toInsert) 828 } 829 } else { 830 node.InsertAfter(toInsert) 831 } 832 } 833 834 createdUpdated(toInsert) 835 tx.nodes[toInsert.ID] = toInsert 836 } 837 838 createdUpdated(insertedNode) 839 tx.nodes[insertedNode.ID] = insertedNode 840 if err = tx.writeTree(tree); err != nil { 841 return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: block.ID} 842 } 843 844 operation.ID = insertedNode.ID 845 operation.ParentID = insertedNode.Parent.ID 846 847 // 将 appendInsert 转换为 insert 推送 848 operation.Action = "insert" 849 if nil != insertedNode.Previous { 850 operation.PreviousID = insertedNode.Previous.ID 851 } 852 return 853} 854 855func (tx *Transaction) doAppend(operation *Operation) (ret *TxErr) { 856 var err error 857 id := operation.ID 858 srcTree, err := tx.loadTree(id) 859 if err != nil { 860 logging.LogErrorf("load tree [%s] failed: %s", id, err) 861 return &TxErr{code: TxErrCodeBlockNotFound, id: id} 862 } 863 864 srcNode := treenode.GetNodeInTree(srcTree, id) 865 if nil == srcNode { 866 logging.LogErrorf("get node [%s] in tree [%s] failed", id, srcTree.Root.ID) 867 return &TxErr{code: TxErrCodeBlockNotFound, id: id} 868 } 869 870 if ast.NodeDocument == srcNode.Type { 871 logging.LogWarnf("can't append a root to another root") 872 return 873 } 874 875 var headingChildren []*ast.Node 876 if isMovingFoldHeading := ast.NodeHeading == srcNode.Type && "1" == srcNode.IALAttr("fold"); isMovingFoldHeading { 877 headingChildren = treenode.HeadingChildren(srcNode) 878 } 879 var srcEmptyList, targetNewList *ast.Node 880 if ast.NodeListItem == srcNode.Type { 881 targetNewListID := ast.NewNodeID() 882 targetNewList = &ast.Node{ID: targetNewListID, Type: ast.NodeList, ListData: &ast.ListData{Typ: srcNode.ListData.Typ}} 883 targetNewList.SetIALAttr("id", targetNewListID) 884 if srcNode.Parent.FirstChild == srcNode && srcNode.Parent.LastChild == srcNode { 885 // 列表中唯一的列表项被移除后,该列表就为空了 886 srcEmptyList = srcNode.Parent 887 } 888 } 889 890 targetRootID := operation.ParentID 891 if id == targetRootID { 892 logging.LogWarnf("target root id is nil") 893 return 894 } 895 896 targetTree, err := tx.loadTree(targetRootID) 897 if err != nil { 898 logging.LogErrorf("load tree [%s] failed: %s", targetRootID, err) 899 return &TxErr{code: TxErrCodeBlockNotFound, id: targetRootID} 900 } 901 isSameTree := srcTree.ID == targetTree.ID 902 if isSameTree { 903 targetTree = srcTree 904 } 905 906 targetRoot := targetTree.Root 907 if nil != targetNewList { 908 if nil != targetRoot.LastChild { 909 if ast.NodeList != targetRoot.LastChild.Type { 910 targetNewList.AppendChild(srcNode) 911 targetRoot.AppendChild(targetNewList) 912 } else { 913 targetRoot.LastChild.AppendChild(srcNode) 914 } 915 } else { 916 targetRoot.AppendChild(srcNode) 917 } 918 } else { 919 targetRoot.AppendChild(srcNode) 920 } 921 for _, c := range headingChildren { 922 targetRoot.AppendChild(c) 923 } 924 if nil != srcEmptyList { 925 srcEmptyList.Unlink() 926 } 927 928 if err = tx.writeTree(srcTree); err != nil { 929 return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: id} 930 } 931 932 if !isSameTree { 933 if err = tx.writeTree(targetTree); err != nil { 934 return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: id} 935 } 936 } 937 return 938} 939 940func (tx *Transaction) doDelete(operation *Operation) (ret *TxErr) { 941 var err error 942 id := operation.ID 943 tree, err := tx.loadTree(id) 944 if err != nil { 945 if errors.Is(err, ErrBlockNotFound) { 946 // move 以后这里会空,算作正常情况 947 return 948 } 949 950 msg := fmt.Sprintf("load tree [%s] failed: %s", id, err) 951 logging.LogErrorf(msg) 952 return &TxErr{code: TxErrCodeBlockNotFound, id: id} 953 } 954 955 node := treenode.GetNodeInTree(tree, id) 956 if nil == node { 957 return nil // move 以后的情况,列表项移动导致的状态异常 https://github.com/siyuan-note/insider/issues/961 958 } 959 960 // 收集引用的定义块 ID 961 refDefIDs := getRefDefIDs(node) 962 // 推送定义节点引用计数 963 for _, defID := range refDefIDs { 964 task.AppendAsyncTaskWithDelay(task.SetDefRefCount, util.SQLFlushInterval, refreshRefCount, defID) 965 } 966 967 parent := node.Parent 968 if nil != node.Next && ast.NodeKramdownBlockIAL == node.Next.Type && bytes.Contains(node.Next.Tokens, []byte(node.ID)) { 969 // 列表块撤销状态异常 https://github.com/siyuan-note/siyuan/issues/3985 970 node.Next.Unlink() 971 } 972 973 node.Unlink() 974 975 if nil != parent && ast.NodeListItem == parent.Type && nil == parent.FirstChild { 976 needAppendEmptyListItem := true 977 for _, op := range tx.DoOperations { 978 if "insert" == op.Action && op.ParentID == parent.ID { 979 needAppendEmptyListItem = false 980 break 981 } 982 } 983 984 if needAppendEmptyListItem { 985 parent.AppendChild(treenode.NewParagraph(ast.NewNodeID())) 986 } 987 } 988 treenode.RemoveBlockTree(node.ID) 989 990 delete(tx.nodes, node.ID) 991 if err = tx.writeTree(tree); err != nil { 992 return 993 } 994 995 // 如果是断开列表时的删除列表项事务,则不需要删除数据库绑定块,因为断开列表事务后面会再次插入相同 ID 的列表项 996 // List item disconnection no longer affects database binding blocks https://github.com/siyuan-note/siyuan/issues/12235 997 needSyncDel2AvBlock := true 998 if ast.NodeListItem == node.Type { 999 for _, op := range tx.DoOperations { 1000 // 不可能出现相同 ID 先插入再删除的情况,只可能出现先删除再插入的情况,所以这里只需要查找插入操作 1001 if "insert" == op.Action { 1002 data := strings.ReplaceAll(op.Data.(string), editor.FrontEndCaret, "") 1003 subTree := tx.luteEngine.BlockDOM2Tree(data) 1004 ast.Walk(subTree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { 1005 if !entering || ast.NodeListItem != n.Type { 1006 return ast.WalkContinue 1007 } 1008 1009 if n.ID == operation.ID { 1010 needSyncDel2AvBlock = false 1011 return ast.WalkStop 1012 } 1013 return ast.WalkContinue 1014 }) 1015 1016 break 1017 } 1018 } 1019 } 1020 1021 if needSyncDel2AvBlock { 1022 syncDelete2AvBlock(node, tree, tx) 1023 } 1024 return 1025} 1026 1027func syncDelete2AvBlock(node *ast.Node, nodeTree *parse.Tree, tx *Transaction) { 1028 changedAvIDs := syncDelete2AttributeView(node) 1029 avIDs := tx.syncDelete2Block(node, nodeTree) 1030 changedAvIDs = append(changedAvIDs, avIDs...) 1031 changedAvIDs = gulu.Str.RemoveDuplicatedElem(changedAvIDs) 1032 1033 for _, avID := range changedAvIDs { 1034 ReloadAttrView(avID) 1035 } 1036} 1037 1038func (tx *Transaction) syncDelete2Block(node *ast.Node, nodeTree *parse.Tree) (changedAvIDs []string) { 1039 ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus { 1040 if !entering || ast.NodeAttributeView != n.Type { 1041 return ast.WalkContinue 1042 } 1043 1044 avID := n.AttributeViewID 1045 isMirror := av.IsMirror(avID) 1046 if changed := av.RemoveBlockRel(avID, n.ID, treenode.ExistBlockTree); changed { 1047 changedAvIDs = append(changedAvIDs, avID) 1048 } 1049 1050 if isMirror { 1051 // 删除镜像数据库节点后不需要解绑块,因为其他镜像节点还在使用 1052 return ast.WalkContinue 1053 } 1054 1055 attrView, err := av.ParseAttributeView(avID) 1056 if err != nil { 1057 return ast.WalkContinue 1058 } 1059 1060 trees, nodes := tx.getAttrViewBoundNodes(attrView) 1061 for _, toChangNode := range nodes { 1062 avs := toChangNode.IALAttr(av.NodeAttrNameAvs) 1063 if "" != avs { 1064 avIDs := strings.Split(avs, ",") 1065 avIDs = gulu.Str.RemoveElem(avIDs, avID) 1066 if 1 > len(avIDs) { 1067 toChangNode.RemoveIALAttr(av.NodeAttrNameAvs) 1068 } else { 1069 toChangNode.SetIALAttr(av.NodeAttrNameAvs, strings.Join(avIDs, ",")) 1070 } 1071 } 1072 avNames := getAvNames(toChangNode.IALAttr(av.NodeAttrNameAvs)) 1073 oldAttrs := parse.IAL2Map(toChangNode.KramdownIAL) 1074 toChangNode.SetIALAttr(av.NodeAttrViewNames, avNames) 1075 pushBroadcastAttrTransactions(oldAttrs, toChangNode) 1076 } 1077 1078 for _, tree := range trees { 1079 if nodeTree.ID != tree.ID { 1080 indexWriteTreeUpsertQueue(tree) 1081 } 1082 } 1083 return ast.WalkContinue 1084 }) 1085 1086 changedAvIDs = gulu.Str.RemoveDuplicatedElem(changedAvIDs) 1087 return 1088} 1089 1090func syncDelete2AttributeView(node *ast.Node) (changedAvIDs []string) { 1091 ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus { 1092 if !entering || !n.IsBlock() { 1093 return ast.WalkContinue 1094 } 1095 1096 avs := n.IALAttr(av.NodeAttrNameAvs) 1097 if "" == avs { 1098 return ast.WalkContinue 1099 } 1100 1101 avIDs := strings.Split(avs, ",") 1102 for _, avID := range avIDs { 1103 attrView, parseErr := av.ParseAttributeView(avID) 1104 if nil != parseErr { 1105 continue 1106 } 1107 1108 changedAv := false 1109 blockValues := attrView.GetBlockKeyValues() 1110 if nil == blockValues { 1111 continue 1112 } 1113 1114 for i, blockValue := range blockValues.Values { 1115 if nil == blockValue.Block { 1116 continue 1117 } 1118 1119 if blockValue.Block.ID == n.ID { 1120 blockValues.Values = append(blockValues.Values[:i], blockValues.Values[i+1:]...) 1121 changedAv = true 1122 break 1123 } 1124 } 1125 1126 if changedAv { 1127 regenAttrViewGroups(attrView) 1128 av.SaveAttributeView(attrView) 1129 changedAvIDs = append(changedAvIDs, avID) 1130 } 1131 } 1132 return ast.WalkContinue 1133 }) 1134 1135 changedAvIDs = gulu.Str.RemoveDuplicatedElem(changedAvIDs) 1136 return 1137} 1138 1139func (tx *Transaction) doLargeInsert(previousID string) (ret *TxErr) { 1140 tree, err := tx.loadTree(previousID) 1141 if nil != err { 1142 logging.LogErrorf("load tree [%s] failed: %s", previousID, err) 1143 return &TxErr{code: TxErrCodeBlockNotFound, id: previousID} 1144 } 1145 1146 for _, operation := range tx.DoOperations { 1147 if "insert" != operation.Action { 1148 break 1149 } 1150 1151 data := strings.ReplaceAll(operation.Data.(string), editor.FrontEndCaret, "") 1152 subTree := tx.luteEngine.BlockDOM2Tree(data) 1153 tx.processGlobalAssets(subTree) 1154 1155 insertedNode := subTree.Root.FirstChild 1156 if nil == insertedNode { 1157 return &TxErr{code: TxErrCodeBlockNotFound, msg: "invalid data tree", id: tree.ID} 1158 } 1159 var remains []*ast.Node 1160 for remain := insertedNode.Next; nil != remain; remain = remain.Next { 1161 if ast.NodeKramdownBlockIAL != remain.Type { 1162 if "" == remain.ID { 1163 remain.ID = ast.NewNodeID() 1164 remain.SetIALAttr("id", remain.ID) 1165 } 1166 remains = append(remains, remain) 1167 } 1168 } 1169 if "" == insertedNode.ID { 1170 insertedNode.ID = ast.NewNodeID() 1171 insertedNode.SetIALAttr("id", insertedNode.ID) 1172 } 1173 1174 node := treenode.GetNodeInTree(tree, previousID) 1175 if nil == node { 1176 logging.LogErrorf("get node [%s] in tree [%s] failed", previousID, tree.Root.ID) 1177 return &TxErr{code: TxErrCodeBlockNotFound, id: previousID} 1178 } 1179 1180 if ast.NodeHeading == node.Type && "1" == node.IALAttr("fold") { 1181 children := treenode.HeadingChildren(node) 1182 if l := len(children); 0 < l { 1183 node = children[l-1] 1184 } 1185 } 1186 if ast.NodeList == insertedNode.Type && nil != node.Parent && ast.NodeList == node.Parent.Type { 1187 insertedNode = insertedNode.FirstChild 1188 } 1189 for i := len(remains) - 1; 0 <= i; i-- { 1190 remain := remains[i] 1191 node.InsertAfter(remain) 1192 } 1193 node.InsertAfter(insertedNode) 1194 1195 createdUpdated(insertedNode) 1196 tx.nodes[insertedNode.ID] = insertedNode 1197 tx.trees[tree.ID] = tree 1198 1199 // 收集引用的定义块 ID 1200 refDefIDs := getRefDefIDs(insertedNode) 1201 // 推送定义节点引用计数 1202 for _, defID := range refDefIDs { 1203 task.AppendAsyncTaskWithDelay(task.SetDefRefCount, util.SQLFlushInterval, refreshRefCount, defID) 1204 } 1205 1206 upsertAvBlockRel(insertedNode) 1207 1208 // 复制为副本时将该副本块插入到数据库中 https://github.com/siyuan-note/siyuan/issues/11959 1209 avs := insertedNode.IALAttr(av.NodeAttrNameAvs) 1210 for _, avID := range strings.Split(avs, ",") { 1211 if !ast.IsNodeIDPattern(avID) { 1212 continue 1213 } 1214 1215 AddAttributeViewBlock(tx, []map[string]interface{}{{ 1216 "id": insertedNode.ID, 1217 "isDetached": false, 1218 }}, avID, "", "", "", previousID, false, map[string]interface{}{}) 1219 ReloadAttrView(avID) 1220 } 1221 1222 if ast.NodeAttributeView == insertedNode.Type { 1223 // 插入数据库块时需要重新绑定其中已经存在的块 1224 // 比如剪切操作时,会先进行 delete 数据库解绑块,这里需要重新绑定 https://github.com/siyuan-note/siyuan/issues/13031 1225 attrView, parseErr := av.ParseAttributeView(insertedNode.AttributeViewID) 1226 if nil == parseErr { 1227 trees, toBindNodes := tx.getAttrViewBoundNodes(attrView) 1228 for _, toBindNode := range toBindNodes { 1229 t := trees[toBindNode.ID] 1230 bindBlockAv0(tx, insertedNode.AttributeViewID, toBindNode, t) 1231 } 1232 1233 // 设置视图 https://github.com/siyuan-note/siyuan/issues/15279 1234 v := attrView.GetView(attrView.ViewID) 1235 if nil != v { 1236 insertedNode.AttributeViewType = string(v.LayoutType) 1237 attrs := parse.IAL2Map(insertedNode.KramdownIAL) 1238 if "" == attrs[av.NodeAttrView] { 1239 attrs[av.NodeAttrView] = v.ID 1240 err = setNodeAttrs(insertedNode, tree, attrs) 1241 if err != nil { 1242 logging.LogWarnf("set node [%s] attrs failed: %s", operation.BlockID, err) 1243 return 1244 } 1245 } 1246 } 1247 } 1248 } 1249 1250 operation.ID = insertedNode.ID 1251 operation.ParentID = insertedNode.Parent.ID 1252 } 1253 1254 if err = tx.writeTree(tree); nil != err { 1255 return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: tree.ID} 1256 } 1257 return tx.doDelete(tx.DoOperations[len(tx.DoOperations)-1]) 1258} 1259 1260func (tx *Transaction) doInsert(operation *Operation) (ret *TxErr) { 1261 var bt *treenode.BlockTree 1262 bts := treenode.GetBlockTrees([]string{operation.ParentID, operation.PreviousID, operation.NextID}) 1263 for _, b := range bts { 1264 if "" != b.ID { 1265 bt = b 1266 break 1267 } 1268 } 1269 if nil == bt { 1270 logging.LogWarnf("not found block tree [%s, %s, %s]", operation.ParentID, operation.PreviousID, operation.NextID) 1271 util.ReloadUI() // 比如分屏后编辑器状态不一致,这里强制重新载入界面 1272 return 1273 } 1274 1275 var err error 1276 tree, err := tx.loadTreeByBlockTree(bt) 1277 if err != nil { 1278 msg := fmt.Sprintf("load tree [%s] failed: %s", bt.ID, err) 1279 logging.LogErrorf(msg) 1280 return &TxErr{code: TxErrCodeBlockNotFound, id: bt.ID} 1281 } 1282 1283 data := strings.ReplaceAll(operation.Data.(string), editor.FrontEndCaret, "") 1284 subTree := tx.luteEngine.BlockDOM2Tree(data) 1285 tx.processGlobalAssets(subTree) 1286 1287 insertedNode := subTree.Root.FirstChild 1288 if nil == insertedNode { 1289 return &TxErr{code: TxErrCodeBlockNotFound, msg: "invalid data tree", id: bt.ID} 1290 } 1291 var remains []*ast.Node 1292 for remain := insertedNode.Next; nil != remain; remain = remain.Next { 1293 if ast.NodeKramdownBlockIAL != remain.Type { 1294 if "" == remain.ID { 1295 remain.ID = ast.NewNodeID() 1296 remain.SetIALAttr("id", remain.ID) 1297 } 1298 remains = append(remains, remain) 1299 } 1300 } 1301 if "" == insertedNode.ID { 1302 insertedNode.ID = ast.NewNodeID() 1303 insertedNode.SetIALAttr("id", insertedNode.ID) 1304 } 1305 1306 var node *ast.Node 1307 nextID := operation.NextID 1308 previousID := operation.PreviousID 1309 if "" != nextID { 1310 node = treenode.GetNodeInTree(tree, nextID) 1311 if nil == node { 1312 logging.LogErrorf("get node [%s] in tree [%s] failed", nextID, tree.Root.ID) 1313 return &TxErr{code: TxErrCodeBlockNotFound, id: nextID} 1314 } 1315 1316 if ast.NodeList == insertedNode.Type && nil != node.Parent && ast.NodeList == node.Parent.Type { 1317 insertedNode = insertedNode.FirstChild 1318 } 1319 node.InsertBefore(insertedNode) 1320 } else if "" != previousID { 1321 node = treenode.GetNodeInTree(tree, previousID) 1322 if nil == node { 1323 logging.LogErrorf("get node [%s] in tree [%s] failed", previousID, tree.Root.ID) 1324 return &TxErr{code: TxErrCodeBlockNotFound, id: previousID} 1325 } 1326 1327 if ast.NodeHeading == node.Type && "1" == node.IALAttr("fold") { 1328 children := treenode.HeadingChildren(node) 1329 if l := len(children); 0 < l { 1330 node = children[l-1] 1331 } 1332 } 1333 if ast.NodeList == insertedNode.Type && nil != node.Parent && ast.NodeList == node.Parent.Type { 1334 insertedNode = insertedNode.FirstChild 1335 } 1336 for i := len(remains) - 1; 0 <= i; i-- { 1337 remain := remains[i] 1338 node.InsertAfter(remain) 1339 } 1340 node.InsertAfter(insertedNode) 1341 } else { 1342 node = treenode.GetNodeInTree(tree, operation.ParentID) 1343 if nil == node { 1344 logging.LogErrorf("get node [%s] in tree [%s] failed", operation.ParentID, tree.Root.ID) 1345 return &TxErr{code: TxErrCodeBlockNotFound, id: operation.ParentID} 1346 } 1347 if ast.NodeSuperBlock == node.Type { 1348 // 在布局节点后插入 1349 node.FirstChild.Next.InsertAfter(insertedNode) 1350 } else { 1351 if ast.NodeList == insertedNode.Type && nil != insertedNode.FirstChild && operation.ID == insertedNode.FirstChild.ID && operation.ID != insertedNode.ID { 1352 // 将一个列表项移动到另一个列表的第一项时 https://github.com/siyuan-note/siyuan/issues/2341 1353 insertedNode = insertedNode.FirstChild 1354 } 1355 1356 if ast.NodeListItem == node.Type && 3 == node.ListData.Typ { 1357 // 在任务列表标记节点后插入 1358 node.FirstChild.InsertAfter(insertedNode) 1359 for _, remain := range remains { 1360 node.FirstChild.InsertAfter(remain) 1361 } 1362 } else { 1363 if !node.IsContainerBlock() { 1364 for i := len(remains) - 1; 0 <= i; i-- { 1365 remain := remains[i] 1366 node.InsertAfter(remain) 1367 } 1368 node.InsertAfter(insertedNode) 1369 } else { 1370 for i := len(remains) - 1; 0 <= i; i-- { 1371 remain := remains[i] 1372 node.PrependChild(remain) 1373 } 1374 node.PrependChild(insertedNode) 1375 } 1376 } 1377 } 1378 } 1379 1380 createdUpdated(insertedNode) 1381 tx.nodes[insertedNode.ID] = insertedNode 1382 if err = tx.writeTree(tree); err != nil { 1383 return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: bt.ID} 1384 } 1385 1386 // 收集引用的定义块 ID 1387 refDefIDs := getRefDefIDs(insertedNode) 1388 // 推送定义节点引用计数 1389 for _, defID := range refDefIDs { 1390 task.AppendAsyncTaskWithDelay(task.SetDefRefCount, util.SQLFlushInterval, refreshRefCount, defID) 1391 } 1392 1393 upsertAvBlockRel(insertedNode) 1394 1395 // 复制为副本时将该副本块插入到数据库中 https://github.com/siyuan-note/siyuan/issues/11959 1396 avs := insertedNode.IALAttr(av.NodeAttrNameAvs) 1397 for _, avID := range strings.Split(avs, ",") { 1398 if !ast.IsNodeIDPattern(avID) { 1399 continue 1400 } 1401 1402 AddAttributeViewBlock(tx, []map[string]interface{}{{ 1403 "id": insertedNode.ID, 1404 "isDetached": false, 1405 }}, avID, "", "", "", previousID, false, map[string]interface{}{}) 1406 ReloadAttrView(avID) 1407 } 1408 1409 if ast.NodeAttributeView == insertedNode.Type { 1410 // 插入数据库块时需要重新绑定其中已经存在的块 1411 // 比如剪切操作时,会先进行 delete 数据库解绑块,这里需要重新绑定 https://github.com/siyuan-note/siyuan/issues/13031 1412 attrView, parseErr := av.ParseAttributeView(insertedNode.AttributeViewID) 1413 if nil == parseErr { 1414 trees, toBindNodes := tx.getAttrViewBoundNodes(attrView) 1415 for _, toBindNode := range toBindNodes { 1416 t := trees[toBindNode.ID] 1417 bindBlockAv0(tx, insertedNode.AttributeViewID, toBindNode, t) 1418 } 1419 1420 // 设置视图 https://github.com/siyuan-note/siyuan/issues/15279 1421 v := attrView.GetView(attrView.ViewID) 1422 if nil != v { 1423 insertedNode.AttributeViewType = string(v.LayoutType) 1424 attrs := parse.IAL2Map(insertedNode.KramdownIAL) 1425 if "" == attrs[av.NodeAttrView] { 1426 attrs[av.NodeAttrView] = v.ID 1427 err = setNodeAttrs(insertedNode, tree, attrs) 1428 if err != nil { 1429 logging.LogWarnf("set node [%s] attrs failed: %s", operation.BlockID, err) 1430 return 1431 } 1432 } 1433 } 1434 } 1435 } 1436 1437 operation.ID = insertedNode.ID 1438 operation.ParentID = insertedNode.Parent.ID 1439 return 1440} 1441 1442func (tx *Transaction) processGlobalAssets(tree *parse.Tree) { 1443 if !tx.isGlobalAssetsInit { 1444 tx.assetsDir = getAssetsDir(filepath.Join(util.DataDir, tree.Box), filepath.Dir(filepath.Join(util.DataDir, tree.Box, tree.Path))) 1445 tx.isGlobalAssets = strings.HasPrefix(tx.assetsDir, filepath.Join(util.DataDir, "assets")) 1446 tx.isGlobalAssetsInit = true 1447 } 1448 1449 if tx.isGlobalAssets { 1450 return 1451 } 1452 1453 // 本地资源文件需要移动到用户手动建立的 assets 下 https://github.com/siyuan-note/siyuan/issues/2410 1454 ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { 1455 if !entering { 1456 return ast.WalkContinue 1457 } 1458 1459 if ast.NodeLinkDest == n.Type && bytes.HasPrefix(n.Tokens, []byte("assets/")) { 1460 assetP := gulu.Str.FromBytes(n.Tokens) 1461 assetPath, e := GetAssetAbsPath(assetP) 1462 if nil != e { 1463 logging.LogErrorf("get path of asset [%s] failed: %s", assetP, e) 1464 return ast.WalkContinue 1465 } 1466 1467 if !strings.HasPrefix(assetPath, filepath.Join(util.DataDir, "assets")) { 1468 // 非全局 assets 则跳过 1469 return ast.WalkContinue 1470 } 1471 1472 // 只有全局 assets 才移动到相对 assets 1473 targetP := filepath.Join(tx.assetsDir, filepath.Base(assetPath)) 1474 if e = filelock.Rename(assetPath, targetP); e != nil { 1475 logging.LogErrorf("copy path of asset from [%s] to [%s] failed: %s", assetPath, targetP, e) 1476 return ast.WalkContinue 1477 } 1478 } 1479 return ast.WalkContinue 1480 }) 1481} 1482 1483func (tx *Transaction) doUpdate(operation *Operation) (ret *TxErr) { 1484 id := operation.ID 1485 tree, err := tx.loadTree(id) 1486 if err != nil { 1487 logging.LogErrorf("load tree [%s] failed: %s", id, err) 1488 return &TxErr{code: TxErrCodeBlockNotFound, id: id} 1489 } 1490 1491 data := strings.ReplaceAll(operation.Data.(string), editor.FrontEndCaret, "") 1492 if "" == data { 1493 logging.LogErrorf("update data is nil") 1494 return &TxErr{code: TxErrCodeBlockNotFound, id: id} 1495 } 1496 1497 subTree := tx.luteEngine.BlockDOM2Tree(data) 1498 subTree.ID, subTree.Box, subTree.Path = tree.ID, tree.Box, tree.Path 1499 oldNode := treenode.GetNodeInTree(tree, id) 1500 if nil == oldNode { 1501 logging.LogErrorf("get node [%s] in tree [%s] failed", id, tree.Root.ID) 1502 return &TxErr{msg: ErrBlockNotFound.Error(), id: id} 1503 } 1504 1505 // 收集引用的定义块 ID 1506 oldDefIDs := getRefDefIDs(oldNode) 1507 var newDefIDs []string 1508 1509 var unlinks []*ast.Node 1510 ast.Walk(subTree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { 1511 if !entering { 1512 return ast.WalkContinue 1513 } 1514 1515 if ast.NodeTextMark == n.Type { 1516 if n.IsTextMarkType("inline-math") { 1517 if "" == strings.TrimSpace(n.TextMarkInlineMathContent) { 1518 // 剔除空白的行级公式 1519 unlinks = append(unlinks, n) 1520 } 1521 } else if n.IsTextMarkType("block-ref") { 1522 sql.CacheRef(subTree, n) 1523 1524 if "d" == n.TextMarkBlockRefSubtype { 1525 // 偶发编辑文档标题后引用处的动态锚文本不更新 https://github.com/siyuan-note/siyuan/issues/5891 1526 // 使用缓存的动态锚文本强制覆盖当前块中的引用节点动态锚文本 1527 if dRefText, ok := treenode.DynamicRefTexts.Load(n.TextMarkBlockRefID); ok && "" != dRefText { 1528 n.TextMarkTextContent = dRefText.(string) 1529 } 1530 } 1531 1532 newDefIDs = append(newDefIDs, n.TextMarkBlockRefID) 1533 } 1534 } 1535 return ast.WalkContinue 1536 }) 1537 for _, n := range unlinks { 1538 n.Unlink() 1539 } 1540 1541 oldDefIDs = gulu.Str.RemoveDuplicatedElem(oldDefIDs) 1542 newDefIDs = gulu.Str.RemoveDuplicatedElem(newDefIDs) 1543 refDefIDs := oldDefIDs 1544 1545 if !slices.Equal(oldDefIDs, newDefIDs) { // 如果引用发生了变化,则推送定义节点引用计数 1546 refDefIDs = append(refDefIDs, newDefIDs...) 1547 refDefIDs = gulu.Str.RemoveDuplicatedElem(refDefIDs) 1548 for _, defID := range refDefIDs { 1549 task.AppendAsyncTaskWithDelay(task.SetDefRefCount, util.SQLFlushInterval, refreshRefCount, defID) 1550 } 1551 } 1552 1553 updatedNode := subTree.Root.FirstChild 1554 if nil == updatedNode { 1555 logging.LogErrorf("get fist node in sub tree [%s] failed", subTree.Root.ID) 1556 return &TxErr{msg: ErrBlockNotFound.Error(), id: id} 1557 } 1558 if ast.NodeList == updatedNode.Type && ast.NodeList == oldNode.Parent.Type { 1559 updatedNode = updatedNode.FirstChild 1560 } 1561 1562 if oldNode.IsContainerBlock() { 1563 // 更新容器块的话需要考虑其子块中可能存在的折叠标题,需要把这些折叠标题的下方块移动到新节点下面 1564 treenode.MoveFoldHeading(updatedNode, oldNode) 1565 } 1566 1567 cache.PutBlockIAL(updatedNode.ID, parse.IAL2Map(updatedNode.KramdownIAL)) 1568 1569 if ast.NodeHTMLBlock == updatedNode.Type { 1570 content := string(updatedNode.Tokens) 1571 // 剔除连续的空行(包括空行内包含空格的情况) https://github.com/siyuan-note/siyuan/issues/15377 1572 var newLines []string 1573 lines := strings.Split(content, "\n") 1574 for _, line := range lines { 1575 if strings.TrimSpace(line) != "" { 1576 newLines = append(newLines, line) 1577 } 1578 } 1579 updatedNode.Tokens = []byte(strings.Join(newLines, "\n")) 1580 } 1581 1582 removedNodes := getRemovedNodes(oldNode, updatedNode) 1583 for _, n := range removedNodes { 1584 syncDelete2AvBlock(n, tree, tx) 1585 } 1586 1587 // 将不属于折叠标题的块移动到折叠标题下方,需要展开折叠标题 1588 needUnfoldParentHeading := 0 < oldNode.HeadingLevel && (0 == updatedNode.HeadingLevel || oldNode.HeadingLevel < updatedNode.HeadingLevel) 1589 1590 oldParentFoldedHeading := treenode.GetParentFoldedHeading(oldNode) 1591 // 将原先折叠标题下的块提升为与折叠标题同级或更高一级的标题时,需要在折叠标题后插入该提升后的标题块(只需要推送界面插入) 1592 needInsertAfterParentHeading := nil != oldParentFoldedHeading && 0 != updatedNode.HeadingLevel && updatedNode.HeadingLevel <= oldParentFoldedHeading.HeadingLevel 1593 1594 oldNode.InsertAfter(updatedNode) 1595 oldNode.Unlink() 1596 1597 if needUnfoldParentHeading { 1598 newParentFoldedHeading := treenode.GetParentFoldedHeading(updatedNode) 1599 if nil == oldParentFoldedHeading || (nil != newParentFoldedHeading && oldParentFoldedHeading.ID != newParentFoldedHeading.ID) { 1600 unfoldHeading(newParentFoldedHeading, updatedNode) 1601 } 1602 } 1603 1604 if needInsertAfterParentHeading { 1605 insertDom := data 1606 if 2 == len(tx.DoOperations) && "foldHeading" == tx.DoOperations[1].Action { 1607 children := treenode.HeadingChildren(updatedNode) 1608 for _, child := range children { 1609 ast.Walk(child, func(n *ast.Node, entering bool) ast.WalkStatus { 1610 if !entering || !n.IsBlock() { 1611 return ast.WalkContinue 1612 } 1613 1614 n.SetIALAttr("fold", "1") 1615 n.SetIALAttr("heading-fold", "1") 1616 return ast.WalkContinue 1617 }) 1618 } 1619 updatedNode.SetIALAttr("fold", "1") 1620 insertDom = tx.luteEngine.RenderNodeBlockDOM(updatedNode) 1621 } 1622 1623 evt := util.NewCmdResult("transactions", 0, util.PushModeBroadcast) 1624 evt.Data = []*Transaction{{ 1625 DoOperations: []*Operation{{Action: "insert", ID: updatedNode.ID, PreviousID: oldParentFoldedHeading.ID, Data: insertDom}}, 1626 UndoOperations: []*Operation{{Action: "delete", ID: updatedNode.ID}}, 1627 }} 1628 util.PushEvent(evt) 1629 } 1630 1631 createdUpdated(updatedNode) 1632 tx.nodes[updatedNode.ID] = updatedNode 1633 if err = tx.writeTree(tree); err != nil { 1634 return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: id} 1635 } 1636 1637 upsertAvBlockRel(updatedNode) 1638 1639 if ast.NodeAttributeView == updatedNode.Type { 1640 // 设置视图 https://github.com/siyuan-note/siyuan/issues/15279 1641 attrView, parseErr := av.ParseAttributeView(updatedNode.AttributeViewID) 1642 if nil == parseErr { 1643 v := attrView.GetView(attrView.ViewID) 1644 if nil != v { 1645 updatedNode.AttributeViewType = string(v.LayoutType) 1646 attrs := parse.IAL2Map(updatedNode.KramdownIAL) 1647 if "" == attrs[av.NodeAttrView] { 1648 attrs[av.NodeAttrView] = v.ID 1649 err = setNodeAttrs(updatedNode, tree, attrs) 1650 if err != nil { 1651 logging.LogWarnf("set node [%s] attrs failed: %s", operation.BlockID, err) 1652 return &TxErr{code: TxErrCodeBlockNotFound, id: id} 1653 } 1654 } 1655 } 1656 } 1657 } 1658 return 1659} 1660 1661func unfoldHeading(heading, currentNode *ast.Node) { 1662 if nil == heading { 1663 return 1664 } 1665 1666 children := treenode.HeadingChildren(heading) 1667 for _, child := range children { 1668 ast.Walk(child, func(n *ast.Node, entering bool) ast.WalkStatus { 1669 if !entering || !n.IsBlock() { 1670 return ast.WalkContinue 1671 } 1672 1673 n.RemoveIALAttr("fold") 1674 n.RemoveIALAttr("heading-fold") 1675 return ast.WalkContinue 1676 }) 1677 } 1678 heading.RemoveIALAttr("fold") 1679 heading.RemoveIALAttr("heading-fold") 1680 1681 util.BroadcastByType("protyle", "unfoldHeading", 0, "", map[string]interface{}{"id": heading.ID, "currentNodeID": currentNode.ID}) 1682} 1683 1684func getRefDefIDs(node *ast.Node) (refDefIDs []string) { 1685 ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus { 1686 if !entering { 1687 return ast.WalkContinue 1688 } 1689 1690 if treenode.IsBlockRef(n) { 1691 refDefIDs = append(refDefIDs, n.TextMarkBlockRefID) 1692 } else if treenode.IsEmbedBlockRef(n) { 1693 defID := treenode.GetEmbedBlockRef(n) 1694 refDefIDs = append(refDefIDs, defID) 1695 } 1696 return ast.WalkContinue 1697 }) 1698 refDefIDs = gulu.Str.RemoveDuplicatedElem(refDefIDs) 1699 return 1700} 1701 1702func getRemovedNodes(oldNode, newNode *ast.Node) (ret []*ast.Node) { 1703 oldNodes := map[string]*ast.Node{} 1704 ast.Walk(oldNode, func(n *ast.Node, entering bool) ast.WalkStatus { 1705 if !entering || !n.IsBlock() { 1706 return ast.WalkContinue 1707 } 1708 oldNodes[n.ID] = n 1709 return ast.WalkContinue 1710 }) 1711 ast.Walk(newNode, func(n *ast.Node, entering bool) ast.WalkStatus { 1712 if !entering || !n.IsBlock() { 1713 return ast.WalkContinue 1714 } 1715 if _, ok := oldNodes[n.ID]; ok { 1716 delete(oldNodes, n.ID) 1717 } 1718 return ast.WalkContinue 1719 }) 1720 for _, n := range oldNodes { 1721 ret = append(ret, n) 1722 } 1723 return 1724} 1725 1726func upsertAvBlockRel(node *ast.Node) { 1727 var affectedAvIDs []string 1728 ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus { 1729 if !entering { 1730 return ast.WalkContinue 1731 } 1732 1733 if ast.NodeAttributeView == n.Type { 1734 avID := n.AttributeViewID 1735 if changed := av.UpsertBlockRel(avID, n.ID); changed { 1736 affectedAvIDs = append(affectedAvIDs, avID) 1737 } 1738 } 1739 return ast.WalkContinue 1740 }) 1741 1742 updatedNodes := []*ast.Node{node} 1743 var parents []*ast.Node 1744 for parent := node.Parent; nil != parent && ast.NodeDocument != parent.Type; parent = parent.Parent { 1745 parents = append(parents, parent) 1746 } 1747 updatedNodes = append(updatedNodes, parents...) 1748 for _, updatedNode := range updatedNodes { 1749 ast.Walk(updatedNode, func(n *ast.Node, entering bool) ast.WalkStatus { 1750 avs := n.IALAttr(av.NodeAttrNameAvs) 1751 if "" == avs { 1752 return ast.WalkContinue 1753 } 1754 1755 avIDs := strings.Split(avs, ",") 1756 affectedAvIDs = append(affectedAvIDs, avIDs...) 1757 return ast.WalkContinue 1758 }) 1759 } 1760 1761 go func() { 1762 time.Sleep(100 * time.Millisecond) 1763 sql.FlushQueue() 1764 1765 affectedAvIDs = gulu.Str.RemoveDuplicatedElem(affectedAvIDs) 1766 var relatedAvIDs []string 1767 for _, avID := range affectedAvIDs { 1768 relatedAvIDs = append(relatedAvIDs, av.GetSrcAvIDs(avID)...) 1769 } 1770 affectedAvIDs = append(affectedAvIDs, relatedAvIDs...) 1771 affectedAvIDs = gulu.Str.RemoveDuplicatedElem(affectedAvIDs) 1772 for _, avID := range affectedAvIDs { 1773 attrView, _ := av.ParseAttributeView(avID) 1774 if nil != attrView { 1775 regenAttrViewGroups(attrView) 1776 av.SaveAttributeView(attrView) 1777 } 1778 1779 ReloadAttrView(avID) 1780 } 1781 }() 1782} 1783 1784func (tx *Transaction) doUpdateUpdated(operation *Operation) (ret *TxErr) { 1785 id := operation.ID 1786 tree, err := tx.loadTree(id) 1787 if err != nil { 1788 if errors.Is(err, ErrBlockNotFound) { 1789 logging.LogWarnf("not found block [%s]", id) 1790 return 1791 } 1792 1793 logging.LogErrorf("load tree [%s] failed: %s", id, err) 1794 return &TxErr{code: TxErrCodeBlockNotFound, id: id} 1795 } 1796 1797 node := treenode.GetNodeInTree(tree, id) 1798 if nil == node { 1799 logging.LogErrorf("get node [%s] in tree [%s] failed", id, tree.Root.ID) 1800 return &TxErr{msg: ErrBlockNotFound.Error(), id: id} 1801 } 1802 1803 node.SetIALAttr("updated", operation.Data.(string)) 1804 createdUpdated(node) 1805 tx.nodes[node.ID] = node 1806 if err = tx.writeTree(tree); err != nil { 1807 return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: id} 1808 } 1809 return 1810} 1811 1812func (tx *Transaction) doCreate(operation *Operation) (ret *TxErr) { 1813 tree := operation.Data.(*parse.Tree) 1814 tx.writeTree(tree) 1815 return 1816} 1817 1818func (tx *Transaction) doSetAttrs(operation *Operation) (ret *TxErr) { 1819 id := operation.ID 1820 tree, err := tx.loadTree(id) 1821 if err != nil { 1822 logging.LogErrorf("load tree [%s] failed: %s", id, err) 1823 return &TxErr{code: TxErrCodeBlockNotFound, id: id} 1824 } 1825 1826 node := treenode.GetNodeInTree(tree, id) 1827 if nil == node { 1828 logging.LogErrorf("get node [%s] in tree [%s] failed", id, tree.Root.ID) 1829 return &TxErr{code: TxErrCodeBlockNotFound, id: id} 1830 } 1831 1832 attrs := map[string]string{} 1833 if err = gulu.JSON.UnmarshalJSON([]byte(operation.Data.(string)), &attrs); err != nil { 1834 logging.LogErrorf("unmarshal attrs failed: %s", err) 1835 return &TxErr{code: TxErrCodeBlockNotFound, id: id} 1836 } 1837 1838 var invalidNames []string 1839 for name := range attrs { 1840 for i := 0; i < len(name); i++ { 1841 if !lex.IsASCIILetterNumHyphen(name[i]) { 1842 logging.LogWarnf("invalid attr name [%s]", name) 1843 invalidNames = append(invalidNames, name) 1844 } 1845 } 1846 } 1847 for _, name := range invalidNames { 1848 delete(attrs, name) 1849 } 1850 1851 for name, value := range attrs { 1852 if "" == value { 1853 node.RemoveIALAttr(name) 1854 } else { 1855 node.SetIALAttr(name, value) 1856 } 1857 } 1858 1859 if err = tx.writeTree(tree); err != nil { 1860 return 1861 } 1862 cache.PutBlockIAL(id, parse.IAL2Map(node.KramdownIAL)) 1863 return 1864} 1865 1866func refreshUpdated(node *ast.Node) { 1867 updated := util.CurrentTimeSecondsStr() 1868 node.SetIALAttr("updated", updated) 1869 parents := treenode.ParentNodesWithHeadings(node) 1870 for _, parent := range parents { // 更新所有父节点的更新时间字段 1871 parent.SetIALAttr("updated", updated) 1872 } 1873} 1874 1875func createdUpdated(node *ast.Node) { 1876 // 补全子节点的更新时间 Improve block update time filling https://github.com/siyuan-note/siyuan/issues/12182 1877 ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus { 1878 if !entering || !n.IsBlock() || ast.NodeKramdownBlockIAL == n.Type { 1879 return ast.WalkContinue 1880 } 1881 1882 updated := n.IALAttr("updated") 1883 if "" == updated && ast.IsNodeIDPattern(n.ID) { 1884 created := util.TimeFromID(n.ID) 1885 n.SetIALAttr("updated", created) 1886 } 1887 return ast.WalkContinue 1888 }) 1889 1890 created := util.TimeFromID(node.ID) 1891 updated := node.IALAttr("updated") 1892 if "" == updated { 1893 updated = created 1894 } 1895 if updated < created { 1896 updated = created 1897 } 1898 parents := treenode.ParentNodesWithHeadings(node) 1899 for _, parent := range parents { // 更新所有父节点的更新时间字段 1900 parent.SetIALAttr("updated", updated) 1901 cache.PutBlockIAL(parent.ID, parse.IAL2Map(parent.KramdownIAL)) 1902 } 1903} 1904 1905type Operation struct { 1906 Action string `json:"action"` 1907 Data interface{} `json:"data"` 1908 ID string `json:"id"` 1909 ParentID string `json:"parentID"` 1910 PreviousID string `json:"previousID"` 1911 NextID string `json:"nextID"` 1912 RetData interface{} `json:"retData"` 1913 BlockIDs []string `json:"blockIDs"` 1914 BlockID string `json:"blockID"` 1915 1916 DeckID string `json:"deckID"` // 用于添加/删除闪卡 1917 1918 AvID string `json:"avID"` // 属性视图 ID 1919 SrcIDs []string `json:"srcIDs"` // 用于从属性视图中删除行 1920 Srcs []map[string]interface{} `json:"srcs"` // 用于添加属性视图行(包括绑定块){id, content, isDetached} 1921 IsDetached bool `json:"isDetached"` // 用于标识是否未绑定块,仅存在于属性视图中 1922 Name string `json:"name"` // 属性视图列名 1923 Typ string `json:"type"` // 属性视图列类型 1924 Format string `json:"format"` // 属性视图列格式化 1925 KeyID string `json:"keyID"` // 属性视图字段 ID 1926 RowID string `json:"rowID"` // 属性视图行 ID 1927 IsTwoWay bool `json:"isTwoWay"` // 属性视图关联列是否是双向关系 1928 BackRelationKeyID string `json:"backRelationKeyID"` // 属性视图关联列回链关联列的 ID 1929 RemoveDest bool `json:"removeDest"` // 属性视图删除关联目标 1930 Layout av.LayoutType `json:"layout"` // 属性视图布局类型 1931 GroupID string `json:"groupID"` // 属性视图分组视图 ID 1932 TargetGroupID string `json:"targetGroupID"` // 属性视图目标分组视图 ID 1933 ViewID string `json:"viewID"` // 属性视图视图 ID 1934 IgnoreDefaultFill bool `json:"ignoreDefaultFill"` // 是否忽略默认填充 1935 1936 Context map[string]interface{} `json:"context"` // 上下文信息 1937} 1938 1939type Transaction struct { 1940 Timestamp int64 `json:"timestamp"` 1941 DoOperations []*Operation `json:"doOperations"` 1942 UndoOperations []*Operation `json:"undoOperations"` 1943 1944 trees map[string]*parse.Tree // 事务中变更的树 1945 nodes map[string]*ast.Node // 事务中变更的节点 1946 1947 isGlobalAssetsInit bool // 是否初始化过全局资源判断 1948 isGlobalAssets bool // 是否属于全局资源 1949 assetsDir string // 资源目录路径 1950 1951 luteEngine *lute.Lute 1952 m *sync.Mutex 1953 state atomic.Int32 // 0: 初始化,1:未提交,:2: 已提交,3: 已回滚 1954} 1955 1956func (tx *Transaction) WaitForCommit() { 1957 for { 1958 if 1 == tx.state.Load() { 1959 time.Sleep(10 * time.Millisecond) 1960 continue 1961 } 1962 return 1963 } 1964} 1965 1966func (tx *Transaction) begin() (err error) { 1967 tx.trees = map[string]*parse.Tree{} 1968 tx.nodes = map[string]*ast.Node{} 1969 tx.luteEngine = util.NewLute() 1970 tx.m.Lock() 1971 tx.state.Store(1) 1972 return 1973} 1974 1975func (tx *Transaction) commit() (err error) { 1976 for _, tree := range tx.trees { 1977 if err = writeTreeUpsertQueue(tree); err != nil { 1978 return 1979 } 1980 1981 var sources []interface{} 1982 sources = append(sources, tx) 1983 util.PushSaveDoc(tree.ID, "tx", sources) 1984 1985 checkUpsertInUserGuide(tree) 1986 } 1987 refreshDynamicRefTexts(tx.nodes, tx.trees) 1988 IncSync() 1989 tx.state.Store(2) 1990 tx.m.Unlock() 1991 return 1992} 1993 1994func (tx *Transaction) rollback() { 1995 tx.trees, tx.nodes = nil, nil 1996 tx.state.Store(3) 1997 tx.m.Unlock() 1998 return 1999} 2000 2001func (tx *Transaction) loadTreeByBlockTree(bt *treenode.BlockTree) (ret *parse.Tree, err error) { 2002 if nil == bt { 2003 return nil, ErrBlockNotFound 2004 } 2005 2006 ret = tx.trees[bt.RootID] 2007 if nil != ret { 2008 return 2009 } 2010 2011 ret, err = filesys.LoadTree(bt.BoxID, bt.Path, tx.luteEngine) 2012 if err != nil { 2013 return 2014 } 2015 tx.trees[bt.RootID] = ret 2016 return 2017} 2018 2019func (tx *Transaction) loadTree(id string) (ret *parse.Tree, err error) { 2020 var rootID, box, p string 2021 bt := treenode.GetBlockTree(id) 2022 if nil == bt { 2023 return nil, ErrBlockNotFound 2024 } 2025 rootID = bt.RootID 2026 box = bt.BoxID 2027 p = bt.Path 2028 2029 ret = tx.trees[rootID] 2030 if nil != ret { 2031 return 2032 } 2033 2034 ret, err = filesys.LoadTree(box, p, tx.luteEngine) 2035 if err != nil { 2036 return 2037 } 2038 tx.trees[rootID] = ret 2039 return 2040} 2041 2042func (tx *Transaction) writeTree(tree *parse.Tree) (err error) { 2043 tx.trees[tree.ID] = tree 2044 treenode.UpsertBlockTree(tree) 2045 return 2046} 2047 2048func getRefsCacheByDefNode(updateNode *ast.Node) (ret []*sql.Ref, changedNodes []*ast.Node) { 2049 changedNodesMap := map[string]*ast.Node{} 2050 ret = sql.GetRefsCacheByDefID(updateNode.ID) 2051 if nil != updateNode.Parent && ast.NodeDocument != updateNode.Parent.Type && 2052 updateNode.Parent.IsContainerBlock() && updateNode == treenode.FirstLeafBlock(updateNode.Parent) { 2053 // 如果是容器块下第一个叶子块,则需要向上查找引用 2054 for parent := updateNode.Parent; nil != parent; parent = parent.Parent { 2055 if ast.NodeDocument == parent.Type { 2056 break 2057 } 2058 2059 parentRefs := sql.GetRefsCacheByDefID(parent.ID) 2060 if 0 < len(parentRefs) { 2061 ret = append(ret, parentRefs...) 2062 if _, ok := changedNodesMap[parent.ID]; !ok { 2063 changedNodesMap[parent.ID] = parent 2064 } 2065 } 2066 } 2067 } 2068 if ast.NodeDocument != updateNode.Type && updateNode.IsContainerBlock() { 2069 // 如果是容器块,则需要向下查找引用 2070 ast.Walk(updateNode, func(n *ast.Node, entering bool) ast.WalkStatus { 2071 if !entering || !n.IsBlock() { 2072 return ast.WalkContinue 2073 } 2074 2075 childRefs := sql.GetRefsCacheByDefID(n.ID) 2076 if 0 < len(childRefs) { 2077 ret = append(ret, childRefs...) 2078 changedNodesMap[n.ID] = n 2079 } 2080 return ast.WalkContinue 2081 }) 2082 } 2083 if ast.NodeHeading == updateNode.Type && "1" == updateNode.IALAttr("fold") { 2084 // 如果是折叠标题,则需要向下查找引用 2085 children := treenode.HeadingChildren(updateNode) 2086 for _, child := range children { 2087 childRefs := sql.GetRefsCacheByDefID(child.ID) 2088 if 0 < len(childRefs) { 2089 ret = append(ret, childRefs...) 2090 changedNodesMap[child.ID] = child 2091 } 2092 } 2093 } 2094 for _, n := range changedNodesMap { 2095 changedNodes = append(changedNodes, n) 2096 } 2097 return 2098} 2099 2100var updateRefTextRenameDocs = map[string]*parse.Tree{} 2101var updateRefTextRenameDocLock = sync.Mutex{} 2102 2103func updateRefTextRenameDoc(renamedTree *parse.Tree) { 2104 updateRefTextRenameDocLock.Lock() 2105 updateRefTextRenameDocs[renamedTree.ID] = renamedTree 2106 updateRefTextRenameDocLock.Unlock() 2107} 2108 2109func FlushUpdateRefTextRenameDocJob() { 2110 sql.FlushQueue() 2111 flushUpdateRefTextRenameDoc() 2112} 2113 2114func flushUpdateRefTextRenameDoc() { 2115 updateRefTextRenameDocLock.Lock() 2116 defer updateRefTextRenameDocLock.Unlock() 2117 2118 for _, tree := range updateRefTextRenameDocs { 2119 refreshDynamicRefText(tree.Root, tree) 2120 } 2121 updateRefTextRenameDocs = map[string]*parse.Tree{} 2122} 2123 2124type changedDefNode struct { 2125 id string 2126 refText string 2127 refType string // ref-d/ref-s/embed 2128} 2129 2130func updateRefText(refNode *ast.Node, changedDefNodes map[string]*ast.Node) (changed bool, defNodes []*changedDefNode) { 2131 ast.Walk(refNode, func(n *ast.Node, entering bool) ast.WalkStatus { 2132 if !entering { 2133 return ast.WalkContinue 2134 } 2135 if treenode.IsBlockRef(n) { 2136 defID, refText, subtype := treenode.GetBlockRef(n) 2137 if "" == defID { 2138 return ast.WalkContinue 2139 } 2140 2141 defNode := changedDefNodes[defID] 2142 if nil == defNode { 2143 return ast.WalkSkipChildren 2144 } 2145 2146 changed = true 2147 if "d" == subtype { 2148 refText = strings.TrimSpace(getNodeRefText(defNode)) 2149 if "" == refText { 2150 refText = n.TextMarkBlockRefID 2151 } 2152 treenode.SetDynamicBlockRefText(n, refText) 2153 } 2154 defNodes = append(defNodes, &changedDefNode{id: defID, refText: refText, refType: "ref-" + subtype}) 2155 return ast.WalkContinue 2156 } else if treenode.IsEmbedBlockRef(n) { 2157 defID := treenode.GetEmbedBlockRef(n) 2158 changed = true 2159 defNodes = append(defNodes, &changedDefNode{id: defID, refType: "embed"}) 2160 return ast.WalkContinue 2161 } 2162 return ast.WalkContinue 2163 }) 2164 return 2165} 2166 2167func checkUpsertInUserGuide(tree *parse.Tree) { 2168 // In production mode, data reset warning pops up when editing data in the user guide https://github.com/siyuan-note/siyuan/issues/9757 2169 if "prod" == util.Mode && IsUserGuide(tree.Box) { 2170 util.PushErrMsg(Conf.Language(52), 7000) 2171 } 2172}