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