A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at upstream/main 852 lines 17 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 api 18 19import ( 20 "errors" 21 "fmt" 22 "net/http" 23 "strings" 24 25 "github.com/88250/gulu" 26 "github.com/88250/lute/html" 27 "github.com/gin-gonic/gin" 28 "github.com/siyuan-note/logging" 29 "github.com/siyuan-note/siyuan/kernel/filesys" 30 "github.com/siyuan-note/siyuan/kernel/model" 31 "github.com/siyuan-note/siyuan/kernel/util" 32) 33 34func checkBlockRef(c *gin.Context) { 35 ret := gulu.Ret.NewResult() 36 defer c.JSON(http.StatusOK, ret) 37 38 arg, ok := util.JsonArg(c, ret) 39 if !ok { 40 return 41 } 42 43 idsArg := arg["ids"].([]interface{}) 44 var ids []string 45 for _, id := range idsArg { 46 ids = append(ids, id.(string)) 47 } 48 ids = gulu.Str.RemoveDuplicatedElem(ids) 49 50 ret.Data = model.CheckBlockRef(ids) 51} 52 53func getBlockTreeInfos(c *gin.Context) { 54 ret := gulu.Ret.NewResult() 55 defer c.JSON(http.StatusOK, ret) 56 57 arg, ok := util.JsonArg(c, ret) 58 if !ok { 59 return 60 } 61 62 var ids []string 63 idsArg := arg["ids"].([]interface{}) 64 for _, id := range idsArg { 65 ids = append(ids, id.(string)) 66 } 67 68 ret.Data = model.GetBlockTreeInfos(ids) 69} 70 71func getBlockSiblingID(c *gin.Context) { 72 ret := gulu.Ret.NewResult() 73 defer c.JSON(http.StatusOK, ret) 74 75 arg, ok := util.JsonArg(c, ret) 76 if !ok { 77 return 78 } 79 80 id := arg["id"].(string) 81 parent, previous, next := model.GetBlockSiblingID(id) 82 ret.Data = map[string]string{ 83 "parent": parent, 84 "next": next, 85 "previous": previous, 86 } 87} 88 89func getBlockRelevantIDs(c *gin.Context) { 90 ret := gulu.Ret.NewResult() 91 defer c.JSON(http.StatusOK, ret) 92 93 arg, ok := util.JsonArg(c, ret) 94 if !ok { 95 return 96 } 97 98 id := arg["id"].(string) 99 parentID, previousID, nextID, err := model.GetBlockRelevantIDs(id) 100 if nil != err { 101 ret.Code = -1 102 ret.Msg = err.Error() 103 ret.Data = map[string]interface{}{"closeTimeout": 7000} 104 return 105 } 106 107 ret.Data = map[string]string{ 108 "parentID": parentID, 109 "previousID": previousID, 110 "nextID": nextID, 111 } 112} 113 114func transferBlockRef(c *gin.Context) { 115 ret := gulu.Ret.NewResult() 116 defer c.JSON(http.StatusOK, ret) 117 118 arg, ok := util.JsonArg(c, ret) 119 if !ok { 120 return 121 } 122 123 fromID := arg["fromID"].(string) 124 if util.InvalidIDPattern(fromID, ret) { 125 return 126 } 127 toID := arg["toID"].(string) 128 if util.InvalidIDPattern(toID, ret) { 129 return 130 } 131 132 reloadUI := true 133 if nil != arg["reloadUI"] { 134 reloadUI = arg["reloadUI"].(bool) 135 } 136 137 var refIDs []string 138 if nil != arg["refIDs"] { 139 for _, refID := range arg["refIDs"].([]interface{}) { 140 refIDs = append(refIDs, refID.(string)) 141 } 142 } 143 144 err := model.TransferBlockRef(fromID, toID, refIDs) 145 if err != nil { 146 ret.Code = -1 147 ret.Msg = err.Error() 148 ret.Data = map[string]interface{}{"closeTimeout": 7000} 149 return 150 } 151 152 if reloadUI { 153 util.ReloadUI() 154 } 155} 156 157func swapBlockRef(c *gin.Context) { 158 ret := gulu.Ret.NewResult() 159 defer c.JSON(http.StatusOK, ret) 160 161 arg, ok := util.JsonArg(c, ret) 162 if !ok { 163 return 164 } 165 166 refID := arg["refID"].(string) 167 defID := arg["defID"].(string) 168 includeChildren := arg["includeChildren"].(bool) 169 err := model.SwapBlockRef(refID, defID, includeChildren) 170 if err != nil { 171 ret.Code = -1 172 ret.Msg = err.Error() 173 ret.Data = map[string]interface{}{"closeTimeout": 7000} 174 return 175 } 176} 177 178func getHeadingChildrenIDs(c *gin.Context) { 179 ret := gulu.Ret.NewResult() 180 defer c.JSON(http.StatusOK, ret) 181 182 arg, ok := util.JsonArg(c, ret) 183 if !ok { 184 return 185 } 186 187 id := arg["id"].(string) 188 ids := model.GetHeadingChildrenIDs(id) 189 ret.Data = ids 190} 191 192func appendHeadingChildren(c *gin.Context) { 193 ret := gulu.Ret.NewResult() 194 defer c.JSON(http.StatusOK, ret) 195 196 arg, ok := util.JsonArg(c, ret) 197 if !ok { 198 return 199 } 200 201 id := arg["id"].(string) 202 childrenDOM := arg["childrenDOM"].(string) 203 model.AppendHeadingChildren(id, childrenDOM) 204} 205 206func getHeadingChildrenDOM(c *gin.Context) { 207 ret := gulu.Ret.NewResult() 208 defer c.JSON(http.StatusOK, ret) 209 210 arg, ok := util.JsonArg(c, ret) 211 if !ok { 212 return 213 } 214 215 id := arg["id"].(string) 216 removeFoldAttr := true 217 if nil != arg["removeFoldAttr"] { 218 removeFoldAttr = arg["removeFoldAttr"].(bool) 219 } 220 dom := model.GetHeadingChildrenDOM(id, removeFoldAttr) 221 ret.Data = dom 222} 223 224func getHeadingDeleteTransaction(c *gin.Context) { 225 ret := gulu.Ret.NewResult() 226 defer c.JSON(http.StatusOK, ret) 227 228 arg, ok := util.JsonArg(c, ret) 229 if !ok { 230 return 231 } 232 233 id := arg["id"].(string) 234 235 transaction, err := model.GetHeadingDeleteTransaction(id) 236 if err != nil { 237 ret.Code = -1 238 ret.Msg = err.Error() 239 ret.Data = map[string]interface{}{"closeTimeout": 7000} 240 return 241 } 242 243 ret.Data = transaction 244} 245 246func getHeadingInsertTransaction(c *gin.Context) { 247 ret := gulu.Ret.NewResult() 248 defer c.JSON(http.StatusOK, ret) 249 250 arg, ok := util.JsonArg(c, ret) 251 if !ok { 252 return 253 } 254 255 id := arg["id"].(string) 256 257 transaction, err := model.GetHeadingInsertTransaction(id) 258 if err != nil { 259 ret.Code = -1 260 ret.Msg = err.Error() 261 ret.Data = map[string]interface{}{"closeTimeout": 7000} 262 return 263 } 264 265 ret.Data = transaction 266} 267 268func getHeadingLevelTransaction(c *gin.Context) { 269 ret := gulu.Ret.NewResult() 270 defer c.JSON(http.StatusOK, ret) 271 272 arg, ok := util.JsonArg(c, ret) 273 if !ok { 274 return 275 } 276 277 id := arg["id"].(string) 278 level := int(arg["level"].(float64)) 279 280 transaction, err := model.GetHeadingLevelTransaction(id, level) 281 if err != nil { 282 ret.Code = -1 283 ret.Msg = err.Error() 284 ret.Data = map[string]interface{}{"closeTimeout": 7000} 285 return 286 } 287 288 ret.Data = transaction 289} 290 291func setBlockReminder(c *gin.Context) { 292 ret := gulu.Ret.NewResult() 293 defer c.JSON(http.StatusOK, ret) 294 295 arg, ok := util.JsonArg(c, ret) 296 if !ok { 297 return 298 } 299 300 id := arg["id"].(string) 301 timed := arg["timed"].(string) // yyyyMMddHHmmss 302 err := model.SetBlockReminder(id, timed) 303 if err != nil { 304 ret.Code = -1 305 ret.Msg = err.Error() 306 ret.Data = map[string]interface{}{"closeTimeout": 7000} 307 return 308 } 309} 310 311func getUnfoldedParentID(c *gin.Context) { 312 ret := gulu.Ret.NewResult() 313 defer c.JSON(http.StatusOK, ret) 314 315 arg, ok := util.JsonArg(c, ret) 316 if !ok { 317 return 318 } 319 320 id := arg["id"].(string) 321 parentID := model.GetUnfoldedParentID(id) 322 ret.Data = map[string]interface{}{ 323 "parentID": parentID, 324 } 325} 326 327func checkBlockFold(c *gin.Context) { 328 ret := gulu.Ret.NewResult() 329 defer c.JSON(http.StatusOK, ret) 330 331 arg, ok := util.JsonArg(c, ret) 332 if !ok { 333 return 334 } 335 336 id := arg["id"].(string) 337 isFolded, isRoot := model.IsBlockFolded(id) 338 ret.Data = map[string]interface{}{ 339 "isFolded": isFolded, 340 "isRoot": isRoot, 341 } 342} 343 344func checkBlockExist(c *gin.Context) { 345 ret := gulu.Ret.NewResult() 346 defer c.JSON(http.StatusOK, ret) 347 348 arg, ok := util.JsonArg(c, ret) 349 if !ok { 350 return 351 } 352 353 id := arg["id"].(string) 354 b, err := model.GetBlock(id, nil) 355 if errors.Is(err, model.ErrIndexing) { 356 ret.Code = 0 357 ret.Data = false 358 return 359 } 360 ret.Data = nil != b 361} 362 363func getDocInfo(c *gin.Context) { 364 ret := gulu.Ret.NewResult() 365 defer c.JSON(http.StatusOK, ret) 366 367 arg, ok := util.JsonArg(c, ret) 368 if !ok { 369 return 370 } 371 372 id := arg["id"].(string) 373 info := model.GetDocInfo(id) 374 if nil == info { 375 ret.Code = -1 376 ret.Msg = fmt.Sprintf(model.Conf.Language(15), id) 377 return 378 } 379 ret.Data = info 380} 381 382func getDocsInfo(c *gin.Context) { 383 ret := gulu.Ret.NewResult() 384 defer c.JSON(http.StatusOK, ret) 385 386 arg, ok := util.JsonArg(c, ret) 387 if !ok { 388 return 389 } 390 idsArg := arg["ids"].([]interface{}) 391 var ids []string 392 for _, id := range idsArg { 393 ids = append(ids, id.(string)) 394 } 395 queryRefCount := arg["refCount"].(bool) 396 queryAv := arg["av"].(bool) 397 info := model.GetDocsInfo(ids, queryRefCount, queryAv) 398 if nil == info { 399 ret.Code = -1 400 ret.Msg = fmt.Sprintf(model.Conf.Language(15), ids) 401 return 402 } 403 ret.Data = info 404} 405 406func getRecentUpdatedBlocks(c *gin.Context) { 407 ret := gulu.Ret.NewResult() 408 defer c.JSON(http.StatusOK, ret) 409 410 blocks := model.RecentUpdatedBlocks() 411 ret.Data = blocks 412} 413 414func getContentWordCount(c *gin.Context) { 415 ret := gulu.Ret.NewResult() 416 defer c.JSON(http.StatusOK, ret) 417 418 arg, ok := util.JsonArg(c, ret) 419 if !ok { 420 return 421 } 422 423 content := arg["content"].(string) 424 ret.Data = map[string]any{ 425 "reqId": arg["reqId"], 426 "stat": filesys.ContentStat(content), 427 } 428} 429 430func getBlocksWordCount(c *gin.Context) { 431 ret := gulu.Ret.NewResult() 432 defer c.JSON(http.StatusOK, ret) 433 434 arg, ok := util.JsonArg(c, ret) 435 if !ok { 436 return 437 } 438 439 idsArg := arg["ids"].([]interface{}) 440 var ids []string 441 for _, id := range idsArg { 442 ids = append(ids, id.(string)) 443 } 444 ret.Data = map[string]any{ 445 "reqId": arg["reqId"], 446 "stat": filesys.BlocksWordCount(ids), 447 } 448} 449 450func getTreeStat(c *gin.Context) { 451 ret := gulu.Ret.NewResult() 452 defer c.JSON(http.StatusOK, ret) 453 454 arg, ok := util.JsonArg(c, ret) 455 if !ok { 456 return 457 } 458 459 id := arg["id"].(string) 460 ret.Data = map[string]any{ 461 "reqId": arg["reqId"], 462 "stat": filesys.StatTree(id), 463 } 464} 465 466func getDOMText(c *gin.Context) { 467 ret := gulu.Ret.NewResult() 468 defer c.JSON(http.StatusOK, ret) 469 470 arg, ok := util.JsonArg(c, ret) 471 if !ok { 472 return 473 } 474 475 dom := arg["dom"].(string) 476 ret.Data = model.GetDOMText(dom) 477} 478 479func getRefText(c *gin.Context) { 480 ret := gulu.Ret.NewResult() 481 defer c.JSON(http.StatusOK, ret) 482 483 arg, ok := util.JsonArg(c, ret) 484 if !ok { 485 return 486 } 487 488 id := arg["id"].(string) 489 if util.InvalidIDPattern(id, ret) { 490 return 491 } 492 493 refText := model.GetBlockRefText(id) 494 if "" == refText { 495 // 空块返回 id https://github.com/siyuan-note/siyuan/issues/10259 496 refText = id 497 ret.Data = refText 498 return 499 } 500 501 if strings.Count(refText, "\\") == len(refText) { 502 // 全部都是 \ 的话使用实体 https://github.com/siyuan-note/siyuan/issues/11473 503 refText = strings.ReplaceAll(refText, "\\", "&#92;") 504 ret.Data = refText 505 return 506 } 507 508 ret.Data = refText 509} 510 511func getRefIDs(c *gin.Context) { 512 ret := gulu.Ret.NewResult() 513 defer c.JSON(http.StatusOK, ret) 514 515 arg, ok := util.JsonArg(c, ret) 516 if !ok { 517 return 518 } 519 520 if nil == arg["id"] { 521 arg["id"] = "" 522 } 523 524 id := arg["id"].(string) 525 refDefs, originalRefBlockIDs := model.GetBlockRefs(id) 526 ret.Data = map[string]any{ 527 "refDefs": refDefs, 528 "originalRefBlockIDs": originalRefBlockIDs, 529 } 530} 531 532func getRefIDsByFileAnnotationID(c *gin.Context) { 533 ret := gulu.Ret.NewResult() 534 defer c.JSON(http.StatusOK, ret) 535 536 arg, ok := util.JsonArg(c, ret) 537 if !ok { 538 return 539 } 540 541 id := arg["id"].(string) 542 refIDs := model.GetBlockRefIDsByFileAnnotationID(id) 543 var retRefDefs []model.RefDefs 544 for _, blockID := range refIDs { 545 retRefDefs = append(retRefDefs, model.RefDefs{RefID: blockID, DefIDs: []string{}}) 546 } 547 if 1 > len(retRefDefs) { 548 retRefDefs = []model.RefDefs{} 549 } 550 551 ret.Data = map[string]any{ 552 "refDefs": retRefDefs, 553 } 554} 555 556func getBlockDefIDsByRefText(c *gin.Context) { 557 ret := gulu.Ret.NewResult() 558 defer c.JSON(http.StatusOK, ret) 559 560 arg, ok := util.JsonArg(c, ret) 561 if !ok { 562 return 563 } 564 565 anchor := arg["anchor"].(string) 566 excludeIDsArg := arg["excludeIDs"].([]interface{}) 567 var excludeIDs []string 568 for _, excludeID := range excludeIDsArg { 569 excludeIDs = append(excludeIDs, excludeID.(string)) 570 } 571 excludeIDs = nil // 不限制虚拟引用搜索自己 https://ld246.com/article/1633243424177 572 ids := model.GetBlockDefIDsByRefText(anchor, excludeIDs) 573 var retRefDefs []model.RefDefs 574 for _, id := range ids { 575 retRefDefs = append(retRefDefs, model.RefDefs{RefID: id, DefIDs: []string{}}) 576 } 577 if 1 > len(retRefDefs) { 578 retRefDefs = []model.RefDefs{} 579 } 580 581 ret.Data = map[string]any{ 582 "refDefs": retRefDefs, 583 } 584} 585 586func getBlockBreadcrumb(c *gin.Context) { 587 ret := gulu.Ret.NewResult() 588 defer c.JSON(http.StatusOK, ret) 589 590 arg, ok := util.JsonArg(c, ret) 591 if !ok { 592 return 593 } 594 595 id := arg["id"].(string) 596 excludeTypesArg := arg["excludeTypes"] 597 var excludeTypes []string 598 if nil != excludeTypesArg { 599 for _, excludeType := range excludeTypesArg.([]interface{}) { 600 excludeTypes = append(excludeTypes, excludeType.(string)) 601 } 602 } 603 604 blockPath, err := model.BuildBlockBreadcrumb(id, excludeTypes) 605 if err != nil { 606 ret.Code = -1 607 ret.Msg = err.Error() 608 return 609 } 610 611 ret.Data = blockPath 612} 613 614func getBlockIndex(c *gin.Context) { 615 ret := gulu.Ret.NewResult() 616 defer c.JSON(http.StatusOK, ret) 617 618 arg, ok := util.JsonArg(c, ret) 619 if !ok { 620 return 621 } 622 623 id := arg["id"].(string) 624 index := model.GetBlockIndex(id) 625 ret.Data = index 626} 627 628func getBlocksIndexes(c *gin.Context) { 629 ret := gulu.Ret.NewResult() 630 defer c.JSON(http.StatusOK, ret) 631 632 arg, ok := util.JsonArg(c, ret) 633 if !ok { 634 return 635 } 636 637 idsArg := arg["ids"].([]interface{}) 638 var ids []string 639 for _, id := range idsArg { 640 ids = append(ids, id.(string)) 641 } 642 index := model.GetBlocksIndexes(ids) 643 ret.Data = index 644} 645 646func getBlockInfo(c *gin.Context) { 647 ret := gulu.Ret.NewResult() 648 defer c.JSON(http.StatusOK, ret) 649 650 arg, ok := util.JsonArg(c, ret) 651 if !ok { 652 return 653 } 654 655 id := arg["id"].(string) 656 657 // 仅在此处使用带重建索引的加载函数,其他地方不要使用 658 tree, err := model.LoadTreeByBlockIDWithReindex(id) 659 if errors.Is(err, model.ErrIndexing) { 660 ret.Code = 3 661 ret.Msg = model.Conf.Language(56) 662 return 663 } 664 665 block, _ := model.GetBlock(id, tree) 666 if nil == block { 667 ret.Code = -1 668 ret.Msg = fmt.Sprintf(model.Conf.Language(15), id) 669 return 670 } 671 672 var rootChildID string 673 b := block 674 for i := 0; i < 128; i++ { 675 parentID := b.ParentID 676 if "" == parentID { 677 rootChildID = b.ID 678 break 679 } 680 if b, _ = model.GetBlock(parentID, tree); nil == b { 681 logging.LogErrorf("not found parent") 682 break 683 } 684 } 685 686 root, err := model.GetBlock(block.RootID, tree) 687 if errors.Is(err, model.ErrIndexing) { 688 ret.Code = 3 689 ret.Data = model.Conf.Language(56) 690 return 691 } 692 rootTitle := root.IAL["title"] 693 rootTitle = html.UnescapeString(rootTitle) 694 icon := root.IAL["icon"] 695 ret.Data = map[string]string{ 696 "box": block.Box, 697 "path": block.Path, 698 "rootID": block.RootID, 699 "rootTitle": rootTitle, 700 "rootChildID": rootChildID, 701 "rootIcon": icon, 702 } 703} 704 705func getBlockDOM(c *gin.Context) { 706 ret := gulu.Ret.NewResult() 707 defer c.JSON(http.StatusOK, ret) 708 709 arg, ok := util.JsonArg(c, ret) 710 if !ok { 711 return 712 } 713 714 id := arg["id"].(string) 715 dom := model.GetBlockDOM(id) 716 ret.Data = map[string]string{ 717 "id": id, 718 "dom": dom, 719 } 720} 721 722func getBlockDOMs(c *gin.Context) { 723 ret := gulu.Ret.NewResult() 724 defer c.JSON(http.StatusOK, ret) 725 726 arg, ok := util.JsonArg(c, ret) 727 if !ok { 728 return 729 } 730 731 idsArg := arg["ids"].([]interface{}) 732 var ids []string 733 for _, id := range idsArg { 734 ids = append(ids, id.(string)) 735 } 736 737 doms := model.GetBlockDOMs(ids) 738 ret.Data = doms 739} 740 741func getBlockDOMWithEmbed(c *gin.Context) { 742 ret := gulu.Ret.NewResult() 743 defer c.JSON(http.StatusOK, ret) 744 745 arg, ok := util.JsonArg(c, ret) 746 if !ok { 747 return 748 } 749 750 id := arg["id"].(string) 751 dom := model.GetBlockDOMWithEmbed(id) 752 ret.Data = map[string]string{ 753 "id": id, 754 "dom": dom, 755 } 756} 757 758func getBlockDOMsWithEmbed(c *gin.Context) { 759 ret := gulu.Ret.NewResult() 760 defer c.JSON(http.StatusOK, ret) 761 762 arg, ok := util.JsonArg(c, ret) 763 if !ok { 764 return 765 } 766 767 idsArg := arg["ids"].([]interface{}) 768 var ids []string 769 for _, id := range idsArg { 770 ids = append(ids, id.(string)) 771 } 772 773 doms := model.GetBlockDOMsWithEmbed(ids) 774 ret.Data = doms 775} 776 777func getBlockKramdown(c *gin.Context) { 778 ret := gulu.Ret.NewResult() 779 defer c.JSON(http.StatusOK, ret) 780 781 arg, ok := util.JsonArg(c, ret) 782 if !ok { 783 return 784 } 785 786 id := arg["id"].(string) 787 if util.InvalidIDPattern(id, ret) { 788 return 789 } 790 791 // md:Markdown 标记符模式,使用标记符导出 792 // textmark:文本标记模式,使用 span 标签导出 793 // https://github.com/siyuan-note/siyuan/issues/13183 794 mode := "md" 795 if modeArg := arg["mode"]; nil != modeArg { 796 mode = modeArg.(string) 797 if "md" != mode && "textmark" != mode { 798 ret.Code = -1 799 ret.Msg = "Invalid mode" 800 return 801 } 802 } 803 804 kramdown := model.GetBlockKramdown(id, mode) 805 ret.Data = map[string]string{ 806 "id": id, 807 "kramdown": kramdown, 808 } 809} 810 811func getChildBlocks(c *gin.Context) { 812 ret := gulu.Ret.NewResult() 813 defer c.JSON(http.StatusOK, ret) 814 815 arg, ok := util.JsonArg(c, ret) 816 if !ok { 817 return 818 } 819 820 id := arg["id"].(string) 821 if util.InvalidIDPattern(id, ret) { 822 return 823 } 824 825 ret.Data = model.GetChildBlocks(id) 826} 827 828func getTailChildBlocks(c *gin.Context) { 829 ret := gulu.Ret.NewResult() 830 defer c.JSON(http.StatusOK, ret) 831 832 arg, ok := util.JsonArg(c, ret) 833 if !ok { 834 return 835 } 836 837 id := arg["id"].(string) 838 if util.InvalidIDPattern(id, ret) { 839 return 840 } 841 842 var n int 843 nArg := arg["n"] 844 if nil != nArg { 845 n = int(nArg.(float64)) 846 } 847 if 1 > n { 848 n = 7 849 } 850 851 ret.Data = model.GetTailChildBlocks(id, n) 852}