bookmark.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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. package model
  17. import (
  18. "errors"
  19. "fmt"
  20. "sort"
  21. "strings"
  22. "github.com/88250/gulu"
  23. "github.com/88250/lute/parse"
  24. "github.com/siyuan-note/logging"
  25. "github.com/siyuan-note/siyuan/kernel/av"
  26. "github.com/siyuan-note/siyuan/kernel/cache"
  27. "github.com/siyuan-note/siyuan/kernel/sql"
  28. "github.com/siyuan-note/siyuan/kernel/treenode"
  29. "github.com/siyuan-note/siyuan/kernel/util"
  30. )
  31. func RemoveBookmark(bookmark string) (err error) {
  32. util.PushEndlessProgress(Conf.Language(116))
  33. bookmarks := sql.QueryBookmarkBlocksByKeyword(bookmark)
  34. treeBlocks := map[string][]string{}
  35. for _, tag := range bookmarks {
  36. if blocks, ok := treeBlocks[tag.RootID]; !ok {
  37. treeBlocks[tag.RootID] = []string{tag.ID}
  38. } else {
  39. treeBlocks[tag.RootID] = append(blocks, tag.ID)
  40. }
  41. }
  42. for treeID, blocks := range treeBlocks {
  43. util.PushEndlessProgress("[" + treeID + "]")
  44. tree, e := LoadTreeByBlockID(treeID)
  45. if nil != e {
  46. util.PushClearProgress()
  47. return e
  48. }
  49. for _, blockID := range blocks {
  50. node := treenode.GetNodeInTree(tree, blockID)
  51. if nil == node {
  52. continue
  53. }
  54. if bookmarkAttrVal := node.IALAttr("bookmark"); bookmarkAttrVal == bookmark {
  55. node.RemoveIALAttr("bookmark")
  56. cache.PutBlockIAL(node.ID, parse.IAL2Map(node.KramdownIAL))
  57. }
  58. }
  59. util.PushEndlessProgress(fmt.Sprintf(Conf.Language(111), util.EscapeHTML(tree.Root.IALAttr("title"))))
  60. if err = writeTreeUpsertQueue(tree); nil != err {
  61. util.ClearPushProgress(100)
  62. return
  63. }
  64. util.RandomSleep(50, 150)
  65. }
  66. util.ReloadUI()
  67. return
  68. }
  69. func RenameBookmark(oldBookmark, newBookmark string) (err error) {
  70. if invalidChar := treenode.ContainsMarker(newBookmark); "" != invalidChar {
  71. return errors.New(fmt.Sprintf(Conf.Language(112), invalidChar))
  72. }
  73. newBookmark = strings.TrimSpace(newBookmark)
  74. if "" == newBookmark {
  75. return errors.New(Conf.Language(126))
  76. }
  77. if oldBookmark == newBookmark {
  78. return
  79. }
  80. util.PushEndlessProgress(Conf.Language(110))
  81. bookmarks := sql.QueryBookmarkBlocksByKeyword(oldBookmark)
  82. treeBlocks := map[string][]string{}
  83. for _, tag := range bookmarks {
  84. if blocks, ok := treeBlocks[tag.RootID]; !ok {
  85. treeBlocks[tag.RootID] = []string{tag.ID}
  86. } else {
  87. treeBlocks[tag.RootID] = append(blocks, tag.ID)
  88. }
  89. }
  90. for treeID, blocks := range treeBlocks {
  91. util.PushEndlessProgress("[" + treeID + "]")
  92. tree, e := LoadTreeByBlockID(treeID)
  93. if nil != e {
  94. util.ClearPushProgress(100)
  95. return e
  96. }
  97. for _, blockID := range blocks {
  98. node := treenode.GetNodeInTree(tree, blockID)
  99. if nil == node {
  100. continue
  101. }
  102. if bookmarkAttrVal := node.IALAttr("bookmark"); bookmarkAttrVal == oldBookmark {
  103. node.SetIALAttr("bookmark", newBookmark)
  104. cache.PutBlockIAL(node.ID, parse.IAL2Map(node.KramdownIAL))
  105. }
  106. }
  107. util.PushEndlessProgress(fmt.Sprintf(Conf.Language(111), util.EscapeHTML(tree.Root.IALAttr("title"))))
  108. if err = writeTreeUpsertQueue(tree); nil != err {
  109. util.ClearPushProgress(100)
  110. return
  111. }
  112. util.RandomSleep(50, 150)
  113. }
  114. util.ReloadUI()
  115. return
  116. }
  117. type BookmarkLabel string
  118. type BookmarkBlocks []*Block
  119. type Bookmark struct {
  120. Name BookmarkLabel `json:"name"`
  121. Blocks []*Block `json:"blocks"`
  122. Type string `json:"type"` // "bookmark"
  123. Depth int `json:"depth"`
  124. Count int `json:"count"`
  125. }
  126. type Bookmarks []*Bookmark
  127. func (s Bookmarks) Len() int { return len(s) }
  128. func (s Bookmarks) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  129. func (s Bookmarks) Less(i, j int) bool { return s[i].Name < s[j].Name }
  130. func BookmarkLabels() (ret []string) {
  131. ret = sql.QueryBookmarkLabels()
  132. return
  133. }
  134. func BuildBookmark() (ret *Bookmarks) {
  135. WaitForWritingFiles()
  136. if !sql.IsEmptyQueue() {
  137. sql.WaitForWritingDatabase()
  138. }
  139. ret = &Bookmarks{}
  140. sqlBlocks := sql.QueryBookmarkBlocks()
  141. labelBlocks := map[BookmarkLabel]BookmarkBlocks{}
  142. blocks := fromSQLBlocks(&sqlBlocks, "", 0)
  143. luteEngine := NewLute()
  144. for _, block := range blocks {
  145. if "" != block.Name {
  146. // Blocks in the bookmark panel display their name instead of content https://github.com/siyuan-note/siyuan/issues/8514
  147. block.Content = block.Name
  148. } else if "NodeAttributeView" == block.Type {
  149. // Display database title in bookmark panel https://github.com/siyuan-note/siyuan/issues/11666
  150. avID := gulu.Str.SubStringBetween(block.Markdown, "av-id=\"", "\"")
  151. block.Content, _ = av.GetAttributeViewName(avID)
  152. } else {
  153. // Improve bookmark panel rendering https://github.com/siyuan-note/siyuan/issues/9361
  154. tree, err := LoadTreeByBlockID(block.ID)
  155. if nil != err {
  156. logging.LogErrorf("parse block [%s] failed: %s", block.ID, err)
  157. } else {
  158. n := treenode.GetNodeInTree(tree, block.ID)
  159. block.Content = renderOutline(n, luteEngine)
  160. }
  161. }
  162. label := BookmarkLabel(block.IAL["bookmark"])
  163. if bs, ok := labelBlocks[label]; ok {
  164. bs = append(bs, block)
  165. labelBlocks[label] = bs
  166. } else {
  167. labelBlocks[label] = []*Block{block}
  168. }
  169. }
  170. for label, bs := range labelBlocks {
  171. for _, b := range bs {
  172. b.Depth = 1
  173. }
  174. *ret = append(*ret, &Bookmark{Name: label, Blocks: bs, Type: "bookmark", Count: len(bs)})
  175. }
  176. sort.Sort(ret)
  177. return
  178. }