A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 306 lines 7.5 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 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}