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 "strings"
23 "time"
24
25 "github.com/88250/gulu"
26 "github.com/88250/lute/ast"
27 "github.com/88250/lute/editor"
28 "github.com/88250/lute/lex"
29 "github.com/88250/lute/parse"
30 "github.com/araddon/dateparse"
31 "github.com/siyuan-note/siyuan/kernel/cache"
32 "github.com/siyuan-note/siyuan/kernel/filesys"
33 "github.com/siyuan-note/siyuan/kernel/sql"
34 "github.com/siyuan-note/siyuan/kernel/treenode"
35 "github.com/siyuan-note/siyuan/kernel/util"
36)
37
38func SetBlockReminder(id string, timed string) (err error) {
39 if !IsSubscriber() {
40 if "ios" == util.Container {
41 return errors.New(Conf.Language(122))
42 }
43 return errors.New(Conf.Language(29))
44 }
45
46 var timedMills int64
47 if "0" != timed {
48 t, e := dateparse.ParseIn(timed, time.Now().Location())
49 if nil != e {
50 return e
51 }
52 timedMills = t.UnixMilli()
53 }
54
55 FlushTxQueue()
56
57 attrs := sql.GetBlockAttrs(id)
58 tree, err := LoadTreeByBlockID(id)
59 if err != nil {
60 return
61 }
62
63 node := treenode.GetNodeInTree(tree, id)
64 if nil == node {
65 return errors.New(fmt.Sprintf(Conf.Language(15), id))
66 }
67
68 if ast.NodeDocument != node.Type && node.IsContainerBlock() {
69 node = treenode.FirstLeafBlock(node)
70 }
71 content := sql.NodeStaticContent(node, nil, false, false, false)
72 content = gulu.Str.SubStr(content, 128)
73 content = strings.ReplaceAll(content, editor.Zwsp, "")
74 err = SetCloudBlockReminder(id, content, timedMills)
75 if err != nil {
76 return
77 }
78
79 attrName := "custom-reminder-wechat"
80 if "0" == timed {
81 delete(attrs, attrName)
82 old := node.IALAttr(attrName)
83 oldTimedMills, e := dateparse.ParseIn(old, time.Now().Location())
84 if nil == e {
85 util.PushMsg(fmt.Sprintf(Conf.Language(109), oldTimedMills.Format("2006-01-02 15:04")), 3000)
86 }
87 node.RemoveIALAttr(attrName)
88 } else {
89 attrs[attrName] = timed
90 node.SetIALAttr(attrName, timed)
91 util.PushMsg(fmt.Sprintf(Conf.Language(101), time.UnixMilli(timedMills).Format("2006-01-02 15:04")), 5000)
92 }
93 if err = indexWriteTreeUpsertQueue(tree); err != nil {
94 return
95 }
96 IncSync()
97 cache.PutBlockIAL(id, attrs)
98 return
99}
100
101func BatchSetBlockAttrs(blockAttrs []map[string]interface{}) (err error) {
102 if util.ReadOnly {
103 return
104 }
105
106 FlushTxQueue()
107
108 var blockIDs []string
109 for _, blockAttr := range blockAttrs {
110 blockIDs = append(blockIDs, blockAttr["id"].(string))
111 }
112
113 trees := filesys.LoadTrees(blockIDs)
114 var nodes []*ast.Node
115 for _, blockAttr := range blockAttrs {
116 id := blockAttr["id"].(string)
117 tree := trees[id]
118 if nil == tree {
119 return errors.New(fmt.Sprintf(Conf.Language(15), id))
120 }
121
122 node := treenode.GetNodeInTree(tree, id)
123 if nil == node {
124 return errors.New(fmt.Sprintf(Conf.Language(15), id))
125 }
126
127 attrs := blockAttr["attrs"].(map[string]string)
128 oldAttrs, e := setNodeAttrs0(node, attrs)
129 if nil != e {
130 return e
131 }
132
133 cache.PutBlockIAL(node.ID, parse.IAL2Map(node.KramdownIAL))
134 pushBroadcastAttrTransactions(oldAttrs, node)
135 nodes = append(nodes, node)
136 }
137
138 for _, tree := range trees {
139 if err = indexWriteTreeUpsertQueue(tree); err != nil {
140 return
141 }
142 }
143
144 IncSync()
145 // 不做锚文本刷新
146 return
147}
148
149func SetBlockAttrs(id string, nameValues map[string]string) (err error) {
150 if util.ReadOnly {
151 return
152 }
153
154 FlushTxQueue()
155
156 tree, err := LoadTreeByBlockID(id)
157 if err != nil {
158 return err
159 }
160
161 node := treenode.GetNodeInTree(tree, id)
162 if nil == node {
163 return errors.New(fmt.Sprintf(Conf.Language(15), id))
164 }
165
166 err = setNodeAttrs(node, tree, nameValues)
167 return
168}
169
170func setNodeAttrs(node *ast.Node, tree *parse.Tree, nameValues map[string]string) (err error) {
171 oldAttrs, err := setNodeAttrs0(node, nameValues)
172 if err != nil {
173 return
174 }
175
176 if err = indexWriteTreeUpsertQueue(tree); err != nil {
177 return
178 }
179
180 IncSync()
181 cache.PutBlockIAL(node.ID, parse.IAL2Map(node.KramdownIAL))
182
183 pushBroadcastAttrTransactions(oldAttrs, node)
184
185 go func() {
186 sql.FlushQueue()
187 refreshDynamicRefText(node, tree)
188 }()
189 return
190}
191
192func setNodeAttrsWithTx(tx *Transaction, node *ast.Node, tree *parse.Tree, nameValues map[string]string) (err error) {
193 oldAttrs, err := setNodeAttrs0(node, nameValues)
194 if err != nil {
195 return
196 }
197
198 if err = tx.writeTree(tree); err != nil {
199 return
200 }
201
202 IncSync()
203 cache.PutBlockIAL(node.ID, parse.IAL2Map(node.KramdownIAL))
204 pushBroadcastAttrTransactions(oldAttrs, node)
205 return
206}
207
208func setNodeAttrs0(node *ast.Node, nameValues map[string]string) (oldAttrs map[string]string, err error) {
209 oldAttrs = parse.IAL2Map(node.KramdownIAL)
210
211 for name := range nameValues {
212 for i := 0; i < len(name); i++ {
213 if !lex.IsASCIILetterNumHyphen(name[i]) {
214 err = errors.New(fmt.Sprintf(Conf.Language(25), node.ID))
215 return
216 }
217 }
218 }
219
220 if tag, ok := nameValues["tags"]; ok {
221 var tags []string
222 tmp := strings.Split(tag, ",")
223 for _, t := range tmp {
224 t = util.RemoveInvalid(t)
225 t = strings.TrimSpace(t)
226 if "" != t {
227 tags = append(tags, t)
228 }
229 }
230 tags = gulu.Str.RemoveDuplicatedElem(tags)
231 if 0 < len(tags) {
232 nameValues["tags"] = strings.Join(tags, ",")
233 }
234 }
235
236 for name, value := range nameValues {
237 value = util.RemoveInvalidRetainCtrl(value)
238 value = strings.TrimSpace(value)
239 value = strings.TrimSuffix(value, ",")
240 if "" == value {
241 node.RemoveIALAttr(name)
242 } else {
243 node.SetIALAttr(name, value)
244 }
245 }
246
247 if oldAttrs["tags"] != nameValues["tags"] {
248 ReloadTag()
249 }
250 return
251}
252
253func pushBroadcastAttrTransactions(oldAttrs map[string]string, node *ast.Node) {
254 newAttrs := parse.IAL2Map(node.KramdownIAL)
255 data := map[string]interface{}{"old": oldAttrs, "new": newAttrs}
256 if "" != node.AttributeViewType {
257 data["data-av-type"] = node.AttributeViewType
258 }
259 doOp := &Operation{Action: "updateAttrs", Data: data, ID: node.ID}
260 evt := util.NewCmdResult("transactions", 0, util.PushModeBroadcast)
261 evt.Data = []*Transaction{{
262 DoOperations: []*Operation{doOp},
263 UndoOperations: []*Operation{},
264 }}
265 util.PushEvent(evt)
266}
267
268func ResetBlockAttrs(id string, nameValues map[string]string) (err error) {
269 tree, err := LoadTreeByBlockID(id)
270 if err != nil {
271 return err
272 }
273
274 node := treenode.GetNodeInTree(tree, id)
275 if nil == node {
276 return errors.New(fmt.Sprintf(Conf.Language(15), id))
277 }
278
279 for name := range nameValues {
280 for i := 0; i < len(name); i++ {
281 if !lex.IsASCIILetterNumHyphen(name[i]) {
282 return errors.New(fmt.Sprintf(Conf.Language(25), id))
283 }
284 }
285 }
286
287 node.ClearIALAttrs()
288 for name, value := range nameValues {
289 if "" != value {
290 node.SetIALAttr(name, value)
291 }
292 }
293
294 if ast.NodeDocument == node.Type {
295 // 修改命名文档块后引用动态锚文本未跟随 https://github.com/siyuan-note/siyuan/issues/6398
296 // 使用重命名文档队列来刷新引用锚文本
297 updateRefTextRenameDoc(tree)
298 }
299
300 if err = indexWriteTreeUpsertQueue(tree); err != nil {
301 return
302 }
303 IncSync()
304 cache.RemoveBlockIAL(id)
305 return
306}