A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
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, "\\", "\")
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}