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 "bytes"
21 "fmt"
22 "math/rand"
23 "os"
24 "path/filepath"
25 "slices"
26 "sort"
27 "strconv"
28 "strings"
29 "time"
30 "unicode/utf8"
31
32 "github.com/88250/gulu"
33 "github.com/88250/lute/ast"
34 "github.com/88250/lute/parse"
35 "github.com/jinzhu/copier"
36 "github.com/siyuan-note/filelock"
37 "github.com/siyuan-note/logging"
38 "github.com/siyuan-note/siyuan/kernel/av"
39 "github.com/siyuan-note/siyuan/kernel/cache"
40 "github.com/siyuan-note/siyuan/kernel/filesys"
41 "github.com/siyuan-note/siyuan/kernel/sql"
42 "github.com/siyuan-note/siyuan/kernel/treenode"
43 "github.com/siyuan-note/siyuan/kernel/util"
44 "github.com/xrash/smetrics"
45)
46
47func GetAttributeViewItemIDs(avID string, blockIDs []string) (ret map[string]string) {
48 ret = map[string]string{}
49 for _, blockID := range blockIDs {
50 ret[blockID] = ""
51 }
52
53 attrView, err := av.ParseAttributeView(avID)
54 if err != nil {
55 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
56 return
57 }
58
59 blockKv := attrView.GetBlockKeyValues()
60 for _, b := range blockKv.Values {
61 if _, ok := ret[b.Block.ID]; ok {
62 ret[b.Block.ID] = b.BlockID
63 }
64 }
65 return
66}
67
68func GetAttributeViewBoundBlockIDs(avID string, itemIDs []string) (ret map[string]string) {
69 ret = map[string]string{}
70 for _, itemID := range itemIDs {
71 ret[itemID] = ""
72 }
73
74 attrView, err := av.ParseAttributeView(avID)
75 if err != nil {
76 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
77 return
78 }
79
80 blockKv := attrView.GetBlockKeyValues()
81 for _, b := range blockKv.Values {
82 if _, ok := ret[b.BlockID]; ok {
83 ret[b.BlockID] = b.Block.ID
84 }
85 }
86 return
87}
88
89func GetAttrViewAddingBlockDefaultValues(avID, viewID, groupID, previousBlockID, addingBlockID string) (ret map[string]*av.Value) {
90 ret = map[string]*av.Value{}
91
92 attrView, err := av.ParseAttributeView(avID)
93 if err != nil {
94 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
95 return
96 }
97
98 view, _ := attrView.GetCurrentView(viewID)
99 if nil == view {
100 logging.LogErrorf("view [%s] not found in attribute view [%s]", viewID, avID)
101 return
102 }
103
104 if 1 > len(view.Filters) && !view.IsGroupView() {
105 // 没有过滤条件也没有分组条件时忽略
106 return
107 }
108
109 groupView := view
110 if "" != groupID {
111 groupView = view.GetGroupByID(groupID)
112 }
113 if nil == groupView {
114 logging.LogErrorf("group [%s] not found in view [%s] of attribute view [%s]", groupID, viewID, avID)
115 return
116 }
117
118 ret = getAttrViewAddingBlockDefaultValues(attrView, view, groupView, previousBlockID, addingBlockID)
119 for _, value := range ret {
120 // 主键都不返回内容,避免闪烁 https://github.com/siyuan-note/siyuan/issues/15561#issuecomment-3184746195
121 if av.KeyTypeBlock == value.Type {
122 value.Block.Content = ""
123 }
124 }
125 return
126}
127
128func getAttrViewAddingBlockDefaultValues(attrView *av.AttributeView, view, groupView *av.View, previousItemID, addingItemID string) (ret map[string]*av.Value) {
129 ret = map[string]*av.Value{}
130
131 if 1 > len(view.Filters) && !view.IsGroupView() {
132 // 没有过滤条件也没有分组条件时忽略
133 return
134 }
135
136 nearItem := getNearItem(attrView, view, groupView, previousItemID)
137
138 // 使用模板或汇总进行过滤或分组时,需要解析涉及到的其他字段
139 templateRelevantKeys, rollupRelevantKeys := map[string][]*av.Key{}, map[string]*av.Key{}
140 for _, keyValues := range attrView.KeyValues {
141 if av.KeyTypeTemplate == keyValues.Key.Type {
142 if tplRelevantKeys := sql.GetTemplateKeyRelevantKeys(attrView, keyValues.Key); 0 < len(tplRelevantKeys) {
143 for _, k := range tplRelevantKeys {
144 templateRelevantKeys[keyValues.Key.ID] = append(templateRelevantKeys[keyValues.Key.ID], k)
145 }
146 }
147 } else if av.KeyTypeRollup == keyValues.Key.Type {
148 if nil != keyValues.Key.Rollup {
149 relKey, _ := attrView.GetKey(keyValues.Key.Rollup.RelationKeyID)
150 if nil != relKey && nil != relKey.Relation {
151 if attrView.ID == relKey.Relation.AvID {
152 if k, _ := attrView.GetKey(keyValues.Key.Rollup.KeyID); nil != k {
153 rollupRelevantKeys[k.ID] = k
154 }
155 }
156 }
157 }
158 }
159 }
160
161 filterKeyIDs := map[string]bool{}
162 for _, filter := range view.Filters {
163 filterKeyIDs[filter.Column] = true
164 keyValues, _ := attrView.GetKeyValues(filter.Column)
165 if nil == keyValues {
166 continue
167 }
168
169 if av.KeyTypeTemplate == keyValues.Key.Type && nil != nearItem {
170 if keys := templateRelevantKeys[keyValues.Key.ID]; 0 < len(keys) {
171 for _, k := range keys {
172 if nil == ret[k.ID] {
173 ret[k.ID] = getNewValueByNearItem(nearItem, k, addingItemID)
174 }
175 }
176 }
177 continue
178 }
179
180 if av.KeyTypeRollup == keyValues.Key.Type && nil != nearItem {
181 if relKey, ok := rollupRelevantKeys[keyValues.Key.ID]; ok {
182 if nil == ret[relKey.ID] {
183 ret[relKey.ID] = getNewValueByNearItem(nearItem, relKey, addingItemID)
184 }
185 }
186 continue
187 }
188
189 if av.KeyTypeMAsset == keyValues.Key.Type {
190 if nil != nearItem {
191 if _, ok := ret[keyValues.Key.ID]; !ok {
192 ret[keyValues.Key.ID] = getNewValueByNearItem(nearItem, keyValues.Key, addingItemID)
193 }
194 }
195 return
196 }
197
198 newValue := filter.GetAffectValue(keyValues.Key, addingItemID)
199 if nil == newValue {
200 newValue = getNewValueByNearItem(nearItem, keyValues.Key, addingItemID)
201 }
202 if nil != newValue {
203 if av.KeyTypeDate == keyValues.Key.Type {
204 if nil != nearItem {
205 nearValue := getNewValueByNearItem(nearItem, keyValues.Key, addingItemID)
206 newValue.Date.IsNotTime = nearValue.Date.IsNotTime
207 }
208
209 if nil != keyValues.Key.Date && keyValues.Key.Date.AutoFillNow {
210 newValue.Date.Content = time.Now().UnixMilli()
211 newValue.Date.IsNotEmpty = true
212 }
213 }
214
215 ret[keyValues.Key.ID] = newValue
216 }
217 }
218
219 groupKey := view.GetGroupKey(attrView)
220 if nil == groupKey {
221 return
222 }
223
224 keyValues, _ := attrView.GetKeyValues(groupKey.ID)
225 if nil == keyValues {
226 return
227 }
228
229 newValue := getNewValueByNearItem(nearItem, groupKey, addingItemID)
230 if av.KeyTypeSelect == groupKey.Type || av.KeyTypeMSelect == groupKey.Type {
231 // 因为单选或多选只能按选项分组,并且可能存在空白分组(找不到临近项),所以单选或多选类型的分组字段使用分组值内容对应的选项
232 if opt := groupKey.GetOption(groupView.GetGroupValue()); nil != opt && groupValueDefault != groupView.GetGroupValue() {
233 if nil == newValue {
234 // 如果没有临近项,则尝试从过滤结果中获取
235 newValue = ret[groupKey.ID]
236 }
237
238 if nil != newValue {
239 if !av.MSelectExistOption(newValue.MSelect, groupView.GetGroupValue()) {
240 if 1 > len(newValue.MSelect) || av.KeyTypeMSelect == groupKey.Type {
241 newValue.MSelect = append(newValue.MSelect, &av.ValueSelect{Content: opt.Name, Color: opt.Color})
242 }
243 }
244 } else {
245 newValue = av.GetAttributeViewDefaultValue(ast.NewNodeID(), groupKey.ID, addingItemID, groupKey.Type, false)
246 newValue.MSelect = append(newValue.MSelect, &av.ValueSelect{Content: opt.Name, Color: opt.Color})
247 }
248 }
249
250 if nil != newValue {
251 ret[groupKey.ID] = newValue
252 }
253 return
254 }
255
256 if av.KeyTypeTemplate == keyValues.Key.Type && nil != nearItem {
257 if keys := templateRelevantKeys[keyValues.Key.ID]; 0 < len(keys) {
258 for _, k := range keys {
259 if nil == ret[k.ID] {
260 ret[k.ID] = getNewValueByNearItem(nearItem, k, addingItemID)
261 }
262 }
263 }
264 return
265 }
266
267 if av.KeyTypeRollup == keyValues.Key.Type && nil != nearItem {
268 if relKey, ok := rollupRelevantKeys[keyValues.Key.ID]; ok {
269 if nil == ret[relKey.ID] {
270 ret[relKey.ID] = getNewValueByNearItem(nearItem, relKey, addingItemID)
271 }
272 }
273 return
274 }
275
276 if nil != nearItem && filterKeyIDs[groupKey.ID] {
277 // 临近项不为空并且分组字段和过滤字段相同时,优先使用临近项 https://github.com/siyuan-note/siyuan/issues/15591
278 newValue = getNewValueByNearItem(nearItem, groupKey, addingItemID)
279 ret[groupKey.ID] = newValue
280
281 if nil != keyValues.Key.Date && keyValues.Key.Date.AutoFillNow {
282 newValue.Date.Content = time.Now().UnixMilli()
283 newValue.Date.IsNotEmpty = true
284 }
285 return
286 }
287
288 if nil == nearItem && !filterKeyIDs[groupKey.ID] {
289 // 没有临近项并且分组字段和过滤字段不同时,使用分组值
290 newValue = av.GetAttributeViewDefaultValue(ast.NewNodeID(), groupKey.ID, addingItemID, groupKey.Type, false)
291 if av.KeyTypeText == groupView.GroupVal.Type {
292 content := groupView.GroupVal.Text.Content
293 switch newValue.Type {
294 case av.KeyTypeBlock:
295 newValue.Block.Content = content
296 case av.KeyTypeText:
297 newValue.Text.Content = content
298 case av.KeyTypeNumber:
299 num, _ := strconv.ParseFloat(strings.Split(content, " - ")[0], 64)
300 newValue.Number.Content = num
301 newValue.Number.IsNotEmpty = true
302 case av.KeyTypeURL:
303 newValue.URL.Content = content
304 case av.KeyTypeEmail:
305 newValue.Email.Content = content
306 case av.KeyTypePhone:
307 newValue.Phone.Content = content
308 }
309 } else if av.KeyTypeCheckbox == groupView.GroupVal.Type {
310 newValue.Checkbox.Checked = groupView.GroupVal.Checkbox.Checked
311 }
312
313 ret[groupKey.ID] = newValue
314 return
315 }
316
317 if nil != newValue && !filterKeyIDs[groupKey.ID] {
318 ret[groupKey.ID] = newValue
319
320 if nil != keyValues.Key.Date && keyValues.Key.Date.AutoFillNow {
321 newValue.Date.Content = time.Now().UnixMilli()
322 newValue.Date.IsNotEmpty = true
323 }
324 }
325 return
326}
327
328func (tx *Transaction) doSortAttrViewGroup(operation *Operation) (ret *TxErr) {
329 if err := sortAttributeViewGroup(operation.AvID, operation.BlockID, operation.PreviousID, operation.ID); nil != err {
330 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
331 }
332 return
333}
334
335func sortAttributeViewGroup(avID, blockID, previousGroupID, groupID string) (err error) {
336 attrView, err := av.ParseAttributeView(avID)
337 if err != nil {
338 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
339 return
340 }
341
342 view, err := getAttrViewViewByBlockID(attrView, blockID)
343 if err != nil {
344 return err
345 }
346
347 sortGroupViews(attrView, view)
348
349 var groupView *av.View
350 var index, previousIndex int
351 for i, g := range view.Groups {
352 if g.ID == groupID {
353 groupView = g
354 index = i
355 break
356 }
357 }
358 if nil == groupView {
359 return
360 }
361 view.Group.Order = av.GroupOrderMan
362
363 view.Groups = append(view.Groups[:index], view.Groups[index+1:]...)
364 for i, g := range view.Groups {
365 if g.ID == previousGroupID {
366 previousIndex = i + 1
367 break
368 }
369 }
370 view.Groups = util.InsertElem(view.Groups, previousIndex, groupView)
371
372 for i, g := range view.Groups {
373 g.GroupSort = i
374 }
375
376 err = av.SaveAttributeView(attrView)
377 return
378}
379
380func (tx *Transaction) doRemoveAttrViewGroup(operation *Operation) (ret *TxErr) {
381 if err := removeAttributeViewGroup(operation.AvID, operation.BlockID); nil != err {
382 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
383 }
384 return
385}
386
387func removeAttributeViewGroup(avID, blockID string) (err error) {
388 attrView, err := av.ParseAttributeView(avID)
389 if err != nil {
390 return err
391 }
392
393 view, err := getAttrViewViewByBlockID(attrView, blockID)
394 if err != nil {
395 return err
396 }
397
398 removeAttributeViewGroup0(view)
399 err = av.SaveAttributeView(attrView)
400 if err != nil {
401 logging.LogErrorf("save attribute view [%s] failed: %s", avID, err)
402 return err
403 }
404 return nil
405}
406
407func removeAttributeViewGroup0(view *av.View) {
408 view.Group, view.Groups, view.GroupCreated = nil, nil, 0
409}
410
411func (tx *Transaction) doSyncAttrViewTableColWidth(operation *Operation) (ret *TxErr) {
412 err := syncAttrViewTableColWidth(operation)
413 if err != nil {
414 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
415 }
416 return
417}
418
419func syncAttrViewTableColWidth(operation *Operation) (err error) {
420 attrView, err := av.ParseAttributeView(operation.AvID)
421 if err != nil {
422 return
423 }
424
425 view := attrView.GetView(operation.ID)
426 if nil == view {
427 err = av.ErrViewNotFound
428 logging.LogErrorf("view [%s] not found in attribute view [%s]", operation.ID, operation.AvID)
429 return
430 }
431
432 var width string
433 switch view.LayoutType {
434 case av.LayoutTypeTable:
435 for _, column := range view.Table.Columns {
436 if column.ID == operation.KeyID {
437 width = column.Width
438 break
439 }
440 }
441 case av.LayoutTypeGallery, av.LayoutTypeKanban:
442 return
443 }
444
445 for _, v := range attrView.Views {
446 if av.LayoutTypeTable == v.LayoutType {
447 for _, column := range v.Table.Columns {
448 if column.ID == operation.KeyID {
449 column.Width = width
450 break
451 }
452 }
453 }
454 }
455
456 err = av.SaveAttributeView(attrView)
457 return
458}
459
460func (tx *Transaction) doHideAttrViewGroup(operation *Operation) (ret *TxErr) {
461 if err := hideAttributeViewGroup(operation.AvID, operation.BlockID, operation.ID, int(operation.Data.(float64))); nil != err {
462 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
463 }
464 return
465}
466
467func hideAttributeViewGroup(avID, blockID, groupID string, hidden int) (err error) {
468 attrView, err := av.ParseAttributeView(avID)
469 if err != nil {
470 return
471 }
472
473 view, err := getAttrViewViewByBlockID(attrView, blockID)
474 if err != nil {
475 return
476 }
477
478 for _, group := range view.Groups {
479 if group.ID == groupID {
480 group.GroupHidden = hidden
481 break
482 }
483 }
484
485 err = av.SaveAttributeView(attrView)
486 if err != nil {
487 logging.LogErrorf("save attribute view [%s] failed: %s", avID, err)
488 return
489 }
490 return
491}
492
493func (tx *Transaction) doHideAttrViewAllGroups(operation *Operation) (ret *TxErr) {
494 if err := hideAttributeViewAllGroups(operation.AvID, operation.BlockID, operation.Data.(bool)); nil != err {
495 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
496 }
497 return
498}
499
500func hideAttributeViewAllGroups(avID, blockID string, hidden bool) (err error) {
501 attrView, err := av.ParseAttributeView(avID)
502 if err != nil {
503 return
504 }
505
506 view, err := getAttrViewViewByBlockID(attrView, blockID)
507 if err != nil {
508 return
509 }
510
511 for _, group := range view.Groups {
512 if hidden {
513 group.GroupHidden = 2
514 } else {
515 group.GroupHidden = 0
516 }
517 }
518
519 err = av.SaveAttributeView(attrView)
520 if err != nil {
521 logging.LogErrorf("save attribute view [%s] failed: %s", avID, err)
522 return
523 }
524 return
525}
526
527func (tx *Transaction) doFoldAttrViewGroup(operation *Operation) (ret *TxErr) {
528 if err := foldAttrViewGroup(operation.AvID, operation.BlockID, operation.ID, operation.Data.(bool)); nil != err {
529 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
530 }
531 return
532}
533
534func foldAttrViewGroup(avID, blockID, groupID string, folded bool) (err error) {
535 attrView, err := av.ParseAttributeView(avID)
536 if err != nil {
537 return err
538 }
539
540 view, err := getAttrViewViewByBlockID(attrView, blockID)
541 if err != nil {
542 return err
543 }
544
545 if !view.IsGroupView() {
546 return
547 }
548
549 for _, group := range view.Groups {
550 if group.ID == groupID {
551 group.GroupFolded = folded
552 break
553 }
554 }
555
556 err = av.SaveAttributeView(attrView)
557 if err != nil {
558 logging.LogErrorf("save attribute view [%s] failed: %s", avID, err)
559 return err
560 }
561 return nil
562}
563
564func (tx *Transaction) doSetAttrViewGroup(operation *Operation) (ret *TxErr) {
565 data, err := gulu.JSON.MarshalJSON(operation.Data)
566 if nil != err {
567 logging.LogErrorf("marshal operation data failed: %s", err)
568 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
569 }
570
571 group := &av.ViewGroup{}
572 if err = gulu.JSON.UnmarshalJSON(data, &group); nil != err {
573 logging.LogErrorf("unmarshal operation data failed: %s", err)
574 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
575 }
576
577 if err = SetAttributeViewGroup(operation.AvID, operation.BlockID, group); nil != err {
578 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
579 }
580 return
581}
582
583func SetAttributeViewGroup(avID, blockID string, group *av.ViewGroup) (err error) {
584 attrView, err := av.ParseAttributeView(avID)
585 if err != nil {
586 return err
587 }
588
589 view, err := getAttrViewViewByBlockID(attrView, blockID)
590 if err != nil {
591 return err
592 }
593
594 setAttributeViewGroup(attrView, view, group)
595
596 err = av.SaveAttributeView(attrView)
597 ReloadAttrView(avID)
598 return
599}
600
601func setAttributeViewGroup(attrView *av.AttributeView, view *av.View, group *av.ViewGroup) {
602 var oldHideEmpty, firstInit, changeGroupField bool
603 if nil != view.Group {
604 oldHideEmpty = view.Group.HideEmpty
605 changeGroupField = group.Field != view.Group.Field
606 } else {
607 firstInit = true
608 }
609
610 groupStates := getAttrViewGroupStates(view)
611 view.Group = group
612 regenAttrViewGroups(attrView)
613 setAttrViewGroupStates(view, groupStates)
614
615 if view.Group.HideEmpty != oldHideEmpty {
616 if !oldHideEmpty && view.Group.HideEmpty { // 启用隐藏空分组
617 for _, g := range view.Groups {
618 groupViewable := sql.RenderGroupView(attrView, view, g, "")
619 // 必须经过渲染才能得到最终的条目数
620 renderViewableInstance(groupViewable, view, attrView, 1, -1)
621 if g.GroupHidden == 0 && 1 > groupViewable.(av.Collection).CountItems() {
622 g.GroupHidden = 1
623 }
624 }
625 }
626 if oldHideEmpty && !view.Group.HideEmpty { // 禁用隐藏空分组
627 for _, g := range view.Groups {
628 groupViewable := sql.RenderGroupView(attrView, view, g, "")
629 renderViewableInstance(groupViewable, view, attrView, 1, -1)
630 if g.GroupHidden == 1 && 1 > groupViewable.(av.Collection).CountItems() {
631 g.GroupHidden = 0
632 }
633 }
634 }
635 }
636
637 if firstInit || changeGroupField { // 首次设置分组时
638 if groupKey := view.GetGroupKey(attrView); nil != groupKey {
639 if av.KeyTypeSelect == groupKey.Type || av.KeyTypeMSelect == groupKey.Type {
640 // 如果分组字段是单选或多选,则将分组排序方式改为按选项排序 https://github.com/siyuan-note/siyuan/issues/15534
641 view.Group.Order = av.GroupOrderSelectOption
642 sortGroupsBySelectOption(view, groupKey)
643 } else if av.KeyTypeCheckbox == groupKey.Type {
644 // 如果分组字段是复选框,则将分组排序改为手动排序,并且已勾选在前面
645 view.Group.Order = av.GroupOrderMan
646 checked := view.GetGroupByGroupValue(av.CheckboxCheckedStr)
647 unchecked := view.GetGroupByGroupValue("")
648 view.Groups = nil
649 view.Groups = append(view.Groups, checked, unchecked)
650 }
651
652 }
653
654 for i, g := range view.Groups {
655 g.GroupSort = i
656 }
657 }
658}
659
660func (tx *Transaction) doSetAttrViewCardAspectRatio(operation *Operation) (ret *TxErr) {
661 err := setAttrViewCardAspectRatio(operation)
662 if err != nil {
663 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
664 }
665 return
666}
667
668func setAttrViewCardAspectRatio(operation *Operation) (err error) {
669 attrView, err := av.ParseAttributeView(operation.AvID)
670 if err != nil {
671 return
672 }
673
674 view, err := getAttrViewViewByBlockID(attrView, operation.BlockID)
675 if err != nil {
676 return
677 }
678
679 switch view.LayoutType {
680 case av.LayoutTypeTable:
681 return
682 case av.LayoutTypeGallery:
683 view.Gallery.CardAspectRatio = av.CardAspectRatio(operation.Data.(float64))
684 case av.LayoutTypeKanban:
685 view.Kanban.CardAspectRatio = av.CardAspectRatio(operation.Data.(float64))
686 }
687
688 err = av.SaveAttributeView(attrView)
689 return
690}
691
692func (tx *Transaction) doSetAttrViewBlockView(operation *Operation) (ret *TxErr) {
693 err := SetDatabaseBlockView(operation.BlockID, operation.AvID, operation.ID)
694 if err != nil {
695 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
696 }
697 return
698}
699
700func (tx *Transaction) doChangeAttrViewLayout(operation *Operation) (ret *TxErr) {
701 err := ChangeAttrViewLayout(operation.BlockID, operation.AvID, operation.Layout)
702 if err != nil {
703 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
704 }
705 return
706}
707
708func ChangeAttrViewLayout(blockID, avID string, layout av.LayoutType) (err error) {
709 attrView, err := av.ParseAttributeView(avID)
710 if err != nil {
711 return
712 }
713
714 view, err := getAttrViewViewByBlockID(attrView, blockID)
715 if err != nil {
716 return
717 }
718
719 newLayout := layout
720 if newLayout == view.LayoutType {
721 return
722 }
723
724 switch newLayout {
725 case av.LayoutTypeTable:
726 if view.Name == av.GetAttributeViewI18n("gallery") || view.Name == av.GetAttributeViewI18n("kanban") {
727 view.Name = av.GetAttributeViewI18n("table")
728 }
729
730 if nil != view.Table {
731 break
732 }
733
734 view.Table = av.NewLayoutTable()
735 switch view.LayoutType {
736 case av.LayoutTypeGallery:
737 for _, field := range view.Gallery.CardFields {
738 view.Table.Columns = append(view.Table.Columns, &av.ViewTableColumn{BaseField: &av.BaseField{ID: field.ID}})
739 }
740 case av.LayoutTypeKanban:
741 for _, field := range view.Kanban.Fields {
742 view.Table.Columns = append(view.Table.Columns, &av.ViewTableColumn{BaseField: &av.BaseField{ID: field.ID}})
743 }
744 }
745 case av.LayoutTypeGallery:
746 if view.Name == av.GetAttributeViewI18n("table") || view.Name == av.GetAttributeViewI18n("kanban") {
747 view.Name = av.GetAttributeViewI18n("gallery")
748 }
749
750 if nil != view.Gallery {
751 break
752 }
753
754 view.Gallery = av.NewLayoutGallery()
755 switch view.LayoutType {
756 case av.LayoutTypeTable:
757 for _, col := range view.Table.Columns {
758 view.Gallery.CardFields = append(view.Gallery.CardFields, &av.ViewGalleryCardField{BaseField: &av.BaseField{ID: col.ID}})
759 }
760 case av.LayoutTypeKanban:
761 for _, field := range view.Kanban.Fields {
762 view.Gallery.CardFields = append(view.Gallery.CardFields, &av.ViewGalleryCardField{BaseField: &av.BaseField{ID: field.ID}})
763 }
764 }
765 case av.LayoutTypeKanban:
766 if view.Name == av.GetAttributeViewI18n("table") || view.Name == av.GetAttributeViewI18n("gallery") {
767 view.Name = av.GetAttributeViewI18n("kanban")
768 }
769
770 if nil != view.Kanban {
771 break
772 }
773
774 view.Kanban = av.NewLayoutKanban()
775 switch view.LayoutType {
776 case av.LayoutTypeTable:
777 for _, col := range view.Table.Columns {
778 view.Kanban.Fields = append(view.Kanban.Fields, &av.ViewKanbanField{BaseField: &av.BaseField{ID: col.ID}})
779 }
780 case av.LayoutTypeGallery:
781 for _, field := range view.Gallery.CardFields {
782 view.Kanban.Fields = append(view.Kanban.Fields, &av.ViewKanbanField{BaseField: &av.BaseField{ID: field.ID}})
783 }
784 }
785
786 preferredGroupKey := getKanbanPreferredGroupKey(attrView)
787 group := &av.ViewGroup{Field: preferredGroupKey.ID}
788 setAttributeViewGroup(attrView, view, group)
789 }
790
791 view.LayoutType = newLayout
792
793 blockIDs := treenode.GetMirrorAttrViewBlockIDs(avID)
794 for _, bID := range blockIDs {
795 node, tree, _ := getNodeByBlockID(nil, bID)
796 if nil == node || nil == tree {
797 logging.LogErrorf("get node by block ID [%s] failed", bID)
798 continue
799 }
800
801 changed := false
802 attrs := parse.IAL2Map(node.KramdownIAL)
803 if blockID == bID { // 当前操作的镜像库
804 attrs[av.NodeAttrView] = view.ID
805 node.AttributeViewType = string(view.LayoutType)
806 attrView.ViewID = view.ID
807 changed = true
808 } else {
809 if view.ID == attrs[av.NodeAttrView] {
810 // 仅更新和当前操作的镜像库指定的视图相同的镜像库
811 node.AttributeViewType = string(view.LayoutType)
812 changed = true
813 }
814 }
815
816 if changed {
817 err = setNodeAttrs(node, tree, attrs)
818 if err != nil {
819 logging.LogWarnf("set node [%s] attrs failed: %s", bID, err)
820 return
821 }
822 }
823 }
824
825 regenAttrViewGroups(attrView)
826
827 if err = av.SaveAttributeView(attrView); nil != err {
828 logging.LogErrorf("save attribute view [%s] failed: %s", avID, err)
829 return
830 }
831
832 ReloadAttrView(avID)
833 return
834}
835
836func (tx *Transaction) doSetAttrViewWrapField(operation *Operation) (ret *TxErr) {
837 err := setAttrViewWrapField(operation)
838 if err != nil {
839 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
840 }
841 return
842}
843
844func setAttrViewWrapField(operation *Operation) (err error) {
845 attrView, err := av.ParseAttributeView(operation.AvID)
846 if err != nil {
847 return
848 }
849
850 view, err := getAttrViewViewByBlockID(attrView, operation.BlockID)
851 if err != nil {
852 return
853 }
854
855 allFieldWrap := operation.Data.(bool)
856 switch view.LayoutType {
857 case av.LayoutTypeTable:
858 view.Table.WrapField = allFieldWrap
859 for _, col := range view.Table.Columns {
860 col.Wrap = allFieldWrap
861 }
862 case av.LayoutTypeGallery:
863 view.Gallery.WrapField = allFieldWrap
864 for _, field := range view.Gallery.CardFields {
865 field.Wrap = allFieldWrap
866 }
867 case av.LayoutTypeKanban:
868 view.Kanban.WrapField = allFieldWrap
869 for _, field := range view.Kanban.Fields {
870 field.Wrap = allFieldWrap
871 }
872 }
873
874 err = av.SaveAttributeView(attrView)
875 return
876}
877
878func (tx *Transaction) doSetAttrViewShowIcon(operation *Operation) (ret *TxErr) {
879 err := setAttrViewShowIcon(operation)
880 if err != nil {
881 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
882 }
883 return
884}
885
886func setAttrViewShowIcon(operation *Operation) (err error) {
887 attrView, err := av.ParseAttributeView(operation.AvID)
888 if err != nil {
889 return
890 }
891
892 view, err := getAttrViewViewByBlockID(attrView, operation.BlockID)
893 if err != nil {
894 return
895 }
896
897 switch view.LayoutType {
898 case av.LayoutTypeTable:
899 view.Table.ShowIcon = operation.Data.(bool)
900 case av.LayoutTypeGallery:
901 view.Gallery.ShowIcon = operation.Data.(bool)
902 case av.LayoutTypeKanban:
903 view.Kanban.ShowIcon = operation.Data.(bool)
904 }
905
906 err = av.SaveAttributeView(attrView)
907 return
908}
909
910func (tx *Transaction) doSetAttrViewFitImage(operation *Operation) (ret *TxErr) {
911 err := setAttrViewFitImage(operation)
912 if err != nil {
913 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
914 }
915 return
916}
917
918func setAttrViewFitImage(operation *Operation) (err error) {
919 attrView, err := av.ParseAttributeView(operation.AvID)
920 if err != nil {
921 return
922 }
923
924 view, err := getAttrViewViewByBlockID(attrView, operation.BlockID)
925 if err != nil {
926 return
927 }
928
929 switch view.LayoutType {
930 case av.LayoutTypeTable:
931 return
932 case av.LayoutTypeGallery:
933 view.Gallery.FitImage = operation.Data.(bool)
934 case av.LayoutTypeKanban:
935 view.Kanban.FitImage = operation.Data.(bool)
936 }
937
938 err = av.SaveAttributeView(attrView)
939 return
940}
941
942func (tx *Transaction) doSetAttrViewDisplayFieldName(operation *Operation) (ret *TxErr) {
943 err := setAttrViewDisplayFieldName(operation)
944 if err != nil {
945 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
946 }
947 return
948}
949
950func setAttrViewDisplayFieldName(operation *Operation) (err error) {
951 attrView, err := av.ParseAttributeView(operation.AvID)
952 if err != nil {
953 return
954 }
955
956 view, err := getAttrViewViewByBlockID(attrView, operation.BlockID)
957 if err != nil {
958 return
959 }
960
961 switch view.LayoutType {
962 case av.LayoutTypeTable:
963 return
964 case av.LayoutTypeGallery:
965 view.Gallery.DisplayFieldName = operation.Data.(bool)
966 case av.LayoutTypeKanban:
967 view.Kanban.DisplayFieldName = operation.Data.(bool)
968 }
969
970 err = av.SaveAttributeView(attrView)
971 return
972}
973
974func (tx *Transaction) doSetAttrViewCardSize(operation *Operation) (ret *TxErr) {
975 err := setAttrViewCardSize(operation)
976 if err != nil {
977 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
978 }
979 return
980}
981
982func setAttrViewCardSize(operation *Operation) (err error) {
983 attrView, err := av.ParseAttributeView(operation.AvID)
984 if err != nil {
985 return
986 }
987
988 view, err := getAttrViewViewByBlockID(attrView, operation.BlockID)
989 if err != nil {
990 return
991 }
992
993 switch view.LayoutType {
994 case av.LayoutTypeTable:
995 return
996 case av.LayoutTypeGallery:
997 view.Gallery.CardSize = av.CardSize(operation.Data.(float64))
998 case av.LayoutTypeKanban:
999 view.Kanban.CardSize = av.CardSize(operation.Data.(float64))
1000 }
1001
1002 err = av.SaveAttributeView(attrView)
1003 return
1004}
1005
1006func (tx *Transaction) doSetAttrViewCoverFromAssetKeyID(operation *Operation) (ret *TxErr) {
1007 err := setAttrViewCoverFromAssetKeyID(operation)
1008 if err != nil {
1009 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
1010 }
1011 return
1012}
1013
1014func setAttrViewCoverFromAssetKeyID(operation *Operation) (err error) {
1015 attrView, err := av.ParseAttributeView(operation.AvID)
1016 if err != nil {
1017 return
1018 }
1019
1020 view, err := getAttrViewViewByBlockID(attrView, operation.BlockID)
1021 if err != nil {
1022 return
1023 }
1024
1025 switch view.LayoutType {
1026 case av.LayoutTypeTable:
1027 return
1028 case av.LayoutTypeGallery:
1029 view.Gallery.CoverFromAssetKeyID = operation.KeyID
1030 case av.LayoutTypeKanban:
1031 view.Kanban.CoverFromAssetKeyID = operation.KeyID
1032 }
1033
1034 err = av.SaveAttributeView(attrView)
1035 return
1036}
1037
1038func (tx *Transaction) doSetAttrViewCoverFrom(operation *Operation) (ret *TxErr) {
1039 err := setAttrViewCoverFrom(operation)
1040 if err != nil {
1041 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
1042 }
1043 return
1044}
1045
1046func setAttrViewCoverFrom(operation *Operation) (err error) {
1047 attrView, err := av.ParseAttributeView(operation.AvID)
1048 if err != nil {
1049 return
1050 }
1051
1052 view, err := getAttrViewViewByBlockID(attrView, operation.BlockID)
1053 if err != nil {
1054 return
1055 }
1056
1057 switch view.LayoutType {
1058 case av.LayoutTypeTable:
1059 return
1060 case av.LayoutTypeGallery:
1061 view.Gallery.CoverFrom = av.CoverFrom(operation.Data.(float64))
1062 case av.LayoutTypeKanban:
1063 view.Kanban.CoverFrom = av.CoverFrom(operation.Data.(float64))
1064 }
1065
1066 err = av.SaveAttributeView(attrView)
1067 return
1068}
1069
1070func AppendAttributeViewDetachedBlocksWithValues(avID string, blocksValues [][]*av.Value) (err error) {
1071 attrView, err := av.ParseAttributeView(avID)
1072 if err != nil {
1073 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
1074 return
1075 }
1076
1077 now := util.CurrentTimeMillis()
1078 var blockIDs []string
1079 for _, blockValues := range blocksValues {
1080 blockID := ast.NewNodeID()
1081 blockIDs = append(blockIDs, blockID)
1082 for _, v := range blockValues {
1083 keyValues, _ := attrView.GetKeyValues(v.KeyID)
1084 if nil == keyValues {
1085 err = fmt.Errorf("key [%s] not found", v.KeyID)
1086 return
1087 }
1088
1089 v.ID = ast.NewNodeID()
1090 v.BlockID = blockID
1091 v.Type = keyValues.Key.Type
1092 if av.KeyTypeBlock == v.Type {
1093 v.Block.Created = now
1094 v.Block.Updated = now
1095 v.Block.ID = ""
1096 }
1097 v.IsDetached = true
1098 v.CreatedAt = now
1099 v.UpdatedAt = now
1100 v.IsRenderAutoFill = false
1101 keyValues.Values = append(keyValues.Values, v)
1102
1103 if av.KeyTypeSelect == v.Type || av.KeyTypeMSelect == v.Type {
1104 // 保存选项 https://github.com/siyuan-note/siyuan/issues/12475
1105 key, _ := attrView.GetKey(v.KeyID)
1106 if nil != key && 0 < len(v.MSelect) {
1107 for _, valOpt := range v.MSelect {
1108 if opt := key.GetOption(valOpt.Content); nil == opt {
1109 // 不存在的选项新建保存
1110 opt = &av.SelectOption{Name: valOpt.Content, Color: valOpt.Color}
1111 key.Options = append(key.Options, opt)
1112 } else {
1113 // 已经存在的选项颜色需要保持不变
1114 valOpt.Color = opt.Color
1115 }
1116 }
1117 }
1118 }
1119 }
1120 }
1121
1122 for _, v := range attrView.Views {
1123 for _, addingBlockID := range blockIDs {
1124 v.ItemIDs = append(v.ItemIDs, addingBlockID)
1125 }
1126 }
1127
1128 regenAttrViewGroups(attrView)
1129 if err = av.SaveAttributeView(attrView); err != nil {
1130 logging.LogErrorf("save attribute view [%s] failed: %s", avID, err)
1131 return
1132 }
1133
1134 ReloadAttrView(avID)
1135 return
1136}
1137
1138func DuplicateDatabaseBlock(avID string) (newAvID, newBlockID string, err error) {
1139 storageAvDir := filepath.Join(util.DataDir, "storage", "av")
1140 oldAvPath := filepath.Join(storageAvDir, avID+".json")
1141 newAvID, newBlockID = ast.NewNodeID(), ast.NewNodeID()
1142
1143 oldAv, err := av.ParseAttributeView(avID)
1144 if err != nil {
1145 return
1146 }
1147
1148 data, err := filelock.ReadFile(oldAvPath)
1149 if err != nil {
1150 logging.LogErrorf("read attribute view [%s] failed: %s", avID, err)
1151 return
1152 }
1153
1154 data = bytes.ReplaceAll(data, []byte(avID), []byte(newAvID))
1155 av.UpsertBlockRel(newAvID, newBlockID)
1156
1157 newAv := &av.AttributeView{}
1158 if err = gulu.JSON.UnmarshalJSON(data, newAv); err != nil {
1159 logging.LogErrorf("unmarshal attribute view [%s] failed: %s", newAvID, err)
1160 return
1161 }
1162
1163 if "" != newAv.Name {
1164 newAv.Name = oldAv.Name + " (Duplicated " + time.Now().Format("2006-01-02 15:04:05") + ")"
1165 }
1166
1167 for _, keyValues := range newAv.KeyValues {
1168 if nil != keyValues.Key.Relation && keyValues.Key.Relation.IsTwoWay {
1169 // 断开双向关联
1170 keyValues.Key.Relation.IsTwoWay = false
1171 keyValues.Key.Relation.BackKeyID = ""
1172 }
1173 }
1174
1175 data, err = gulu.JSON.MarshalJSON(newAv)
1176 if err != nil {
1177 logging.LogErrorf("marshal attribute view [%s] failed: %s", newAvID, err)
1178 return
1179 }
1180
1181 newAvPath := filepath.Join(storageAvDir, newAvID+".json")
1182 if err = filelock.WriteFile(newAvPath, data); err != nil {
1183 logging.LogErrorf("write attribute view [%s] failed: %s", newAvID, err)
1184 return
1185 }
1186
1187 updateBoundBlockAvsAttribute([]string{newAvID})
1188 return
1189}
1190
1191func GetAttributeViewKeysByID(avID string, keyIDs ...string) (ret []*av.Key) {
1192 ret = []*av.Key{}
1193
1194 attrView, err := av.ParseAttributeView(avID)
1195 if err != nil {
1196 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
1197 return
1198 }
1199
1200 if 1 > len(keyIDs) {
1201 for _, keyValues := range attrView.KeyValues {
1202 key := keyValues.Key
1203 ret = append(ret, key)
1204 }
1205 return
1206 }
1207
1208 for _, keyValues := range attrView.KeyValues {
1209 key := keyValues.Key
1210 for _, keyID := range keyIDs {
1211 if key.ID == keyID {
1212 ret = append(ret, key)
1213 }
1214 }
1215 }
1216 return ret
1217}
1218
1219func SetDatabaseBlockView(blockID, avID, viewID string) (err error) {
1220 attrView, err := av.ParseAttributeView(avID)
1221 if nil != err {
1222 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
1223 return
1224 }
1225 if attrView.ViewID != viewID {
1226 attrView.ViewID = viewID
1227 if err = av.SaveAttributeView(attrView); err != nil {
1228 return
1229 }
1230 }
1231
1232 view := attrView.GetView(viewID)
1233 if nil == view {
1234 err = av.ErrViewNotFound
1235 logging.LogErrorf("view [%s] not found in attribute view [%s]", viewID, avID)
1236 return
1237 }
1238
1239 node, tree, err := getNodeByBlockID(nil, blockID)
1240 if err != nil {
1241 return
1242 }
1243
1244 node.AttributeViewType = string(view.LayoutType)
1245 attrs := parse.IAL2Map(node.KramdownIAL)
1246 attrs[av.NodeAttrView] = viewID
1247 err = setNodeAttrs(node, tree, attrs)
1248 if err != nil {
1249 logging.LogWarnf("set node [%s] attrs failed: %s", blockID, err)
1250 return
1251 }
1252 return
1253}
1254
1255func GetAttributeViewPrimaryKeyValues(avID, keyword string, page, pageSize int) (attributeViewName string, databaseBlockIDs []string, keyValues *av.KeyValues, err error) {
1256 waitForSyncingStorages()
1257
1258 attrView, err := av.ParseAttributeView(avID)
1259 if err != nil {
1260 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
1261 return
1262 }
1263 attributeViewName = getAttrViewName(attrView)
1264
1265 databaseBlockIDs = treenode.GetMirrorAttrViewBlockIDs(avID)
1266
1267 keyValues = attrView.GetBlockKeyValues()
1268 var values []*av.Value
1269 for _, kv := range keyValues.Values {
1270 if !kv.IsDetached && !treenode.ExistBlockTree(kv.Block.ID) {
1271 continue
1272 }
1273
1274 if strings.Contains(strings.ToLower(kv.String(true)), strings.ToLower(keyword)) {
1275 values = append(values, kv)
1276 }
1277 }
1278 keyValues.Values = values
1279
1280 if 1 > pageSize {
1281 pageSize = 16
1282 }
1283 start := (page - 1) * pageSize
1284 end := start + pageSize
1285 if len(keyValues.Values) < end {
1286 end = len(keyValues.Values)
1287 }
1288 keyValues.Values = keyValues.Values[start:end]
1289
1290 sort.Slice(keyValues.Values, func(i, j int) bool {
1291 return keyValues.Values[i].Block.Updated > keyValues.Values[j].Block.Updated
1292 })
1293 return
1294}
1295
1296func GetAttributeViewFilterSort(avID, blockID string) (filters []*av.ViewFilter, sorts []*av.ViewSort) {
1297 waitForSyncingStorages()
1298
1299 attrView, err := av.ParseAttributeView(avID)
1300 if err != nil {
1301 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
1302 return
1303 }
1304
1305 view, err := getAttrViewViewByBlockID(attrView, blockID)
1306 if nil == view {
1307 view, err = attrView.GetCurrentView(attrView.ViewID)
1308 if nil == view || err != nil {
1309 logging.LogErrorf("get current view failed: %s", err)
1310 return
1311 }
1312 }
1313
1314 filters = view.Filters
1315 sorts = view.Sorts
1316 if 1 > len(filters) {
1317 filters = []*av.ViewFilter{}
1318 }
1319 if 1 > len(sorts) {
1320 sorts = []*av.ViewSort{}
1321 }
1322 return
1323}
1324
1325func SearchAttributeViewNonRelationKey(avID, keyword string) (ret []*av.Key) {
1326 waitForSyncingStorages()
1327
1328 ret = []*av.Key{}
1329 attrView, err := av.ParseAttributeView(avID)
1330 if err != nil {
1331 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
1332 return
1333 }
1334
1335 for _, keyValues := range attrView.KeyValues {
1336 if av.KeyTypeRelation != keyValues.Key.Type && av.KeyTypeRollup != keyValues.Key.Type && av.KeyTypeLineNumber != keyValues.Key.Type {
1337 if strings.Contains(strings.ToLower(keyValues.Key.Name), strings.ToLower(keyword)) {
1338 ret = append(ret, keyValues.Key)
1339 }
1340 }
1341 }
1342 return
1343}
1344
1345func SearchAttributeViewRollupDestKeys(avID, keyword string) (ret []*av.Key) {
1346 waitForSyncingStorages()
1347
1348 ret = []*av.Key{}
1349 attrView, err := av.ParseAttributeView(avID)
1350 if err != nil {
1351 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
1352 return
1353 }
1354
1355 for _, keyValues := range attrView.KeyValues {
1356 if av.KeyTypeRollup != keyValues.Key.Type && av.KeyTypeLineNumber != keyValues.Key.Type {
1357 if strings.Contains(strings.ToLower(keyValues.Key.Name), strings.ToLower(keyword)) {
1358 ret = append(ret, keyValues.Key)
1359 }
1360 }
1361 }
1362 return
1363}
1364
1365func SearchAttributeViewRelationKey(avID, keyword string) (ret []*av.Key) {
1366 waitForSyncingStorages()
1367
1368 ret = []*av.Key{}
1369 attrView, err := av.ParseAttributeView(avID)
1370 if err != nil {
1371 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
1372 return
1373 }
1374
1375 for _, keyValues := range attrView.KeyValues {
1376 if av.KeyTypeRelation == keyValues.Key.Type && nil != keyValues.Key.Relation {
1377 if strings.Contains(strings.ToLower(keyValues.Key.Name), strings.ToLower(keyword)) {
1378 ret = append(ret, keyValues.Key)
1379 }
1380 }
1381 }
1382 return
1383}
1384
1385func GetAttributeView(avID string) (ret *av.AttributeView) {
1386 waitForSyncingStorages()
1387
1388 ret, _ = av.ParseAttributeView(avID)
1389 return
1390}
1391
1392type AvSearchResult struct {
1393 AvID string `json:"avID"`
1394 AvName string `json:"avName"`
1395 ViewName string `json:"viewName"`
1396 ViewID string `json:"viewID"`
1397 ViewLayout av.LayoutType `json:"viewLayout"`
1398 BlockID string `json:"blockID"`
1399 HPath string `json:"hPath"`
1400 Children []*AvSearchResult `json:"children,omitempty"`
1401}
1402
1403type AvSearchTempResult struct {
1404 AvID string
1405 AvName string
1406 AvUpdated int64
1407 Score float64
1408}
1409
1410func SearchAttributeView(keyword string, excludeAvIDs []string) (ret []*AvSearchResult) {
1411 waitForSyncingStorages()
1412
1413 ret = []*AvSearchResult{}
1414 keyword = strings.TrimSpace(keyword)
1415 keywords := strings.Fields(keyword)
1416
1417 var avSearchTmpResults []*AvSearchTempResult
1418 avDir := filepath.Join(util.DataDir, "storage", "av")
1419 entries, err := os.ReadDir(avDir)
1420 if err != nil {
1421 logging.LogErrorf("read directory [%s] failed: %s", avDir, err)
1422 return
1423 }
1424
1425 avBlockRels := av.GetBlockRels()
1426 if 1 > len(avBlockRels) {
1427 return
1428 }
1429
1430 for _, entry := range entries {
1431 if entry.IsDir() {
1432 continue
1433 }
1434
1435 id := strings.TrimSuffix(entry.Name(), ".json")
1436 if !ast.IsNodeIDPattern(id) {
1437 continue
1438 }
1439
1440 if gulu.Str.Contains(id, excludeAvIDs) {
1441 continue
1442 }
1443
1444 if nil == avBlockRels[id] {
1445 continue
1446 }
1447
1448 name, _ := av.GetAttributeViewNameByPath(filepath.Join(avDir, entry.Name()))
1449 info, _ := entry.Info()
1450 if "" != keyword {
1451 score := 0.0
1452 hit := false
1453 for _, k := range keywords {
1454 if strings.Contains(strings.ToLower(name), strings.ToLower(k)) {
1455 score += smetrics.JaroWinkler(name, k, 0.7, 4)
1456 hit = true
1457 } else {
1458 hit = false
1459 break
1460 }
1461 }
1462
1463 if hit {
1464 a := &AvSearchTempResult{AvID: id, AvName: name, Score: score}
1465 if nil != info && !info.ModTime().IsZero() {
1466 a.AvUpdated = info.ModTime().UnixMilli()
1467 }
1468 avSearchTmpResults = append(avSearchTmpResults, a)
1469 }
1470 } else {
1471 a := &AvSearchTempResult{AvID: id, AvName: name}
1472 if nil != info && !info.ModTime().IsZero() {
1473 a.AvUpdated = info.ModTime().UnixMilli()
1474 }
1475 avSearchTmpResults = append(avSearchTmpResults, a)
1476 }
1477 }
1478
1479 if "" == keyword {
1480 sort.Slice(avSearchTmpResults, func(i, j int) bool { return avSearchTmpResults[i].AvUpdated > avSearchTmpResults[j].AvUpdated })
1481 } else {
1482 sort.SliceStable(avSearchTmpResults, func(i, j int) bool {
1483 if avSearchTmpResults[i].Score == avSearchTmpResults[j].Score {
1484 return avSearchTmpResults[i].AvUpdated > avSearchTmpResults[j].AvUpdated
1485 }
1486 return avSearchTmpResults[i].Score > avSearchTmpResults[j].Score
1487 })
1488 }
1489 if 12 <= len(avSearchTmpResults) {
1490 avSearchTmpResults = avSearchTmpResults[:12]
1491 }
1492
1493 for _, tmpResult := range avSearchTmpResults {
1494 bIDs := avBlockRels[tmpResult.AvID]
1495 var node *ast.Node
1496 for _, bID := range bIDs {
1497 tree, _ := LoadTreeByBlockID(bID)
1498 if nil == tree {
1499 continue
1500 }
1501
1502 node = treenode.GetNodeInTree(tree, bID)
1503 if nil == node || "" == node.AttributeViewID || ast.NodeAttributeView != node.Type {
1504 node = nil
1505 continue
1506 }
1507
1508 break
1509 }
1510
1511 if nil == node {
1512 continue
1513 }
1514
1515 attrView, _ := av.ParseAttributeView(tmpResult.AvID)
1516 if nil == attrView {
1517 continue
1518 }
1519
1520 var hPath string
1521 baseBlock := treenode.GetBlockTreeRootByPath(node.Box, node.Path)
1522 if nil != baseBlock {
1523 hPath = baseBlock.HPath
1524 }
1525 box := Conf.Box(node.Box)
1526 if nil != box {
1527 hPath = box.Name + hPath
1528 }
1529
1530 name := tmpResult.AvName
1531 if "" == name {
1532 name = Conf.language(267)
1533 }
1534
1535 parent := &AvSearchResult{
1536 AvID: tmpResult.AvID,
1537 AvName: tmpResult.AvName,
1538 BlockID: node.ID,
1539 HPath: hPath,
1540 }
1541 ret = append(ret, parent)
1542
1543 for _, view := range attrView.Views {
1544 child := &AvSearchResult{
1545 AvID: tmpResult.AvID,
1546 AvName: tmpResult.AvName,
1547 ViewName: view.Name,
1548 ViewID: view.ID,
1549 ViewLayout: view.LayoutType,
1550 BlockID: node.ID,
1551 HPath: hPath,
1552 }
1553 parent.Children = append(parent.Children, child)
1554 }
1555 }
1556 return
1557}
1558
1559type BlockAttributeViewKeys struct {
1560 AvID string `json:"avID"`
1561 AvName string `json:"avName"`
1562 BlockIDs []string `json:"blockIDs"`
1563 KeyValues []*av.KeyValues `json:"keyValues"`
1564}
1565
1566func GetBlockAttributeViewKeys(nodeID string) (ret []*BlockAttributeViewKeys) {
1567 waitForSyncingStorages()
1568
1569 ret = []*BlockAttributeViewKeys{}
1570 attrs := sql.GetBlockAttrs(nodeID)
1571 avs := attrs[av.NodeAttrNameAvs]
1572 if "" == avs {
1573 return
1574 }
1575
1576 cachedAttrViews := map[string]*av.AttributeView{}
1577 avIDs := strings.Split(avs, ",")
1578 for _, avID := range avIDs {
1579 attrView := cachedAttrViews[avID]
1580 if nil == attrView {
1581 var err error
1582 attrView, err = av.ParseAttributeView(avID)
1583 if nil == attrView {
1584 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
1585 continue
1586 }
1587 cachedAttrViews[avID] = attrView
1588 }
1589
1590 if !attrView.ExistBoundBlock(nodeID) {
1591 // 比如剪切后粘贴,块 ID 会变,但是属性还在块上,这里做一次数据订正
1592 // Auto verify the database name when clicking the block superscript icon https://github.com/siyuan-note/siyuan/issues/10861
1593 unbindBlockAv(nil, avID, nodeID)
1594 return
1595 }
1596
1597 blockVal := attrView.GetBlockValueByBoundID(nodeID)
1598 if nil == blockVal {
1599 continue
1600 }
1601
1602 itemID := blockVal.BlockID
1603 var keyValues []*av.KeyValues
1604 for _, kv := range attrView.KeyValues {
1605 if av.KeyTypeLineNumber == kv.Key.Type {
1606 // 属性面板中不显示行号字段
1607 // The line number field no longer appears in the database attribute panel https://github.com/siyuan-note/siyuan/issues/11319
1608 continue
1609 }
1610
1611 kValues := &av.KeyValues{Key: kv.Key}
1612 for _, v := range kv.Values {
1613 if v.BlockID == itemID {
1614 kValues.Values = append(kValues.Values, v)
1615 }
1616 }
1617
1618 switch kValues.Key.Type {
1619 case av.KeyTypeRollup:
1620 kValues.Values = append(kValues.Values, &av.Value{ID: ast.NewNodeID(), KeyID: kValues.Key.ID, BlockID: itemID, Type: av.KeyTypeRollup, Rollup: &av.ValueRollup{Contents: []*av.Value{}}})
1621 case av.KeyTypeTemplate:
1622 kValues.Values = append(kValues.Values, &av.Value{ID: ast.NewNodeID(), KeyID: kValues.Key.ID, BlockID: itemID, Type: av.KeyTypeTemplate, Template: &av.ValueTemplate{Content: ""}})
1623 case av.KeyTypeCreated:
1624 kValues.Values = append(kValues.Values, &av.Value{ID: ast.NewNodeID(), KeyID: kValues.Key.ID, BlockID: itemID, Type: av.KeyTypeCreated})
1625 case av.KeyTypeUpdated:
1626 kValues.Values = append(kValues.Values, &av.Value{ID: ast.NewNodeID(), KeyID: kValues.Key.ID, BlockID: itemID, Type: av.KeyTypeUpdated})
1627 case av.KeyTypeNumber:
1628 for _, v := range kValues.Values {
1629 if nil != v.Number {
1630 v.Number.Format = kValues.Key.NumberFormat
1631 v.Number.FormatNumber()
1632 }
1633 }
1634 }
1635
1636 if 0 < len(kValues.Values) {
1637 for _, v := range kValues.Values {
1638 sql.FillAttributeViewNilValue(v, v.Type)
1639 }
1640 keyValues = append(keyValues, kValues)
1641 } else {
1642 // 如果没有值,那么就补一个默认值
1643 kValues.Values = append(kValues.Values, av.GetAttributeViewDefaultValue(itemID[:14]+ast.NewNodeID()[14:], kv.Key.ID, itemID, kv.Key.Type, false))
1644 keyValues = append(keyValues, kValues)
1645 }
1646 }
1647
1648 // 渲染主键、创建时间、更新时间
1649 for _, kv := range keyValues {
1650 switch kv.Key.Type {
1651 case av.KeyTypeBlock: // 对于主键可能需要填充静态锚文本 Database-bound block primary key supports setting static anchor text https://github.com/siyuan-note/siyuan/issues/10049
1652 if nil != kv.Values[0].Block {
1653 ial := sql.GetBlockAttrs(nodeID)
1654 if v := ial[av.NodeAttrViewStaticText+"-"+attrView.ID]; "" != v {
1655 kv.Values[0].Block.Content = v
1656 }
1657 }
1658 case av.KeyTypeCreated:
1659 createdStr := nodeID[:len("20060102150405")]
1660 created, parseErr := time.ParseInLocation("20060102150405", createdStr, time.Local)
1661 if nil == parseErr {
1662 kv.Values[0].Created = av.NewFormattedValueCreated(created.UnixMilli(), 0, av.CreatedFormatNone)
1663 kv.Values[0].Created.IsNotEmpty = true
1664 } else {
1665 logging.LogWarnf("parse created [%s] failed: %s", createdStr, parseErr)
1666 kv.Values[0].Created = av.NewFormattedValueCreated(time.Now().UnixMilli(), 0, av.CreatedFormatNone)
1667 }
1668 case av.KeyTypeUpdated:
1669 ial := sql.GetBlockAttrs(nodeID)
1670 updatedStr := ial["updated"]
1671 updated, parseErr := time.ParseInLocation("20060102150405", updatedStr, time.Local)
1672 if nil == parseErr {
1673 kv.Values[0].Updated = av.NewFormattedValueUpdated(updated.UnixMilli(), 0, av.UpdatedFormatNone)
1674 kv.Values[0].Updated.IsNotEmpty = true
1675 } else {
1676 logging.LogWarnf("parse updated [%s] failed: %s", updatedStr, parseErr)
1677 kv.Values[0].Updated = av.NewFormattedValueUpdated(time.Now().UnixMilli(), 0, av.UpdatedFormatNone)
1678 }
1679 }
1680 }
1681
1682 // 渲染关联和汇总
1683 rollupFurtherCollections := sql.GetFurtherCollections(attrView, cachedAttrViews)
1684 for _, kv := range keyValues {
1685 switch kv.Key.Type {
1686 case av.KeyTypeRollup:
1687 if nil == kv.Key.Rollup {
1688 break
1689 }
1690
1691 relKey, _ := attrView.GetKey(kv.Key.Rollup.RelationKeyID)
1692 if nil == relKey {
1693 break
1694 }
1695
1696 relVal := attrView.GetValue(kv.Key.Rollup.RelationKeyID, kv.Values[0].BlockID)
1697 if nil != relVal && nil != relVal.Relation {
1698 destAv := cachedAttrViews[relKey.Relation.AvID]
1699 if nil == destAv {
1700 destAv, _ = av.ParseAttributeView(relKey.Relation.AvID)
1701 if nil == destAv {
1702 break
1703 }
1704 cachedAttrViews[relKey.Relation.AvID] = destAv
1705 }
1706
1707 destKey, _ := destAv.GetKey(kv.Key.Rollup.KeyID)
1708 if nil != destKey {
1709 furtherCollection := rollupFurtherCollections[kv.Key.ID]
1710 kv.Values[0].Rollup.BuildContents(keyValues, destKey, relVal, kv.Key.Rollup.Calc, furtherCollection)
1711 }
1712 }
1713 case av.KeyTypeRelation:
1714 if nil == kv.Key.Relation {
1715 break
1716 }
1717
1718 destAv := cachedAttrViews[kv.Key.Relation.AvID]
1719 if nil == destAv {
1720 destAv, _ = av.ParseAttributeView(kv.Key.Relation.AvID)
1721 if nil == destAv {
1722 break
1723 }
1724
1725 cachedAttrViews[kv.Key.Relation.AvID] = destAv
1726 }
1727
1728 blocks := map[string]*av.Value{}
1729 for _, blockValue := range destAv.GetBlockKeyValues().Values {
1730 blocks[blockValue.BlockID] = blockValue
1731 }
1732 kv.Values[0].Relation.Contents = nil // 先清空 https://github.com/siyuan-note/siyuan/issues/10670
1733 for _, bID := range kv.Values[0].Relation.BlockIDs {
1734 kv.Values[0].Relation.Contents = append(kv.Values[0].Relation.Contents, blocks[bID])
1735 }
1736 }
1737 }
1738
1739 // 渲染模板
1740 templateKeys, _ := sql.GetTemplateKeysByResolutionOrder(attrView)
1741 var renderTemplateErr error
1742 for _, templateKey := range templateKeys {
1743 for _, kv := range keyValues {
1744 if kv.Key.ID != templateKey.ID || 1 > len(kv.Values) {
1745 continue
1746 }
1747
1748 var ial map[string]string
1749 block := av.GetKeyBlockValue(keyValues)
1750 if nil != block && !block.IsDetached {
1751 ial = sql.GetBlockAttrs(block.BlockID)
1752 }
1753 if nil == ial {
1754 ial = map[string]string{}
1755 }
1756 if nil == kv.Values[0].Template {
1757 kv.Values[0] = av.GetAttributeViewDefaultValue(kv.Values[0].ID, kv.Key.ID, nodeID, kv.Key.Type, false)
1758 }
1759
1760 var renderErr error
1761 kv.Values[0].Template.Content, renderErr = sql.RenderTemplateField(ial, keyValues, kv.Key.Template)
1762 if nil != renderErr {
1763 renderTemplateErr = fmt.Errorf("database [%s] template field [%s] rendering failed: %s", getAttrViewName(attrView), kv.Key.Name, renderErr)
1764 }
1765 }
1766 }
1767
1768 if nil != renderTemplateErr {
1769 util.PushErrMsg(fmt.Sprintf(Conf.Language(44), util.EscapeHTML(renderTemplateErr.Error())), 30000)
1770 }
1771
1772 // 字段排序
1773 refreshAttrViewKeyIDs(attrView, true)
1774 sorts := map[string]int{}
1775 for i, k := range attrView.KeyIDs {
1776 sorts[k] = i
1777 }
1778 sort.Slice(keyValues, func(i, j int) bool {
1779 return sorts[keyValues[i].Key.ID] < sorts[keyValues[j].Key.ID]
1780 })
1781
1782 blockIDs := treenode.GetMirrorAttrViewBlockIDs(avID)
1783 if 1 > len(blockIDs) {
1784 // 老数据兼容处理
1785 avBts := treenode.GetBlockTreesByType("av")
1786 for _, avBt := range avBts {
1787 if nil == avBt {
1788 continue
1789 }
1790 tree, _ := LoadTreeByBlockID(avBt.ID)
1791 if nil == tree {
1792 continue
1793 }
1794 node := treenode.GetNodeInTree(tree, avBt.ID)
1795 if nil == node {
1796 continue
1797 }
1798 if avID == node.AttributeViewID {
1799 blockIDs = append(blockIDs, avBt.ID)
1800 }
1801 }
1802 if 1 > len(blockIDs) {
1803 tree, _ := LoadTreeByBlockID(nodeID)
1804 if nil != tree {
1805 node := treenode.GetNodeInTree(tree, nodeID)
1806 if nil != node {
1807 if removeErr := removeNodeAvID(node, avID, nil, tree); nil != removeErr {
1808 logging.LogErrorf("remove node avID [%s] failed: %s", avID, removeErr)
1809 }
1810 }
1811 }
1812 continue
1813 }
1814 blockIDs = gulu.Str.RemoveDuplicatedElem(blockIDs)
1815 for _, blockID := range blockIDs {
1816 av.UpsertBlockRel(avID, blockID)
1817 }
1818 }
1819
1820 ret = append(ret, &BlockAttributeViewKeys{
1821 AvID: avID,
1822 AvName: getAttrViewName(attrView),
1823 BlockIDs: blockIDs,
1824 KeyValues: keyValues,
1825 })
1826 }
1827 return
1828}
1829
1830func genAttrViewGroups(view *av.View, attrView *av.AttributeView) {
1831 if !view.IsGroupView() {
1832 return
1833 }
1834
1835 groupStates := getAttrViewGroupStates(view)
1836
1837 group := view.Group
1838 view.Groups = nil
1839 viewable := sql.RenderView(attrView, view, "")
1840 var items []av.Item
1841 for _, item := range viewable.(av.Collection).GetItems() {
1842 items = append(items, item)
1843 }
1844
1845 groupKey := view.GetGroupKey(attrView)
1846 if nil == groupKey {
1847 return
1848 }
1849
1850 var rangeStart, rangeEnd float64
1851 switch group.Method {
1852 case av.GroupMethodValue:
1853 if av.GroupOrderMan != group.Order {
1854 sort.SliceStable(items, func(i, j int) bool {
1855 return items[i].GetValue(group.Field).String(false) < items[j].GetValue(group.Field).String(false)
1856 })
1857 }
1858 case av.GroupMethodRangeNum:
1859 if nil == group.Range {
1860 return
1861 }
1862
1863 rangeStart, rangeEnd = group.Range.NumStart, group.Range.NumStart+group.Range.NumStep
1864 sort.SliceStable(items, func(i, j int) bool {
1865 return items[i].GetValue(group.Field).Number.Content < items[j].GetValue(group.Field).Number.Content
1866 })
1867 case av.GroupMethodDateDay, av.GroupMethodDateWeek, av.GroupMethodDateMonth, av.GroupMethodDateYear, av.GroupMethodDateRelative:
1868 if av.KeyTypeCreated == groupKey.Type {
1869 sort.SliceStable(items, func(i, j int) bool {
1870 return items[i].GetValue(group.Field).Created.Content < items[j].GetValue(group.Field).Created.Content
1871 })
1872 } else if av.KeyTypeUpdated == groupKey.Type {
1873 sort.SliceStable(items, func(i, j int) bool {
1874 return items[i].GetValue(group.Field).Updated.Content < items[j].GetValue(group.Field).Updated.Content
1875 })
1876 } else if av.KeyTypeDate == groupKey.Type {
1877 sort.SliceStable(items, func(i, j int) bool {
1878 return items[i].GetValue(group.Field).Date.Content < items[j].GetValue(group.Field).Date.Content
1879 })
1880 }
1881 }
1882
1883 todayStart := time.Now()
1884 todayStart = time.Date(todayStart.Year(), todayStart.Month(), todayStart.Day(), 0, 0, 0, 0, time.Local)
1885
1886 var relationDestAv *av.AttributeView
1887 if av.KeyTypeRelation == groupKey.Type && nil != groupKey.Relation {
1888 if attrView.ID == groupKey.Relation.AvID {
1889 relationDestAv = attrView
1890 } else {
1891 relationDestAv, _ = av.ParseAttributeView(groupKey.Relation.AvID)
1892 }
1893 }
1894
1895 groupItemsMap := map[string][]av.Item{}
1896 for _, item := range items {
1897 value := item.GetValue(group.Field)
1898 if value.IsBlank() {
1899 groupItemsMap[groupValueDefault] = append(groupItemsMap[groupValueDefault], item)
1900 continue
1901 }
1902
1903 var groupVal string
1904 switch group.Method {
1905 case av.GroupMethodValue:
1906 if av.KeyTypeSelect == groupKey.Type || av.KeyTypeMSelect == groupKey.Type {
1907 for _, s := range value.MSelect {
1908 groupItemsMap[s.Content] = append(groupItemsMap[s.Content], item)
1909 }
1910 continue
1911 } else if av.KeyTypeRelation == groupKey.Type {
1912 if nil == relationDestAv {
1913 continue
1914 }
1915
1916 for _, bID := range value.Relation.BlockIDs {
1917 groupItemsMap[bID] = append(groupItemsMap[bID], item)
1918 }
1919 continue
1920 }
1921
1922 groupVal = value.String(false)
1923 case av.GroupMethodRangeNum:
1924 if group.Range.NumStart > value.Number.Content || group.Range.NumEnd < value.Number.Content {
1925 groupVal = groupValueNotInRange
1926 break
1927 }
1928
1929 for rangeEnd <= group.Range.NumEnd && rangeEnd <= value.Number.Content {
1930 rangeStart += group.Range.NumStep
1931 rangeEnd += group.Range.NumStep
1932 }
1933
1934 if rangeStart <= value.Number.Content && rangeEnd > value.Number.Content {
1935 groupVal = fmt.Sprintf("%s - %s", strconv.FormatFloat(rangeStart, 'f', -1, 64), strconv.FormatFloat(rangeEnd, 'f', -1, 64))
1936 }
1937 case av.GroupMethodDateDay, av.GroupMethodDateWeek, av.GroupMethodDateMonth, av.GroupMethodDateYear, av.GroupMethodDateRelative:
1938 var contentTime time.Time
1939 switch value.Type {
1940 case av.KeyTypeDate:
1941 contentTime = time.UnixMilli(value.Date.Content)
1942 case av.KeyTypeCreated:
1943 contentTime = time.UnixMilli(value.Created.Content)
1944 case av.KeyTypeUpdated:
1945 contentTime = time.UnixMilli(value.Updated.Content)
1946 }
1947 switch group.Method {
1948 case av.GroupMethodDateDay:
1949 groupVal = contentTime.Format("2006-01-02")
1950 case av.GroupMethodDateWeek:
1951 year, week := contentTime.ISOWeek()
1952 groupVal = fmt.Sprintf("%d-W%02d", year, week)
1953 case av.GroupMethodDateMonth:
1954 groupVal = contentTime.Format("2006-01")
1955 case av.GroupMethodDateYear:
1956 groupVal = contentTime.Format("2006")
1957 case av.GroupMethodDateRelative:
1958 // 过去 30 天之前的按月分组
1959 // 过去 30 天、过去 7 天、昨天、今天、明天、未来 7 天、未来 30 天
1960 // 未来 30 天之后的按月分组
1961 if contentTime.Before(todayStart.AddDate(0, 0, -30)) {
1962 groupVal = contentTime.Format("2006-01") // 开头的数字用于排序
1963 } else if contentTime.Before(todayStart.AddDate(0, 0, -7)) {
1964 groupVal = groupValueLast30Days
1965 } else if contentTime.Before(todayStart.AddDate(0, 0, -1)) {
1966 groupVal = groupValueLast7Days
1967 } else if contentTime.Before(todayStart) {
1968 groupVal = groupValueYesterday
1969 } else if (contentTime.After(todayStart) || contentTime.Equal(todayStart)) && contentTime.Before(todayStart.AddDate(0, 0, 1)) {
1970 groupVal = groupValueToday
1971 } else if contentTime.After(todayStart.AddDate(0, 0, 30)) {
1972 groupVal = contentTime.Format("2006-01")
1973 } else if contentTime.After(todayStart.AddDate(0, 0, 7)) {
1974 groupVal = groupValueNext30Days
1975 } else if contentTime.Equal(todayStart.AddDate(0, 0, 2)) || contentTime.After(todayStart.AddDate(0, 0, 2)) {
1976 groupVal = groupValueNext7Days
1977 } else {
1978 groupVal = groupValueTomorrow
1979 }
1980 }
1981 }
1982
1983 groupItemsMap[groupVal] = append(groupItemsMap[groupVal], item)
1984 }
1985
1986 if av.KeyTypeSelect == groupKey.Type || av.KeyTypeMSelect == groupKey.Type {
1987 for _, o := range groupKey.Options {
1988 if _, ok := groupItemsMap[o.Name]; !ok {
1989 groupItemsMap[o.Name] = []av.Item{}
1990 }
1991 }
1992 }
1993
1994 if av.KeyTypeCheckbox != groupKey.Type {
1995 if 1 > len(groupItemsMap[groupValueDefault]) {
1996 // 始终保留默认分组 https://github.com/siyuan-note/siyuan/issues/15587
1997 groupItemsMap[groupValueDefault] = []av.Item{}
1998 }
1999 } else {
2000 // 对于复选框分组,空白分组表示未选中状态,始终保留 https://github.com/siyuan-note/siyuan/issues/15650
2001 if nil == groupItemsMap[""] {
2002 groupItemsMap[""] = []av.Item{}
2003 }
2004 if nil == groupItemsMap[av.CheckboxCheckedStr] {
2005 groupItemsMap[av.CheckboxCheckedStr] = []av.Item{}
2006 }
2007 }
2008
2009 for groupValue, groupItems := range groupItemsMap {
2010 var v *av.View
2011 switch view.LayoutType {
2012 case av.LayoutTypeTable:
2013 v = av.NewTableView()
2014 v.Table = av.NewLayoutTable()
2015 case av.LayoutTypeGallery:
2016 v = av.NewGalleryView()
2017 v.Gallery = av.NewLayoutGallery()
2018 case av.LayoutTypeKanban:
2019 v = av.NewKanbanView()
2020 v.Kanban = av.NewLayoutKanban()
2021 default:
2022 logging.LogWarnf("unknown layout type [%s] for group view", view.LayoutType)
2023 return
2024 }
2025
2026 v.GroupItemIDs = []string{}
2027 for _, item := range groupItems {
2028 v.GroupItemIDs = append(v.GroupItemIDs, item.GetID())
2029 }
2030
2031 v.Name = "" // 分组视图的名称在渲染时才填充
2032 v.GroupHidden = 1 // 默认隐藏空白分组
2033 v.GroupKey = groupKey
2034 v.GroupVal = &av.Value{Type: av.KeyTypeText, Text: &av.ValueText{Content: groupValue}}
2035 if av.KeyTypeSelect == groupKey.Type || av.KeyTypeMSelect == groupKey.Type {
2036 if opt := groupKey.GetOption(groupValue); nil != opt {
2037 v.GroupVal.Text = nil
2038 v.GroupVal.Type = av.KeyTypeSelect
2039 v.GroupVal.MSelect = []*av.ValueSelect{{Content: opt.Name, Color: opt.Color}}
2040 }
2041 } else if av.KeyTypeRelation == groupKey.Type {
2042 if relationDestAv != nil && groupValueDefault != groupValue {
2043 v.GroupVal.Text = nil
2044 v.GroupVal.Type = av.KeyTypeRelation
2045 v.GroupVal.Relation = &av.ValueRelation{BlockIDs: []string{groupValue}}
2046
2047 if destBlock := relationDestAv.GetBlockValue(groupValue); nil != destBlock {
2048 v.GroupVal.Relation.Contents = []*av.Value{destBlock}
2049 }
2050 }
2051 } else if av.KeyTypeCheckbox == groupKey.Type {
2052 v.GroupVal.Text = nil
2053 v.GroupVal.Type = av.KeyTypeCheckbox
2054 v.GroupVal.Checkbox = &av.ValueCheckbox{}
2055 if "" != groupValue {
2056 v.GroupVal.Checkbox.Checked = true
2057 }
2058 }
2059 v.GroupSort = -1
2060 view.Groups = append(view.Groups, v)
2061 }
2062
2063 view.GroupCreated = time.Now().UnixMilli()
2064 setAttrViewGroupStates(view, groupStates)
2065}
2066
2067// GroupState 用于临时记录每个分组视图的状态,以便后面重新生成分组后可以恢复这些状态。
2068type GroupState struct {
2069 ID string
2070 Folded bool
2071 Hidden int
2072 Sort int
2073 ItemIDs []string
2074}
2075
2076func getAttrViewGroupStates(view *av.View) (groupStates map[string]*GroupState) {
2077 groupStates = map[string]*GroupState{}
2078 if !view.IsGroupView() {
2079 return
2080 }
2081
2082 for _, groupView := range view.Groups {
2083 groupStates[groupView.GetGroupValue()] = &GroupState{
2084 ID: groupView.ID,
2085 Folded: groupView.GroupFolded,
2086 Hidden: groupView.GroupHidden,
2087 Sort: groupView.GroupSort,
2088 ItemIDs: groupView.GroupItemIDs,
2089 }
2090 }
2091 return
2092}
2093
2094func setAttrViewGroupStates(view *av.View, groupStates map[string]*GroupState) {
2095 for _, groupView := range view.Groups {
2096 if state, ok := groupStates[groupView.GetGroupValue()]; ok {
2097 groupView.ID = state.ID
2098 groupView.GroupFolded = state.Folded
2099 groupView.GroupHidden = state.Hidden
2100 groupView.GroupSort = state.Sort
2101
2102 itemIDsSort := map[string]int{}
2103 for i, itemID := range state.ItemIDs {
2104 itemIDsSort[itemID] = i
2105 }
2106
2107 sort.SliceStable(groupView.GroupItemIDs, func(i, j int) bool {
2108 return itemIDsSort[groupView.GroupItemIDs[i]] < itemIDsSort[groupView.GroupItemIDs[j]]
2109 })
2110 }
2111 }
2112
2113 defaultGroup := view.GetGroupByGroupValue(groupValueDefault)
2114 if nil != defaultGroup {
2115 if -1 == defaultGroup.GroupSort {
2116 view.RemoveGroupByID(defaultGroup.ID)
2117 } else {
2118 defaultGroup = nil
2119 }
2120 }
2121
2122 for i, groupView := range view.Groups {
2123 if i != groupView.GroupSort && -1 == groupView.GroupSort {
2124 groupView.GroupSort = i
2125 }
2126 }
2127
2128 if nil != defaultGroup {
2129 view.Groups = append(view.Groups, defaultGroup)
2130 defaultGroup.GroupSort = len(view.Groups) - 1
2131 }
2132}
2133
2134func GetCurrentAttributeViewImages(avID, viewID, query string) (ret []string, err error) {
2135 var attrView *av.AttributeView
2136 attrView, err = av.ParseAttributeView(avID)
2137 if err != nil {
2138 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
2139 return
2140 }
2141 var view *av.View
2142
2143 if "" != viewID {
2144 view, _ = attrView.GetCurrentView(viewID)
2145 } else {
2146 view = attrView.GetView(attrView.ViewID)
2147 }
2148
2149 cachedAttrViews := map[string]*av.AttributeView{}
2150 rollupFurtherCollections := sql.GetFurtherCollections(attrView, cachedAttrViews)
2151 table := getAttrViewTable(attrView, view, query)
2152 av.Filter(table, attrView, rollupFurtherCollections, cachedAttrViews)
2153 av.Sort(table, attrView)
2154
2155 ids := map[string]bool{}
2156 for _, column := range table.Columns {
2157 ids[column.ID] = column.Hidden
2158 }
2159
2160 for _, row := range table.Rows {
2161 for _, cell := range row.Cells {
2162 if nil != cell.Value && av.KeyTypeMAsset == cell.Value.Type && nil != cell.Value.MAsset && !ids[cell.Value.KeyID] {
2163 for _, a := range cell.Value.MAsset {
2164 if av.AssetTypeImage == a.Type {
2165 ret = append(ret, a.Content)
2166 }
2167 }
2168 }
2169 }
2170 }
2171 return
2172}
2173
2174func (tx *Transaction) doSetAttrViewColDateFillCreated(operation *Operation) (ret *TxErr) {
2175 err := setAttributeViewColDateFillCreated(operation)
2176 if err != nil {
2177 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
2178 }
2179 return
2180}
2181
2182func setAttributeViewColDateFillCreated(operation *Operation) (err error) {
2183 attrView, err := av.ParseAttributeView(operation.AvID)
2184 if err != nil {
2185 return
2186 }
2187
2188 keyID := operation.ID
2189 key, _ := attrView.GetKey(keyID)
2190 if nil == key || av.KeyTypeDate != key.Type {
2191 return
2192 }
2193
2194 if nil == key.Date {
2195 key.Date = &av.Date{}
2196 }
2197
2198 key.Date.AutoFillNow = operation.Data.(bool)
2199 err = av.SaveAttributeView(attrView)
2200 return
2201}
2202
2203func (tx *Transaction) doSetAttrViewColDateFillSpecificTime(operation *Operation) (ret *TxErr) {
2204 err := setAttrViewColDateFillSpecificTime(operation)
2205 if err != nil {
2206 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
2207 }
2208 return
2209}
2210
2211func setAttrViewColDateFillSpecificTime(operation *Operation) (err error) {
2212 attrView, err := av.ParseAttributeView(operation.AvID)
2213 if err != nil {
2214 return
2215 }
2216
2217 keyID := operation.ID
2218 dateValues, _ := attrView.GetKeyValues(keyID)
2219 if nil == dateValues || av.KeyTypeDate != dateValues.Key.Type {
2220 return
2221 }
2222
2223 if nil == dateValues.Key.Date {
2224 dateValues.Key.Date = &av.Date{}
2225 }
2226
2227 dateValues.Key.Date.FillSpecificTime = operation.Data.(bool)
2228 for _, v := range dateValues.Values {
2229 if !v.IsEmpty() {
2230 continue
2231 }
2232 if nil == v.Date {
2233 v.Date = &av.ValueDate{}
2234 }
2235 v.Date.IsNotTime = !dateValues.Key.Date.FillSpecificTime
2236 }
2237
2238 err = av.SaveAttributeView(attrView)
2239 return
2240}
2241
2242func (tx *Transaction) doHideAttrViewName(operation *Operation) (ret *TxErr) {
2243 err := hideAttrViewName(operation)
2244 if err != nil {
2245 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
2246 }
2247 return
2248}
2249
2250func hideAttrViewName(operation *Operation) (err error) {
2251 attrView, err := av.ParseAttributeView(operation.AvID)
2252 if err != nil {
2253 logging.LogErrorf("parse attribute view [%s] failed: %s", operation.AvID, err)
2254 return
2255 }
2256
2257 view, err := getAttrViewViewByBlockID(attrView, operation.BlockID)
2258 if nil == view {
2259 logging.LogErrorf("get view [%s] failed: %s", operation.BlockID, err)
2260 return
2261 }
2262
2263 view.HideAttrViewName = operation.Data.(bool)
2264 err = av.SaveAttributeView(attrView)
2265 return
2266}
2267
2268func (tx *Transaction) doUpdateAttrViewColRollup(operation *Operation) (ret *TxErr) {
2269 err := updateAttributeViewColRollup(operation)
2270 if err != nil {
2271 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
2272 }
2273 return
2274}
2275
2276func updateAttributeViewColRollup(operation *Operation) (err error) {
2277 // operation.AvID 汇总字段所在 av
2278 // operation.ID 汇总字段 ID
2279 // operation.ParentID 汇总字段基于的关联字段 ID
2280 // operation.KeyID 目标字段 ID
2281 // operation.Data 计算方式
2282
2283 attrView, err := av.ParseAttributeView(operation.AvID)
2284 if err != nil {
2285 return
2286 }
2287
2288 rollUpKey, _ := attrView.GetKey(operation.ID)
2289 if nil == rollUpKey {
2290 return
2291 }
2292
2293 rollUpKey.Rollup = &av.Rollup{
2294 RelationKeyID: operation.ParentID,
2295 KeyID: operation.KeyID,
2296 }
2297
2298 if nil == operation.Data {
2299 return
2300 }
2301
2302 data := operation.Data.(map[string]interface{})
2303 if nil != data["calc"] {
2304 calcData, jsonErr := gulu.JSON.MarshalJSON(data["calc"])
2305 if nil != jsonErr {
2306 err = jsonErr
2307 return
2308 }
2309 if jsonErr = gulu.JSON.UnmarshalJSON(calcData, &rollUpKey.Rollup.Calc); nil != jsonErr {
2310 err = jsonErr
2311 return
2312 }
2313 }
2314
2315 // 如果存在该汇总字段的过滤条件,则移除该过滤条件 https://github.com/siyuan-note/siyuan/issues/15660
2316 for _, view := range attrView.Views {
2317 for i, filter := range view.Filters {
2318 if filter.Column != rollUpKey.ID {
2319 continue
2320 }
2321
2322 view.Filters = append(view.Filters[:i], view.Filters[i+1:]...)
2323 }
2324 }
2325
2326 err = av.SaveAttributeView(attrView)
2327 return
2328}
2329
2330func (tx *Transaction) doUpdateAttrViewColRelation(operation *Operation) (ret *TxErr) {
2331 err := updateAttributeViewColRelation(operation)
2332 if err != nil {
2333 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
2334 }
2335 return
2336}
2337
2338func updateAttributeViewColRelation(operation *Operation) (err error) {
2339 // operation.AvID 源 avID
2340 // operation.ID 目标 avID
2341 // operation.KeyID 源 av 关联字段 ID
2342 // operation.IsTwoWay 是否双向关联
2343 // operation.BackRelationKeyID 双向关联的目标关联字段 ID
2344 // operation.Name 双向关联的目标关联字段名称
2345 // operation.Format 源 av 关联字段名称
2346
2347 srcAv, err := av.ParseAttributeView(operation.AvID)
2348 if err != nil {
2349 return
2350 }
2351
2352 destAv, err := av.ParseAttributeView(operation.ID)
2353 if err != nil {
2354 return
2355 }
2356
2357 isSameAv := srcAv.ID == destAv.ID
2358 if isSameAv {
2359 destAv = srcAv
2360 }
2361
2362 for _, keyValues := range srcAv.KeyValues {
2363 if keyValues.Key.ID != operation.KeyID {
2364 continue
2365 }
2366
2367 srcRel := keyValues.Key.Relation
2368 // 已经设置过双向关联的话需要先断开双向关联
2369 if nil != srcRel {
2370 if srcRel.IsTwoWay {
2371 oldDestAv, _ := av.ParseAttributeView(srcRel.AvID)
2372 if nil != oldDestAv {
2373 isOldSameAv := oldDestAv.ID == destAv.ID
2374 if isOldSameAv {
2375 oldDestAv = destAv
2376 }
2377
2378 oldDestKey, _ := oldDestAv.GetKey(srcRel.BackKeyID)
2379 if nil != oldDestKey && nil != oldDestKey.Relation && oldDestKey.Relation.AvID == srcAv.ID && oldDestKey.Relation.IsTwoWay {
2380 oldDestKey.Relation.IsTwoWay = false
2381 oldDestKey.Relation.BackKeyID = ""
2382 }
2383
2384 if !isOldSameAv {
2385 err = av.SaveAttributeView(oldDestAv)
2386 if err != nil {
2387 return
2388 }
2389 }
2390 }
2391 }
2392
2393 av.RemoveAvRel(srcAv.ID, srcRel.AvID)
2394 }
2395
2396 srcRel = &av.Relation{
2397 AvID: operation.ID,
2398 IsTwoWay: operation.IsTwoWay,
2399 }
2400 if operation.IsTwoWay {
2401 srcRel.BackKeyID = operation.BackRelationKeyID
2402 } else {
2403 srcRel.BackKeyID = ""
2404 }
2405 keyValues.Key.Relation = srcRel
2406 keyValues.Key.Name = operation.Format
2407
2408 break
2409 }
2410
2411 destAdded := false
2412 backRelKey, _ := destAv.GetKey(operation.BackRelationKeyID)
2413 if nil != backRelKey {
2414 backRelKey.Relation = &av.Relation{
2415 AvID: operation.AvID,
2416 IsTwoWay: operation.IsTwoWay,
2417 BackKeyID: operation.KeyID,
2418 }
2419 destAdded = true
2420 if operation.IsTwoWay {
2421 name := strings.TrimSpace(operation.Name)
2422 if "" == name {
2423 name = srcAv.Name + " " + operation.Format
2424 }
2425 backRelKey.Name = strings.TrimSpace(name)
2426 } else {
2427 backRelKey.Relation.BackKeyID = ""
2428 }
2429 }
2430
2431 if !destAdded && operation.IsTwoWay {
2432 // 新建双向关联目标字段
2433 name := strings.TrimSpace(operation.Name)
2434 if "" == name {
2435 name = srcAv.Name + " " + operation.Format
2436 name = strings.TrimSpace(name)
2437 }
2438
2439 destKeyValues := &av.KeyValues{
2440 Key: &av.Key{
2441 ID: operation.BackRelationKeyID,
2442 Name: name,
2443 Type: av.KeyTypeRelation,
2444 Relation: &av.Relation{AvID: operation.AvID, IsTwoWay: operation.IsTwoWay, BackKeyID: operation.KeyID},
2445 },
2446 }
2447 destAv.KeyValues = append(destAv.KeyValues, destKeyValues)
2448
2449 for _, v := range destAv.Views {
2450 switch v.LayoutType {
2451 case av.LayoutTypeTable:
2452 v.Table.Columns = append(v.Table.Columns, &av.ViewTableColumn{BaseField: &av.BaseField{ID: operation.BackRelationKeyID}})
2453 case av.LayoutTypeGallery:
2454 v.Gallery.CardFields = append(v.Gallery.CardFields, &av.ViewGalleryCardField{BaseField: &av.BaseField{ID: operation.BackRelationKeyID}})
2455 case av.LayoutTypeKanban:
2456 v.Kanban.Fields = append(v.Kanban.Fields, &av.ViewKanbanField{BaseField: &av.BaseField{ID: operation.BackRelationKeyID}})
2457 }
2458 }
2459
2460 now := time.Now().UnixMilli()
2461 // 和现有值进行关联
2462 for _, keyValues := range srcAv.KeyValues {
2463 if keyValues.Key.ID != operation.KeyID {
2464 continue
2465 }
2466
2467 for _, srcVal := range keyValues.Values {
2468 for _, blockID := range srcVal.Relation.BlockIDs {
2469 destVal := destAv.GetValue(destKeyValues.Key.ID, blockID)
2470 if nil == destVal {
2471 destVal = &av.Value{ID: ast.NewNodeID(), KeyID: destKeyValues.Key.ID, BlockID: blockID, Type: keyValues.Key.Type, Relation: &av.ValueRelation{}, CreatedAt: now, UpdatedAt: now + 1000}
2472 } else {
2473 destVal.Type = keyValues.Key.Type
2474 if nil == destVal.Relation {
2475 destVal.Relation = &av.ValueRelation{}
2476 }
2477 destVal.UpdatedAt = now
2478 destVal.IsRenderAutoFill = false
2479 }
2480 destVal.Relation.BlockIDs = append(destVal.Relation.BlockIDs, srcVal.BlockID)
2481 destVal.Relation.BlockIDs = gulu.Str.RemoveDuplicatedElem(destVal.Relation.BlockIDs)
2482 destKeyValues.Values = append(destKeyValues.Values, destVal)
2483 }
2484 }
2485 }
2486 }
2487
2488 regenAttrViewGroups(srcAv)
2489 err = av.SaveAttributeView(srcAv)
2490 if err != nil {
2491 return
2492 }
2493 if !isSameAv {
2494 regenAttrViewGroups(destAv)
2495 err = av.SaveAttributeView(destAv)
2496 ReloadAttrView(destAv.ID)
2497 }
2498
2499 av.UpsertAvBackRel(srcAv.ID, destAv.ID)
2500 if operation.IsTwoWay && !isSameAv {
2501 av.UpsertAvBackRel(destAv.ID, srcAv.ID)
2502 }
2503 return
2504}
2505
2506func (tx *Transaction) doSortAttrViewView(operation *Operation) (ret *TxErr) {
2507 avID := operation.AvID
2508 attrView, err := av.ParseAttributeView(avID)
2509 if err != nil {
2510 logging.LogErrorf("parse attribute view [%s] failed: %s", operation.AvID, err)
2511 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
2512 }
2513
2514 view := attrView.GetView(operation.ID)
2515 if nil == view {
2516 logging.LogErrorf("get view failed: %s", operation.BlockID)
2517 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
2518 }
2519 viewID := view.ID
2520 previousViewID := operation.PreviousID
2521 if viewID == previousViewID {
2522 return
2523 }
2524
2525 var index, previousIndex int
2526 for i, v := range attrView.Views {
2527 if v.ID == viewID {
2528 view = v
2529 index = i
2530 break
2531 }
2532 }
2533
2534 attrView.Views = append(attrView.Views[:index], attrView.Views[index+1:]...)
2535 for i, v := range attrView.Views {
2536 if v.ID == previousViewID {
2537 previousIndex = i + 1
2538 break
2539 }
2540 }
2541 attrView.Views = util.InsertElem(attrView.Views, previousIndex, view)
2542
2543 if err = av.SaveAttributeView(attrView); err != nil {
2544 logging.LogErrorf("save attribute view [%s] failed: %s", avID, err)
2545 return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: avID}
2546 }
2547 return
2548}
2549
2550func (tx *Transaction) doRemoveAttrViewView(operation *Operation) (ret *TxErr) {
2551 var err error
2552 avID := operation.AvID
2553 attrView, err := av.ParseAttributeView(avID)
2554 if err != nil {
2555 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
2556 return &TxErr{code: TxErrCodeBlockNotFound, id: avID}
2557 }
2558
2559 if 1 >= len(attrView.Views) {
2560 logging.LogWarnf("can't remove last view [%s] of attribute view [%s]", operation.AvID, avID)
2561 return
2562 }
2563
2564 view, err := getAttrViewViewByBlockID(attrView, operation.BlockID)
2565 if nil == view {
2566 logging.LogWarnf("get view failed: %s", operation.BlockID)
2567 return
2568 }
2569
2570 viewID := view.ID
2571 var index int
2572 for i, view := range attrView.Views {
2573 if viewID == view.ID {
2574 attrView.Views = append(attrView.Views[:i], attrView.Views[i+1:]...)
2575 index = i - 1
2576 break
2577 }
2578 }
2579 if 0 > index {
2580 index = 0
2581 }
2582
2583 view = attrView.Views[index]
2584 attrView.ViewID = view.ID
2585 if err = av.SaveAttributeView(attrView); err != nil {
2586 logging.LogErrorf("save attribute view [%s] failed: %s", avID, err)
2587 return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: avID}
2588 }
2589
2590 trees, nodes := getMirrorBlocksNodes(avID)
2591 for _, node := range nodes {
2592 attrs := parse.IAL2Map(node.KramdownIAL)
2593 blockViewID := attrs[av.NodeAttrView]
2594 if blockViewID == viewID {
2595 attrs[av.NodeAttrView] = attrView.ViewID
2596 node.AttributeViewType = string(view.LayoutType)
2597 oldAttrs, e := setNodeAttrs0(node, attrs)
2598 if nil != e {
2599 logging.LogErrorf("set node attrs failed: %s", e)
2600 continue
2601 }
2602
2603 cache.PutBlockIAL(node.ID, parse.IAL2Map(node.KramdownIAL))
2604 pushBroadcastAttrTransactions(oldAttrs, node)
2605 }
2606 }
2607
2608 for _, tree := range trees {
2609 if err = indexWriteTreeUpsertQueue(tree); err != nil {
2610 return
2611 }
2612 }
2613
2614 operation.RetData = view.LayoutType
2615 return
2616}
2617
2618func getMirrorBlocksNodes(avID string) (trees []*parse.Tree, nodes []*ast.Node) {
2619 mirrorBlockIDs := treenode.GetMirrorAttrViewBlockIDs(avID)
2620 mirrorBlockTrees := filesys.LoadTrees(mirrorBlockIDs)
2621 for id, tree := range mirrorBlockTrees {
2622 node := treenode.GetNodeInTree(tree, id)
2623 if nil == node {
2624 logging.LogErrorf("get node in tree by block ID [%s] failed", id)
2625 continue
2626 }
2627 nodes = append(nodes, node)
2628 }
2629
2630 for _, tree := range mirrorBlockTrees {
2631 trees = append(trees, tree)
2632 }
2633 return
2634}
2635
2636func (tx *Transaction) doDuplicateAttrViewView(operation *Operation) (ret *TxErr) {
2637 var err error
2638 avID := operation.AvID
2639 attrView, err := av.ParseAttributeView(avID)
2640 if err != nil {
2641 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
2642 return &TxErr{code: TxErrHandleAttributeView, id: avID}
2643 }
2644
2645 masterView := attrView.GetView(operation.PreviousID)
2646 if nil == masterView {
2647 logging.LogErrorf("get master view failed: %s", avID)
2648 return &TxErr{code: TxErrHandleAttributeView, id: avID}
2649 }
2650
2651 node, tree, _ := getNodeByBlockID(nil, operation.BlockID)
2652 if nil == node {
2653 logging.LogErrorf("get node by block ID [%s] failed", operation.BlockID)
2654 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID}
2655 }
2656
2657 attrs := parse.IAL2Map(node.KramdownIAL)
2658 attrs[av.NodeAttrView] = operation.ID
2659 node.AttributeViewType = string(masterView.LayoutType)
2660 err = setNodeAttrs(node, tree, attrs)
2661 if err != nil {
2662 logging.LogWarnf("set node [%s] attrs failed: %s", operation.BlockID, err)
2663 return
2664 }
2665
2666 var view *av.View
2667 switch masterView.LayoutType {
2668 case av.LayoutTypeTable:
2669 view = av.NewTableView()
2670 case av.LayoutTypeGallery:
2671 view = av.NewGalleryView()
2672 case av.LayoutTypeKanban:
2673 view = av.NewKanbanView()
2674 }
2675
2676 view.ID = operation.ID
2677 attrView.Views = append(attrView.Views, view)
2678 attrView.ViewID = view.ID
2679
2680 view.Icon = masterView.Icon
2681 view.Name = util.GetDuplicateName(masterView.Name)
2682 view.HideAttrViewName = masterView.HideAttrViewName
2683 view.Desc = masterView.Desc
2684 view.LayoutType = masterView.LayoutType
2685 view.PageSize = masterView.PageSize
2686
2687 for _, filter := range masterView.Filters {
2688 view.Filters = append(view.Filters, &av.ViewFilter{
2689 Column: filter.Column,
2690 Qualifier: filter.Qualifier,
2691 Operator: filter.Operator,
2692 Value: filter.Value,
2693 RelativeDate: filter.RelativeDate,
2694 RelativeDate2: filter.RelativeDate2,
2695 })
2696 }
2697
2698 for _, s := range masterView.Sorts {
2699 view.Sorts = append(view.Sorts, &av.ViewSort{
2700 Column: s.Column,
2701 Order: s.Order,
2702 })
2703 }
2704
2705 switch masterView.LayoutType {
2706 case av.LayoutTypeTable:
2707 for _, col := range masterView.Table.Columns {
2708 view.Table.Columns = append(view.Table.Columns, &av.ViewTableColumn{
2709 BaseField: &av.BaseField{
2710 ID: col.ID,
2711 Wrap: col.Wrap,
2712 Hidden: col.Hidden,
2713 Desc: col.Desc,
2714 },
2715 Pin: col.Pin,
2716 Width: col.Width,
2717 Calc: col.Calc,
2718 })
2719 }
2720
2721 view.Table.ShowIcon = masterView.Table.ShowIcon
2722 view.Table.WrapField = masterView.Table.WrapField
2723 case av.LayoutTypeGallery:
2724 for _, field := range masterView.Gallery.CardFields {
2725 view.Gallery.CardFields = append(view.Gallery.CardFields, &av.ViewGalleryCardField{
2726 BaseField: &av.BaseField{
2727 ID: field.ID,
2728 Wrap: field.Wrap,
2729 Hidden: field.Hidden,
2730 Desc: field.Desc,
2731 },
2732 })
2733 }
2734
2735 view.Gallery.CoverFrom = masterView.Gallery.CoverFrom
2736 view.Gallery.CoverFromAssetKeyID = masterView.Gallery.CoverFromAssetKeyID
2737 view.Gallery.CardSize = masterView.Gallery.CardSize
2738 view.Gallery.FitImage = masterView.Gallery.FitImage
2739 view.Gallery.DisplayFieldName = masterView.Gallery.DisplayFieldName
2740 view.Gallery.ShowIcon = masterView.Gallery.ShowIcon
2741 view.Gallery.WrapField = masterView.Gallery.WrapField
2742 case av.LayoutTypeKanban:
2743 for _, field := range masterView.Kanban.Fields {
2744 view.Kanban.Fields = append(view.Kanban.Fields, &av.ViewKanbanField{
2745 BaseField: &av.BaseField{
2746 ID: field.ID,
2747 Wrap: field.Wrap,
2748 Hidden: field.Hidden,
2749 Desc: field.Desc,
2750 },
2751 })
2752 }
2753
2754 view.Kanban.CoverFrom = masterView.Kanban.CoverFrom
2755 view.Kanban.CoverFromAssetKeyID = masterView.Kanban.CoverFromAssetKeyID
2756 view.Kanban.CardSize = masterView.Kanban.CardSize
2757 view.Kanban.FitImage = masterView.Kanban.FitImage
2758 view.Kanban.DisplayFieldName = masterView.Kanban.DisplayFieldName
2759 view.Kanban.ShowIcon = masterView.Kanban.ShowIcon
2760 view.Kanban.WrapField = masterView.Kanban.WrapField
2761 }
2762
2763 view.ItemIDs = masterView.ItemIDs
2764
2765 if nil != masterView.Group {
2766 view.Group = &av.ViewGroup{}
2767 if copyErr := copier.Copy(view.Group, masterView.Group); nil != copyErr {
2768 logging.LogErrorf("copy group failed: %s", copyErr)
2769 return &TxErr{code: TxErrHandleAttributeView, id: avID, msg: copyErr.Error()}
2770 }
2771
2772 view.GroupItemIDs = masterView.GroupItemIDs
2773 regenAttrViewGroups(attrView)
2774 }
2775
2776 if err = av.SaveAttributeView(attrView); err != nil {
2777 logging.LogErrorf("save attribute view [%s] failed: %s", avID, err)
2778 return &TxErr{code: TxErrHandleAttributeView, msg: err.Error(), id: avID}
2779 }
2780 return
2781}
2782
2783func (tx *Transaction) doAddAttrViewView(operation *Operation) (ret *TxErr) {
2784 err := addAttrViewView(operation.AvID, operation.ID, operation.BlockID, operation.Layout)
2785 if nil != err {
2786 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
2787 }
2788 return
2789}
2790
2791func addAttrViewView(avID, viewID, blockID string, layout av.LayoutType) (err error) {
2792 attrView, err := av.ParseAttributeView(avID)
2793 if err != nil {
2794 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
2795 return
2796 }
2797
2798 if 1 > len(attrView.Views) {
2799 logging.LogErrorf("no view in attribute view [%s]", avID)
2800 return
2801 }
2802
2803 firstView := attrView.Views[0]
2804 if nil == firstView {
2805 logging.LogErrorf("get first view failed: %s", avID)
2806 return
2807 }
2808
2809 if "" == layout {
2810 layout = av.LayoutTypeTable
2811 }
2812
2813 var view *av.View
2814 switch layout {
2815 case av.LayoutTypeTable:
2816 view = av.NewTableView()
2817 switch firstView.LayoutType {
2818 case av.LayoutTypeTable:
2819 for _, col := range firstView.Table.Columns {
2820 view.Table.Columns = append(view.Table.Columns, &av.ViewTableColumn{BaseField: &av.BaseField{ID: col.ID}, Width: col.Width})
2821 }
2822 case av.LayoutTypeGallery:
2823 for _, field := range firstView.Gallery.CardFields {
2824 view.Table.Columns = append(view.Table.Columns, &av.ViewTableColumn{BaseField: &av.BaseField{ID: field.ID}})
2825 }
2826 case av.LayoutTypeKanban:
2827 for _, field := range firstView.Kanban.Fields {
2828 view.Table.Columns = append(view.Table.Columns, &av.ViewTableColumn{BaseField: &av.BaseField{ID: field.ID}})
2829 }
2830 }
2831 case av.LayoutTypeGallery:
2832 view = av.NewGalleryView()
2833 switch firstView.LayoutType {
2834 case av.LayoutTypeTable:
2835 for _, col := range firstView.Table.Columns {
2836 view.Gallery.CardFields = append(view.Gallery.CardFields, &av.ViewGalleryCardField{BaseField: &av.BaseField{ID: col.ID}})
2837 }
2838 case av.LayoutTypeGallery:
2839 for _, field := range firstView.Gallery.CardFields {
2840 view.Gallery.CardFields = append(view.Gallery.CardFields, &av.ViewGalleryCardField{BaseField: &av.BaseField{ID: field.ID}})
2841 }
2842 case av.LayoutTypeKanban:
2843 for _, field := range firstView.Kanban.Fields {
2844 view.Gallery.CardFields = append(view.Gallery.CardFields, &av.ViewGalleryCardField{BaseField: &av.BaseField{ID: field.ID}})
2845 }
2846 }
2847 case av.LayoutTypeKanban:
2848 view = av.NewKanbanView()
2849 switch firstView.LayoutType {
2850 case av.LayoutTypeTable:
2851 for _, col := range firstView.Table.Columns {
2852 view.Kanban.Fields = append(view.Kanban.Fields, &av.ViewKanbanField{BaseField: &av.BaseField{ID: col.ID}})
2853 }
2854 case av.LayoutTypeGallery:
2855 for _, field := range firstView.Gallery.CardFields {
2856 view.Kanban.Fields = append(view.Kanban.Fields, &av.ViewKanbanField{BaseField: &av.BaseField{ID: field.ID}})
2857 }
2858 case av.LayoutTypeKanban:
2859 for _, field := range firstView.Kanban.Fields {
2860 view.Kanban.Fields = append(view.Kanban.Fields, &av.ViewKanbanField{BaseField: &av.BaseField{ID: field.ID}})
2861 }
2862 }
2863
2864 preferredGroupKey := getKanbanPreferredGroupKey(attrView)
2865 group := &av.ViewGroup{Field: preferredGroupKey.ID}
2866 setAttributeViewGroup(attrView, view, group)
2867 default:
2868 err = av.ErrWrongLayoutType
2869 logging.LogErrorf("wrong layout type [%s] for attribute view [%s]", layout, avID)
2870 return
2871 }
2872
2873 view.ItemIDs = firstView.ItemIDs
2874 attrView.ViewID = viewID
2875 view.ID = viewID
2876 attrView.Views = append(attrView.Views, view)
2877
2878 node, tree, _ := getNodeByBlockID(nil, blockID)
2879 if nil == node {
2880 logging.LogErrorf("get node by block ID [%s] failed", blockID)
2881 return
2882 }
2883
2884 node.AttributeViewType = string(view.LayoutType)
2885 attrs := parse.IAL2Map(node.KramdownIAL)
2886 attrs[av.NodeAttrView] = viewID
2887 err = setNodeAttrs(node, tree, attrs)
2888 if err != nil {
2889 logging.LogWarnf("set node [%s] attrs failed: %s", blockID, err)
2890 return
2891 }
2892
2893 if err = av.SaveAttributeView(attrView); err != nil {
2894 logging.LogErrorf("save attribute view [%s] failed: %s", avID, err)
2895 return
2896 }
2897 return
2898}
2899
2900func getKanbanPreferredGroupKey(attrView *av.AttributeView) (ret *av.Key) {
2901 for _, kv := range attrView.KeyValues {
2902 if av.KeyTypeSelect == kv.Key.Type {
2903 ret = kv.Key
2904 break
2905 }
2906 }
2907 if nil == ret {
2908 ret = attrView.GetBlockKey()
2909 }
2910 return
2911}
2912
2913func (tx *Transaction) doSetAttrViewViewName(operation *Operation) (ret *TxErr) {
2914 var err error
2915 avID := operation.AvID
2916 attrView, err := av.ParseAttributeView(avID)
2917 if err != nil {
2918 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
2919 return &TxErr{code: TxErrHandleAttributeView, id: avID}
2920 }
2921
2922 viewID := operation.ID
2923 view := attrView.GetView(viewID)
2924 if nil == view {
2925 logging.LogErrorf("get view [%s] failed: %s", viewID, err)
2926 return &TxErr{code: TxErrHandleAttributeView, id: viewID}
2927 }
2928
2929 view.Name = strings.TrimSpace(operation.Data.(string))
2930 if err = av.SaveAttributeView(attrView); err != nil {
2931 logging.LogErrorf("save attribute view [%s] failed: %s", avID, err)
2932 return &TxErr{code: TxErrHandleAttributeView, msg: err.Error(), id: avID}
2933 }
2934 return
2935}
2936
2937func (tx *Transaction) doSetAttrViewViewIcon(operation *Operation) (ret *TxErr) {
2938 var err error
2939 avID := operation.AvID
2940 attrView, err := av.ParseAttributeView(avID)
2941 if err != nil {
2942 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
2943 return &TxErr{code: TxErrHandleAttributeView, id: avID}
2944 }
2945
2946 viewID := operation.ID
2947 view := attrView.GetView(viewID)
2948 if nil == view {
2949 logging.LogErrorf("get view [%s] failed: %s", viewID, err)
2950 return &TxErr{code: TxErrHandleAttributeView, id: viewID}
2951 }
2952
2953 view.Icon = operation.Data.(string)
2954 if err = av.SaveAttributeView(attrView); err != nil {
2955 logging.LogErrorf("save attribute view [%s] failed: %s", avID, err)
2956 return &TxErr{code: TxErrHandleAttributeView, msg: err.Error(), id: avID}
2957 }
2958 return
2959}
2960
2961func (tx *Transaction) doSetAttrViewViewDesc(operation *Operation) (ret *TxErr) {
2962 var err error
2963 avID := operation.AvID
2964 attrView, err := av.ParseAttributeView(avID)
2965 if err != nil {
2966 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
2967 return &TxErr{code: TxErrHandleAttributeView, id: avID}
2968 }
2969
2970 viewID := operation.ID
2971 view := attrView.GetView(viewID)
2972 if nil == view {
2973 logging.LogErrorf("get view [%s] failed: %s", viewID, err)
2974 return &TxErr{code: TxErrHandleAttributeView, id: viewID}
2975 }
2976
2977 view.Desc = strings.TrimSpace(operation.Data.(string))
2978 if err = av.SaveAttributeView(attrView); err != nil {
2979 logging.LogErrorf("save attribute view [%s] failed: %s", avID, err)
2980 return &TxErr{code: TxErrHandleAttributeView, msg: err.Error(), id: avID}
2981 }
2982 return
2983}
2984
2985func (tx *Transaction) doSetAttrViewName(operation *Operation) (ret *TxErr) {
2986 err := tx.setAttributeViewName(operation)
2987 if err != nil {
2988 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
2989 }
2990 return
2991}
2992
2993const attrAvNameTpl = `<span data-av-id="${avID}" data-popover-url="/api/av/getMirrorDatabaseBlocks" class="popover__block">${avName}</span>`
2994
2995func (tx *Transaction) setAttributeViewName(operation *Operation) (err error) {
2996 avID := operation.ID
2997 attrView, err := av.ParseAttributeView(avID)
2998 if err != nil {
2999 return
3000 }
3001
3002 attrView.Name = strings.TrimSpace(operation.Data.(string))
3003 attrView.Name = strings.ReplaceAll(attrView.Name, "\n", " ")
3004 if 512 < utf8.RuneCountInString(attrView.Name) {
3005 attrView.Name = gulu.Str.SubStr(attrView.Name, 512)
3006 }
3007 err = av.SaveAttributeView(attrView)
3008
3009 _, nodes := tx.getAttrViewBoundNodes(attrView)
3010 for _, node := range nodes {
3011 avNames := getAvNames(node.IALAttr(av.NodeAttrNameAvs))
3012 oldAttrs := parse.IAL2Map(node.KramdownIAL)
3013 node.SetIALAttr(av.NodeAttrViewNames, avNames)
3014 pushBroadcastAttrTransactions(oldAttrs, node)
3015 }
3016 return
3017}
3018
3019func getAvNames(avIDs string) (ret string) {
3020 if "" == avIDs {
3021 return
3022 }
3023 avNames := bytes.Buffer{}
3024 nodeAvIDs := strings.Split(avIDs, ",")
3025 for _, nodeAvID := range nodeAvIDs {
3026 nodeAvName, getErr := av.GetAttributeViewName(nodeAvID)
3027 if nil != getErr {
3028 continue
3029 }
3030 if "" == nodeAvName {
3031 nodeAvName = Conf.language(105)
3032 }
3033
3034 tpl := strings.ReplaceAll(attrAvNameTpl, "${avID}", nodeAvID)
3035 tpl = strings.ReplaceAll(tpl, "${avName}", nodeAvName)
3036 avNames.WriteString(tpl)
3037 avNames.WriteString(" ")
3038 }
3039 if 0 < avNames.Len() {
3040 avNames.Truncate(avNames.Len() - 6)
3041 ret = avNames.String()
3042 }
3043 return
3044}
3045
3046func (tx *Transaction) getAttrViewBoundNodes(attrView *av.AttributeView) (trees map[string]*parse.Tree, nodes []*ast.Node) {
3047 blockKeyValues := attrView.GetBlockKeyValues()
3048 trees = map[string]*parse.Tree{}
3049 for _, blockKeyValue := range blockKeyValues.Values {
3050 if blockKeyValue.IsDetached {
3051 continue
3052 }
3053
3054 var tree *parse.Tree
3055 tree = trees[blockKeyValue.Block.ID]
3056 if nil == tree {
3057 if nil == tx {
3058 tree, _ = LoadTreeByBlockID(blockKeyValue.Block.ID)
3059 } else {
3060 tree, _ = tx.loadTree(blockKeyValue.Block.ID)
3061 }
3062 }
3063 if nil == tree {
3064 continue
3065 }
3066 trees[blockKeyValue.Block.ID] = tree
3067
3068 node := treenode.GetNodeInTree(tree, blockKeyValue.Block.ID)
3069 if nil == node {
3070 continue
3071 }
3072
3073 nodes = append(nodes, node)
3074 }
3075 return
3076}
3077
3078func (tx *Transaction) doSetAttrViewFilters(operation *Operation) (ret *TxErr) {
3079 err := setAttributeViewFilters(operation)
3080 if err != nil {
3081 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
3082 }
3083 return
3084}
3085
3086func setAttributeViewFilters(operation *Operation) (err error) {
3087 attrView, err := av.ParseAttributeView(operation.AvID)
3088 if err != nil {
3089 return
3090 }
3091
3092 view, err := getAttrViewViewByBlockID(attrView, operation.BlockID)
3093 if err != nil {
3094 return
3095 }
3096
3097 operationData := operation.Data.([]interface{})
3098 data, err := gulu.JSON.MarshalJSON(operationData)
3099 if err != nil {
3100 return
3101 }
3102
3103 if err = gulu.JSON.UnmarshalJSON(data, &view.Filters); err != nil {
3104 return
3105 }
3106
3107 err = av.SaveAttributeView(attrView)
3108 return
3109}
3110
3111func (tx *Transaction) doSetAttrViewSorts(operation *Operation) (ret *TxErr) {
3112 err := setAttributeViewSorts(operation)
3113 if err != nil {
3114 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
3115 }
3116 return
3117}
3118
3119func setAttributeViewSorts(operation *Operation) (err error) {
3120 attrView, err := av.ParseAttributeView(operation.AvID)
3121 if err != nil {
3122 return
3123 }
3124
3125 view, err := getAttrViewViewByBlockID(attrView, operation.BlockID)
3126 if err != nil {
3127 return
3128 }
3129
3130 operationData := operation.Data.([]interface{})
3131 data, err := gulu.JSON.MarshalJSON(operationData)
3132 if err != nil {
3133 return
3134 }
3135
3136 if err = gulu.JSON.UnmarshalJSON(data, &view.Sorts); err != nil {
3137 return
3138 }
3139
3140 err = av.SaveAttributeView(attrView)
3141 return
3142}
3143
3144func (tx *Transaction) doSetAttrViewPageSize(operation *Operation) (ret *TxErr) {
3145 err := setAttributeViewPageSize(operation)
3146 if err != nil {
3147 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
3148 }
3149 return
3150}
3151
3152func setAttributeViewPageSize(operation *Operation) (err error) {
3153 attrView, err := av.ParseAttributeView(operation.AvID)
3154 if err != nil {
3155 return
3156 }
3157
3158 view, err := getAttrViewViewByBlockID(attrView, operation.BlockID)
3159 if err != nil {
3160 return
3161 }
3162
3163 view.PageSize = int(operation.Data.(float64))
3164
3165 err = av.SaveAttributeView(attrView)
3166 return
3167}
3168
3169func (tx *Transaction) doSetAttrViewColCalc(operation *Operation) (ret *TxErr) {
3170 err := setAttributeViewColumnCalc(operation)
3171 if err != nil {
3172 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
3173 }
3174 return
3175}
3176
3177func setAttributeViewColumnCalc(operation *Operation) (err error) {
3178 attrView, err := av.ParseAttributeView(operation.AvID)
3179 if err != nil {
3180 return
3181 }
3182
3183 view, err := getAttrViewViewByBlockID(attrView, operation.BlockID)
3184 if err != nil {
3185 return
3186 }
3187
3188 operationData := operation.Data.(interface{})
3189 data, err := gulu.JSON.MarshalJSON(operationData)
3190 if err != nil {
3191 return
3192 }
3193
3194 calc := &av.FieldCalc{}
3195 switch view.LayoutType {
3196 case av.LayoutTypeTable:
3197 if err = gulu.JSON.UnmarshalJSON(data, calc); err != nil {
3198 return
3199 }
3200
3201 for _, column := range view.Table.Columns {
3202 if column.ID == operation.ID {
3203 column.Calc = calc
3204 break
3205 }
3206 }
3207 case av.LayoutTypeGallery, av.LayoutTypeKanban:
3208 return
3209 }
3210
3211 err = av.SaveAttributeView(attrView)
3212 return
3213}
3214
3215func (tx *Transaction) doInsertAttrViewBlock(operation *Operation) (ret *TxErr) {
3216 if nil == operation.Context {
3217 operation.Context = map[string]interface{}{}
3218 }
3219
3220 err := AddAttributeViewBlock(tx, operation.Srcs, operation.AvID, operation.BlockID, operation.ViewID, operation.GroupID, operation.PreviousID, operation.IgnoreDefaultFill, operation.Context)
3221 if err != nil {
3222 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
3223 }
3224 return
3225}
3226
3227func AddAttributeViewBlock(tx *Transaction, srcs []map[string]interface{}, avID, dbBlockID, viewID, groupID, previousItemID string, ignoreDefaultFill bool, context map[string]interface{}) (err error) {
3228 slices.Reverse(srcs) // https://github.com/siyuan-note/siyuan/issues/11286
3229
3230 now := time.Now().UnixMilli()
3231 for _, src := range srcs {
3232 boundBlockID := ""
3233 srcItemID := ast.NewNodeID()
3234 if nil != src["itemID"] {
3235 srcItemID = src["itemID"].(string)
3236 }
3237
3238 isDetached := src["isDetached"].(bool)
3239 var tree *parse.Tree
3240 if !isDetached {
3241 boundBlockID = src["id"].(string)
3242 if !ast.IsNodeIDPattern(boundBlockID) {
3243 continue
3244 }
3245
3246 var loadErr error
3247 if nil != tx {
3248 tree, loadErr = tx.loadTree(boundBlockID)
3249 } else {
3250 tree, loadErr = LoadTreeByBlockID(boundBlockID)
3251 }
3252 if nil != loadErr {
3253 logging.LogErrorf("load tree [%s] failed: %s", boundBlockID, loadErr)
3254 return loadErr
3255 }
3256 }
3257
3258 var srcContent string
3259 if nil != src["content"] {
3260 srcContent = src["content"].(string)
3261 }
3262 if avErr := addAttributeViewBlock(now, avID, dbBlockID, viewID, groupID, previousItemID, srcItemID, boundBlockID, srcContent, isDetached, ignoreDefaultFill, tree, tx, context); nil != avErr {
3263 return avErr
3264 }
3265 }
3266 return
3267}
3268
3269func addAttributeViewBlock(now int64, avID, dbBlockID, viewID, groupID, previousItemID, addingItemID, addingBoundBlockID, addingBlockContent string, isDetached, ignoreDefaultFill bool, tree *parse.Tree, tx *Transaction, context map[string]interface{}) (err error) {
3270 var node *ast.Node
3271 if !isDetached {
3272 node = treenode.GetNodeInTree(tree, addingBoundBlockID)
3273 if nil == node {
3274 err = ErrBlockNotFound
3275 return
3276 }
3277 } else {
3278 if "" == addingItemID {
3279 addingItemID = ast.NewNodeID()
3280 logging.LogWarnf("detached block id is empty, generate a new one [%s]", addingItemID)
3281 }
3282 }
3283
3284 attrView, err := av.ParseAttributeView(avID)
3285 if err != nil {
3286 return
3287 }
3288
3289 var blockIcon string
3290 if !isDetached {
3291 blockIcon, addingBlockContent = getNodeAvBlockText(node, "")
3292 addingBlockContent = util.UnescapeHTML(addingBlockContent)
3293 }
3294
3295 // 检查是否重复添加相同的块
3296 blockValues := attrView.GetBlockKeyValues()
3297 for _, blockValue := range blockValues.Values {
3298 if "" != addingBoundBlockID && blockValue.Block.ID == addingBoundBlockID {
3299 if !isDetached {
3300 // 重复绑定一下,比如剪切数据库块、取消绑定块后再次添加的场景需要
3301 bindBlockAv0(tx, avID, node, tree)
3302 blockValue.IsDetached = isDetached
3303 blockValue.Block.Icon = blockIcon
3304 blockValue.Block.Content = addingBlockContent
3305 blockValue.UpdatedAt = now
3306 err = av.SaveAttributeView(attrView)
3307 }
3308
3309 msg := fmt.Sprintf(Conf.language(269), getAttrViewName(attrView))
3310 util.PushMsg(msg, 5000)
3311 return
3312 }
3313 }
3314
3315 blockValue := &av.Value{
3316 ID: ast.NewNodeID(),
3317 KeyID: blockValues.Key.ID,
3318 BlockID: addingItemID,
3319 Type: av.KeyTypeBlock,
3320 IsDetached: isDetached,
3321 CreatedAt: now,
3322 UpdatedAt: now,
3323 Block: &av.ValueBlock{Icon: blockIcon, Content: addingBlockContent, Created: now, Updated: now}}
3324 if !isDetached {
3325 blockValue.Block.ID = addingBoundBlockID
3326 }
3327
3328 blockValues.Values = append(blockValues.Values, blockValue)
3329
3330 view, err := getAttrViewViewByBlockID(attrView, dbBlockID)
3331 if nil != err {
3332 logging.LogErrorf("get view by block ID [%s] failed: %s", dbBlockID, err)
3333 return
3334 }
3335
3336 if "" != viewID {
3337 view = attrView.GetView(viewID)
3338 if nil == view {
3339 logging.LogErrorf("get view by view ID [%s] failed", viewID)
3340 return av.ErrViewNotFound
3341 }
3342 }
3343
3344 groupView := view
3345 if "" != groupID {
3346 groupView = view.GetGroupByID(groupID)
3347 }
3348
3349 if !ignoreDefaultFill {
3350 fillDefaultValue(attrView, view, groupView, previousItemID, addingItemID)
3351 }
3352
3353 // 处理日期字段默认填充当前创建时间
3354 // The database date field supports filling the current time by default https://github.com/siyuan-note/siyuan/issues/10823
3355 for _, keyValues := range attrView.KeyValues {
3356 if av.KeyTypeDate == keyValues.Key.Type && nil != keyValues.Key.Date && keyValues.Key.Date.AutoFillNow {
3357 val := keyValues.GetValue(addingItemID)
3358 if nil == val { // 避免覆盖已有值(可能前面已经通过过滤或者分组条件填充了值)
3359 dateVal := &av.Value{
3360 ID: ast.NewNodeID(), KeyID: keyValues.Key.ID, BlockID: addingItemID, Type: av.KeyTypeDate, IsDetached: isDetached, CreatedAt: now, UpdatedAt: now + 1000,
3361 Date: &av.ValueDate{Content: now, IsNotEmpty: true, IsNotTime: !keyValues.Key.Date.FillSpecificTime},
3362 }
3363 keyValues.Values = append(keyValues.Values, dateVal)
3364 } else {
3365 if val.IsRenderAutoFill {
3366 val.CreatedAt, val.UpdatedAt = now, now+1000
3367 val.Date.Content, val.Date.IsNotEmpty, val.Date.IsNotTime = now, true, !keyValues.Key.Date.FillSpecificTime
3368 val.IsRenderAutoFill = false
3369 }
3370 }
3371 }
3372 }
3373
3374 if !isDetached {
3375 bindBlockAv0(tx, avID, node, tree)
3376 }
3377
3378 // 在所有视图上添加项目
3379 for _, v := range attrView.Views {
3380 if "" != previousItemID {
3381 changed := false
3382 for i, id := range v.ItemIDs {
3383 if id == previousItemID {
3384 v.ItemIDs = append(v.ItemIDs[:i+1], append([]string{addingItemID}, v.ItemIDs[i+1:]...)...)
3385 changed = true
3386 break
3387 }
3388 }
3389 if !changed {
3390 v.ItemIDs = append(v.ItemIDs, addingItemID)
3391 }
3392 } else {
3393 v.ItemIDs = append([]string{addingItemID}, v.ItemIDs...)
3394 }
3395
3396 // 在所有分组视图中添加,目的是为了在重新分组的过程中保住排序状态 https://github.com/siyuan-note/siyuan/issues/15560
3397 for _, g := range v.Groups {
3398 if "" != previousItemID {
3399 changed := false
3400 for i, id := range g.GroupItemIDs {
3401 if id == previousItemID {
3402 g.GroupItemIDs = append(g.GroupItemIDs[:i+1], append([]string{addingItemID}, g.GroupItemIDs[i+1:]...)...)
3403 changed = true
3404 break
3405 }
3406 }
3407 if !changed {
3408 g.GroupItemIDs = append(g.GroupItemIDs, addingItemID)
3409 }
3410 } else {
3411 g.GroupItemIDs = append([]string{addingItemID}, g.GroupItemIDs...)
3412 }
3413 }
3414 }
3415
3416 regenAttrViewGroups(attrView)
3417 err = av.SaveAttributeView(attrView)
3418 return
3419}
3420
3421func fillDefaultValue(attrView *av.AttributeView, view, groupView *av.View, previousItemID, addingItemID string) {
3422 defaultValues := getAttrViewAddingBlockDefaultValues(attrView, view, groupView, previousItemID, addingItemID)
3423 for keyID, newValue := range defaultValues {
3424 newValue.BlockID = addingItemID
3425 keyValues, getErr := attrView.GetKeyValues(keyID)
3426 if nil != getErr {
3427 continue
3428 }
3429
3430 if av.KeyTypeRollup == newValue.Type {
3431 // 汇总字段的值是渲染时计算的,不需要添加到数据存储中
3432 continue
3433 }
3434
3435 if (av.KeyTypeSelect == newValue.Type || av.KeyTypeMSelect == newValue.Type) && 1 > len(newValue.MSelect) && groupValueDefault != groupView.GetGroupValue() {
3436 // 单选或多选类型的值可能需要从分组条件中获取默认值
3437 if opt := keyValues.Key.GetOption(groupView.GetGroupValue()); nil != opt {
3438 newValue.MSelect = append(newValue.MSelect, &av.ValueSelect{Content: opt.Name, Color: opt.Color})
3439 }
3440 }
3441
3442 if av.KeyTypeRelation == newValue.Type && nil != keyValues.Key.Relation && keyValues.Key.Relation.IsTwoWay {
3443 // 双向关联需要同时更新目标字段的值
3444 updateTwoWayRelationDestAttrView(attrView, keyValues.Key, newValue, 1, []string{})
3445 }
3446
3447 existingVal := keyValues.GetValue(addingItemID)
3448 if nil == existingVal {
3449 newValue.IsRenderAutoFill = false
3450 keyValues.Values = append(keyValues.Values, newValue)
3451 } else {
3452 newValueRaw := newValue.GetValByType(keyValues.Key.Type)
3453 if av.KeyTypeBlock != existingVal.Type || (av.KeyTypeBlock == existingVal.Type && existingVal.IsDetached) {
3454 // 非主键的值直接覆盖,主键的值只覆盖非绑定块
3455 existingVal.IsRenderAutoFill = false
3456 existingVal.SetValByType(keyValues.Key.Type, newValueRaw)
3457 }
3458 }
3459 }
3460}
3461
3462func getNewValueByNearItem(nearItem av.Item, key *av.Key, addingBlockID string) (ret *av.Value) {
3463 if nil == nearItem {
3464 return
3465 }
3466
3467 defaultVal := nearItem.GetValue(key.ID)
3468 ret = defaultVal.Clone()
3469 ret.ID = ast.NewNodeID()
3470 ret.KeyID = key.ID
3471 ret.BlockID = addingBlockID
3472 ret.CreatedAt = util.CurrentTimeMillis()
3473 ret.UpdatedAt = ret.CreatedAt + 1000
3474 return
3475}
3476
3477func getNearItem(attrView *av.AttributeView, view, groupView *av.View, previousItemID string) (ret av.Item) {
3478 cachedAttrViews := map[string]*av.AttributeView{}
3479 rollupFurtherCollections := sql.GetFurtherCollections(attrView, cachedAttrViews)
3480 viewable := sql.RenderGroupView(attrView, view, groupView, "")
3481 av.Filter(viewable, attrView, rollupFurtherCollections, cachedAttrViews)
3482 av.Sort(viewable, attrView)
3483 items := viewable.(av.Collection).GetItems()
3484 if 0 < len(items) {
3485 if "" != previousItemID {
3486 for _, row := range items {
3487 if row.GetID() == previousItemID {
3488 ret = row
3489 return
3490 }
3491 }
3492 } else {
3493 if 0 < len(items) {
3494 ret = items[0]
3495 return
3496 }
3497 }
3498 }
3499 return
3500}
3501
3502func (tx *Transaction) doRemoveAttrViewBlock(operation *Operation) (ret *TxErr) {
3503 err := removeAttributeViewBlock(operation.SrcIDs, operation.AvID, tx)
3504 if err != nil {
3505 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID}
3506 }
3507 return
3508}
3509
3510func RemoveAttributeViewBlock(srcIDs []string, avID string) (err error) {
3511 err = removeAttributeViewBlock(srcIDs, avID, nil)
3512 return
3513}
3514
3515func removeAttributeViewBlock(srcIDs []string, avID string, tx *Transaction) (err error) {
3516 attrView, err := av.ParseAttributeView(avID)
3517 if err != nil {
3518 return
3519 }
3520
3521 trees := map[string]*parse.Tree{}
3522 for _, keyValues := range attrView.KeyValues {
3523 tmp := keyValues.Values[:0]
3524 for i, val := range keyValues.Values {
3525 if !gulu.Str.Contains(val.BlockID, srcIDs) {
3526 tmp = append(tmp, keyValues.Values[i])
3527 } else {
3528 // Remove av block also remove node attr https://github.com/siyuan-note/siyuan/issues/9091#issuecomment-1709824006
3529 if !val.IsDetached && nil != val.Block {
3530 if bt := treenode.GetBlockTree(val.Block.ID); nil != bt {
3531 tree := trees[bt.RootID]
3532 if nil == tree {
3533 tree, _ = LoadTreeByBlockID(val.Block.ID)
3534 }
3535
3536 if nil != tree {
3537 trees[bt.RootID] = tree
3538 if node := treenode.GetNodeInTree(tree, val.Block.ID); nil != node {
3539 if err = removeNodeAvID(node, avID, tx, tree); err != nil {
3540 return
3541 }
3542 }
3543 }
3544 }
3545 }
3546 }
3547 }
3548 keyValues.Values = tmp
3549 }
3550
3551 for _, view := range attrView.Views {
3552 for _, blockID := range srcIDs {
3553 view.ItemIDs = gulu.Str.RemoveElem(view.ItemIDs, blockID)
3554 }
3555 }
3556
3557 regenAttrViewGroups(attrView)
3558
3559 err = av.SaveAttributeView(attrView)
3560 if nil != err {
3561 return
3562 }
3563
3564 refreshRelatedSrcAvs(avID)
3565
3566 historyDir, err := GetHistoryDir(HistoryOpUpdate)
3567 if err != nil {
3568 logging.LogErrorf("get history dir failed: %s", err)
3569 return
3570 }
3571 blockIDs := treenode.GetMirrorAttrViewBlockIDs(avID)
3572 for _, blockID := range blockIDs {
3573 tree := trees[blockID]
3574 if nil == tree {
3575 tree, _ = LoadTreeByBlockID(blockID)
3576 }
3577 if nil == tree {
3578 continue
3579 }
3580
3581 historyPath := filepath.Join(historyDir, tree.Box, tree.Path)
3582 absPath := filepath.Join(util.DataDir, tree.Box, tree.Path)
3583 if err = filelock.Copy(absPath, historyPath); err != nil {
3584 logging.LogErrorf("backup [path=%s] to history [%s] failed: %s", absPath, historyPath, err)
3585 return
3586 }
3587 }
3588
3589 srcAvPath := filepath.Join(util.DataDir, "storage", "av", avID+".json")
3590 destAvPath := filepath.Join(historyDir, "storage", "av", avID+".json")
3591 if copyErr := filelock.Copy(srcAvPath, destAvPath); nil != copyErr {
3592 logging.LogErrorf("copy av [%s] failed: %s", srcAvPath, copyErr)
3593 }
3594
3595 indexHistoryDir(filepath.Base(historyDir), util.NewLute())
3596 return
3597}
3598
3599func removeNodeAvID(node *ast.Node, avID string, tx *Transaction, tree *parse.Tree) (err error) {
3600 attrs := parse.IAL2Map(node.KramdownIAL)
3601 if ast.NodeDocument == node.Type {
3602 delete(attrs, "custom-hidden")
3603 node.RemoveIALAttr("custom-hidden")
3604 }
3605
3606 if avs := attrs[av.NodeAttrNameAvs]; "" != avs {
3607 avIDs := strings.Split(avs, ",")
3608 avIDs = gulu.Str.RemoveElem(avIDs, avID)
3609 var existAvIDs []string
3610 for _, attributeViewID := range avIDs {
3611 if av.IsAttributeViewExist(attributeViewID) {
3612 existAvIDs = append(existAvIDs, attributeViewID)
3613 }
3614 }
3615 avIDs = existAvIDs
3616
3617 if 0 == len(avIDs) {
3618 attrs[av.NodeAttrNameAvs] = ""
3619 } else {
3620 attrs[av.NodeAttrNameAvs] = strings.Join(avIDs, ",")
3621 node.SetIALAttr(av.NodeAttrNameAvs, strings.Join(avIDs, ","))
3622 avNames := getAvNames(node.IALAttr(av.NodeAttrNameAvs))
3623 attrs[av.NodeAttrViewNames] = avNames
3624 }
3625 }
3626
3627 if nil != tx {
3628 if err = setNodeAttrsWithTx(tx, node, tree, attrs); err != nil {
3629 return
3630 }
3631 } else {
3632 if err = setNodeAttrs(node, tree, attrs); err != nil {
3633 return
3634 }
3635 }
3636 return
3637}
3638
3639func (tx *Transaction) doDuplicateAttrViewKey(operation *Operation) (ret *TxErr) {
3640 err := duplicateAttributeViewKey(operation)
3641 if err != nil {
3642 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
3643 }
3644 return
3645}
3646
3647func duplicateAttributeViewKey(operation *Operation) (err error) {
3648 attrView, err := av.ParseAttributeView(operation.AvID)
3649 if err != nil {
3650 return
3651 }
3652
3653 key, _ := attrView.GetKey(operation.KeyID)
3654 if nil == key {
3655 return
3656 }
3657
3658 if av.KeyTypeBlock == key.Type || av.KeyTypeRelation == key.Type {
3659 return
3660 }
3661
3662 copyKey := &av.Key{}
3663 if err = copier.Copy(copyKey, key); err != nil {
3664 logging.LogErrorf("clone key failed: %s", err)
3665 }
3666 copyKey.ID = operation.NextID
3667 copyKey.Name = util.GetDuplicateName(key.Name)
3668
3669 attrView.KeyValues = append(attrView.KeyValues, &av.KeyValues{Key: copyKey})
3670
3671 for _, view := range attrView.Views {
3672 switch view.LayoutType {
3673 case av.LayoutTypeTable:
3674 for i, column := range view.Table.Columns {
3675 if column.ID == key.ID {
3676 view.Table.Columns = append(view.Table.Columns[:i+1], append([]*av.ViewTableColumn{
3677 {
3678 BaseField: &av.BaseField{
3679 ID: copyKey.ID,
3680 Wrap: column.Wrap,
3681 Hidden: column.Hidden,
3682 Desc: column.Desc,
3683 },
3684 Pin: column.Pin,
3685 Width: column.Width,
3686 },
3687 }, view.Table.Columns[i+1:]...)...)
3688 break
3689 }
3690 }
3691 case av.LayoutTypeGallery:
3692 for i, field := range view.Gallery.CardFields {
3693 if field.ID == key.ID {
3694 view.Gallery.CardFields = append(view.Gallery.CardFields[:i+1], append([]*av.ViewGalleryCardField{
3695 {
3696 BaseField: &av.BaseField{
3697 ID: copyKey.ID,
3698 Wrap: field.Wrap,
3699 Hidden: field.Hidden,
3700 Desc: field.Desc,
3701 },
3702 },
3703 }, view.Gallery.CardFields[i+1:]...)...)
3704 break
3705 }
3706 }
3707 case av.LayoutTypeKanban:
3708 for i, field := range view.Kanban.Fields {
3709 if field.ID == key.ID {
3710 view.Kanban.Fields = append(view.Kanban.Fields[:i+1], append([]*av.ViewKanbanField{
3711 {
3712 BaseField: &av.BaseField{
3713 ID: copyKey.ID,
3714 Wrap: field.Wrap,
3715 Hidden: field.Hidden,
3716 Desc: field.Desc,
3717 },
3718 },
3719 }, view.Kanban.Fields[i+1:]...)...)
3720 break
3721 }
3722 }
3723 }
3724 }
3725
3726 err = av.SaveAttributeView(attrView)
3727 return
3728}
3729
3730func (tx *Transaction) doSetAttrViewColumnWidth(operation *Operation) (ret *TxErr) {
3731 err := setAttributeViewColWidth(operation)
3732 if err != nil {
3733 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
3734 }
3735 return
3736}
3737
3738func setAttributeViewColWidth(operation *Operation) (err error) {
3739 attrView, err := av.ParseAttributeView(operation.AvID)
3740 if err != nil {
3741 return
3742 }
3743
3744 view, err := getAttrViewViewByBlockID(attrView, operation.BlockID)
3745 if err != nil {
3746 return
3747 }
3748
3749 switch view.LayoutType {
3750 case av.LayoutTypeTable:
3751 for _, column := range view.Table.Columns {
3752 if column.ID == operation.ID {
3753 column.Width = operation.Data.(string)
3754 break
3755 }
3756 }
3757 case av.LayoutTypeGallery, av.LayoutTypeKanban:
3758 return
3759 }
3760
3761 err = av.SaveAttributeView(attrView)
3762 return
3763}
3764
3765func (tx *Transaction) doSetAttrViewColumnWrap(operation *Operation) (ret *TxErr) {
3766 err := setAttributeViewColWrap(operation)
3767 if err != nil {
3768 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
3769 }
3770 return
3771}
3772
3773func setAttributeViewColWrap(operation *Operation) (err error) {
3774 attrView, err := av.ParseAttributeView(operation.AvID)
3775 if err != nil {
3776 return
3777 }
3778
3779 view, err := getAttrViewViewByBlockID(attrView, operation.BlockID)
3780 if err != nil {
3781 return
3782 }
3783
3784 newWrap := operation.Data.(bool)
3785 allFieldWrap := true
3786 switch view.LayoutType {
3787 case av.LayoutTypeTable:
3788 for _, column := range view.Table.Columns {
3789 if column.ID == operation.ID {
3790 column.Wrap = newWrap
3791 }
3792 allFieldWrap = allFieldWrap && column.Wrap
3793 }
3794 view.Table.WrapField = allFieldWrap
3795 case av.LayoutTypeGallery:
3796 for _, field := range view.Gallery.CardFields {
3797 if field.ID == operation.ID {
3798 field.Wrap = newWrap
3799 }
3800 allFieldWrap = allFieldWrap && field.Wrap
3801 }
3802 view.Gallery.WrapField = allFieldWrap
3803 case av.LayoutTypeKanban:
3804 for _, field := range view.Kanban.Fields {
3805 if field.ID == operation.ID {
3806 field.Wrap = newWrap
3807 }
3808 allFieldWrap = allFieldWrap && field.Wrap
3809 }
3810 view.Kanban.WrapField = allFieldWrap
3811 }
3812
3813 err = av.SaveAttributeView(attrView)
3814 return
3815}
3816
3817func (tx *Transaction) doSetAttrViewColumnHidden(operation *Operation) (ret *TxErr) {
3818 err := setAttributeViewColHidden(operation)
3819 if err != nil {
3820 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
3821 }
3822 return
3823}
3824
3825func setAttributeViewColHidden(operation *Operation) (err error) {
3826 attrView, err := av.ParseAttributeView(operation.AvID)
3827 if err != nil {
3828 return
3829 }
3830
3831 view, err := getAttrViewViewByBlockID(attrView, operation.BlockID)
3832 if err != nil {
3833 return
3834 }
3835
3836 switch view.LayoutType {
3837 case av.LayoutTypeTable:
3838 for _, column := range view.Table.Columns {
3839 if column.ID == operation.ID {
3840 column.Hidden = operation.Data.(bool)
3841 break
3842 }
3843 }
3844 case av.LayoutTypeGallery:
3845 for _, field := range view.Gallery.CardFields {
3846 if field.ID == operation.ID {
3847 field.Hidden = operation.Data.(bool)
3848 break
3849 }
3850 }
3851 case av.LayoutTypeKanban:
3852 for _, field := range view.Kanban.Fields {
3853 if field.ID == operation.ID {
3854 field.Hidden = operation.Data.(bool)
3855 break
3856 }
3857 }
3858 }
3859
3860 err = av.SaveAttributeView(attrView)
3861 return
3862}
3863
3864func (tx *Transaction) doSetAttrViewColumnPin(operation *Operation) (ret *TxErr) {
3865 err := setAttributeViewColPin(operation)
3866 if err != nil {
3867 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
3868 }
3869 return
3870}
3871
3872func setAttributeViewColPin(operation *Operation) (err error) {
3873 attrView, err := av.ParseAttributeView(operation.AvID)
3874 if err != nil {
3875 return
3876 }
3877
3878 view, err := getAttrViewViewByBlockID(attrView, operation.BlockID)
3879 if err != nil {
3880 return
3881 }
3882
3883 switch view.LayoutType {
3884 case av.LayoutTypeTable:
3885 for _, column := range view.Table.Columns {
3886 if column.ID == operation.ID {
3887 column.Pin = operation.Data.(bool)
3888 break
3889 }
3890 }
3891 case av.LayoutTypeGallery, av.LayoutTypeKanban:
3892 return
3893 }
3894
3895 err = av.SaveAttributeView(attrView)
3896 return
3897}
3898
3899func (tx *Transaction) doSetAttrViewColumnIcon(operation *Operation) (ret *TxErr) {
3900 err := setAttributeViewColIcon(operation)
3901 if err != nil {
3902 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
3903 }
3904 return
3905}
3906
3907func setAttributeViewColIcon(operation *Operation) (err error) {
3908 attrView, err := av.ParseAttributeView(operation.AvID)
3909 if err != nil {
3910 return
3911 }
3912
3913 for _, keyValues := range attrView.KeyValues {
3914 if keyValues.Key.ID == operation.ID {
3915 keyValues.Key.Icon = operation.Data.(string)
3916 break
3917 }
3918 }
3919
3920 err = av.SaveAttributeView(attrView)
3921 return
3922}
3923
3924func (tx *Transaction) doSetAttrViewColumnDesc(operation *Operation) (ret *TxErr) {
3925 err := setAttributeViewColDesc(operation)
3926 if err != nil {
3927 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
3928 }
3929 return
3930}
3931
3932func setAttributeViewColDesc(operation *Operation) (err error) {
3933 attrView, err := av.ParseAttributeView(operation.AvID)
3934 if err != nil {
3935 return
3936 }
3937
3938 for _, keyValues := range attrView.KeyValues {
3939 if keyValues.Key.ID == operation.ID {
3940 keyValues.Key.Desc = operation.Data.(string)
3941 break
3942 }
3943 }
3944
3945 err = av.SaveAttributeView(attrView)
3946 return
3947}
3948
3949func (tx *Transaction) doSortAttrViewRow(operation *Operation) (ret *TxErr) {
3950 err := sortAttributeViewRow(operation)
3951 if err != nil {
3952 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
3953 }
3954 return
3955}
3956
3957func sortAttributeViewRow(operation *Operation) (err error) {
3958 if operation.ID == operation.PreviousID {
3959 // 拖拽到自己的下方,不做任何操作 https://github.com/siyuan-note/siyuan/issues/11048
3960 return
3961 }
3962
3963 attrView, err := av.ParseAttributeView(operation.AvID)
3964 if err != nil {
3965 return
3966 }
3967
3968 view, err := getAttrViewViewByBlockID(attrView, operation.BlockID)
3969 if err != nil {
3970 return
3971 }
3972
3973 var itemID string
3974 var idx, previousIndex int
3975
3976 if nil != view.Group && "" != operation.GroupID {
3977 if groupView := view.GetGroupByID(operation.GroupID); nil != groupView {
3978 groupKey := view.GetGroupKey(attrView)
3979 isAcrossGroup := operation.GroupID != operation.TargetGroupID
3980 if isAcrossGroup && (av.KeyTypeTemplate == groupKey.Type || av.KeyTypeCreated == groupKey.Type || av.KeyTypeUpdated == groupKey.Type) {
3981 // 这些字段类型不支持跨分组移动,因为它们的值是自动计算生成的
3982 return
3983 }
3984
3985 for i, id := range groupView.GroupItemIDs {
3986 if id == operation.ID {
3987 itemID = id
3988 idx = i
3989 break
3990 }
3991 }
3992 if "" == itemID {
3993 itemID = operation.ID
3994 groupView.GroupItemIDs = append(groupView.GroupItemIDs, itemID)
3995 idx = len(groupView.GroupItemIDs) - 1
3996 }
3997 groupView.GroupItemIDs = append(groupView.GroupItemIDs[:idx], groupView.GroupItemIDs[idx+1:]...)
3998
3999 if isAcrossGroup {
4000 if targetGroupView := view.GetGroupByID(operation.TargetGroupID); nil != targetGroupView && !gulu.Str.Contains(itemID, targetGroupView.GroupItemIDs) {
4001 fillDefaultValue(attrView, view, targetGroupView, operation.PreviousID, itemID)
4002
4003 for i, r := range targetGroupView.GroupItemIDs {
4004 if r == operation.PreviousID {
4005 previousIndex = i + 1
4006 break
4007 }
4008 }
4009 targetGroupView.GroupItemIDs = util.InsertElem(targetGroupView.GroupItemIDs, previousIndex, itemID)
4010 }
4011
4012 regenAttrViewGroups(attrView)
4013 } else { // 同分组内排序
4014 for i, r := range groupView.GroupItemIDs {
4015 if r == operation.PreviousID {
4016 previousIndex = i + 1
4017 break
4018 }
4019 }
4020 groupView.GroupItemIDs = util.InsertElem(groupView.GroupItemIDs, previousIndex, itemID)
4021 }
4022 }
4023 } else {
4024 for i, id := range view.ItemIDs {
4025 if id == operation.ID {
4026 itemID = id
4027 idx = i
4028 break
4029 }
4030 }
4031 if "" == itemID {
4032 itemID = operation.ID
4033 view.ItemIDs = append(view.ItemIDs, itemID)
4034 idx = len(view.ItemIDs) - 1
4035 }
4036
4037 view.ItemIDs = append(view.ItemIDs[:idx], view.ItemIDs[idx+1:]...)
4038 for i, r := range view.ItemIDs {
4039 if r == operation.PreviousID {
4040 previousIndex = i + 1
4041 break
4042 }
4043 }
4044 view.ItemIDs = util.InsertElem(view.ItemIDs, previousIndex, itemID)
4045 }
4046
4047 err = av.SaveAttributeView(attrView)
4048 return
4049}
4050
4051func (tx *Transaction) doSortAttrViewColumn(operation *Operation) (ret *TxErr) {
4052 err := SortAttributeViewViewKey(operation.AvID, operation.BlockID, operation.ID, operation.PreviousID)
4053 if err != nil {
4054 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
4055 }
4056 return
4057}
4058
4059func SortAttributeViewViewKey(avID, blockID, keyID, previousKeyID string) (err error) {
4060 if keyID == previousKeyID {
4061 // 拖拽到自己的右侧,不做任何操作 https://github.com/siyuan-note/siyuan/issues/11048
4062 return
4063 }
4064
4065 attrView, err := av.ParseAttributeView(avID)
4066 if err != nil {
4067 return
4068 }
4069
4070 view, err := getAttrViewViewByBlockID(attrView, blockID)
4071 if err != nil {
4072 return
4073 }
4074
4075 var curIndex, previousIndex int
4076 switch view.LayoutType {
4077 case av.LayoutTypeTable:
4078 var col *av.ViewTableColumn
4079 for i, column := range view.Table.Columns {
4080 if column.ID == keyID {
4081 col = column
4082 curIndex = i
4083 break
4084 }
4085 }
4086 if nil == col {
4087 return
4088 }
4089
4090 view.Table.Columns = append(view.Table.Columns[:curIndex], view.Table.Columns[curIndex+1:]...)
4091 for i, column := range view.Table.Columns {
4092 if column.ID == previousKeyID {
4093 previousIndex = i + 1
4094 break
4095 }
4096 }
4097 view.Table.Columns = util.InsertElem(view.Table.Columns, previousIndex, col)
4098 case av.LayoutTypeGallery:
4099 var field *av.ViewGalleryCardField
4100 for i, cardField := range view.Gallery.CardFields {
4101 if cardField.ID == keyID {
4102 field = cardField
4103 curIndex = i
4104 break
4105 }
4106 }
4107 if nil == field {
4108 return
4109 }
4110
4111 view.Gallery.CardFields = append(view.Gallery.CardFields[:curIndex], view.Gallery.CardFields[curIndex+1:]...)
4112 for i, cardField := range view.Gallery.CardFields {
4113 if cardField.ID == previousKeyID {
4114 previousIndex = i + 1
4115 break
4116 }
4117 }
4118 view.Gallery.CardFields = util.InsertElem(view.Gallery.CardFields, previousIndex, field)
4119 case av.LayoutTypeKanban:
4120 var field *av.ViewKanbanField
4121 for i, kanbanField := range view.Kanban.Fields {
4122 if kanbanField.ID == keyID {
4123 field = kanbanField
4124 curIndex = i
4125 break
4126 }
4127 }
4128 if nil == field {
4129 return
4130 }
4131
4132 view.Kanban.Fields = append(view.Kanban.Fields[:curIndex], view.Kanban.Fields[curIndex+1:]...)
4133 for i, kanbanField := range view.Kanban.Fields {
4134 if kanbanField.ID == previousKeyID {
4135 previousIndex = i + 1
4136 break
4137 }
4138 }
4139 view.Kanban.Fields = util.InsertElem(view.Kanban.Fields, previousIndex, field)
4140 }
4141
4142 err = av.SaveAttributeView(attrView)
4143 return
4144}
4145
4146func (tx *Transaction) doSortAttrViewKey(operation *Operation) (ret *TxErr) {
4147 err := SortAttributeViewKey(operation.AvID, operation.ID, operation.PreviousID)
4148 if err != nil {
4149 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
4150 }
4151 return
4152}
4153
4154func SortAttributeViewKey(avID, keyID, previousKeyID string) (err error) {
4155 if keyID == previousKeyID {
4156 return
4157 }
4158
4159 attrView, err := av.ParseAttributeView(avID)
4160 if err != nil {
4161 return
4162 }
4163
4164 refreshAttrViewKeyIDs(attrView, false)
4165
4166 var currentKeyID string
4167 var idx, previousIndex int
4168 for i, k := range attrView.KeyIDs {
4169 if k == keyID {
4170 currentKeyID = k
4171 idx = i
4172 break
4173 }
4174 }
4175 if "" == currentKeyID {
4176 return
4177 }
4178
4179 attrView.KeyIDs = append(attrView.KeyIDs[:idx], attrView.KeyIDs[idx+1:]...)
4180
4181 for i, k := range attrView.KeyIDs {
4182 if k == previousKeyID {
4183 previousIndex = i + 1
4184 break
4185 }
4186 }
4187 attrView.KeyIDs = util.InsertElem(attrView.KeyIDs, previousIndex, currentKeyID)
4188
4189 err = av.SaveAttributeView(attrView)
4190 return
4191}
4192
4193func refreshAttrViewKeyIDs(attrView *av.AttributeView, needSave bool) {
4194 // 订正 keyIDs 数据
4195
4196 existKeyIDs := map[string]bool{}
4197 for _, keyValues := range attrView.KeyValues {
4198 existKeyIDs[keyValues.Key.ID] = true
4199 }
4200
4201 for k, _ := range existKeyIDs {
4202 if !gulu.Str.Contains(k, attrView.KeyIDs) {
4203 attrView.KeyIDs = append(attrView.KeyIDs, k)
4204 }
4205 }
4206
4207 var tmp []string
4208 for _, k := range attrView.KeyIDs {
4209 if ok := existKeyIDs[k]; ok {
4210 tmp = append(tmp, k)
4211 }
4212 }
4213 attrView.KeyIDs = tmp
4214
4215 if needSave {
4216 av.SaveAttributeView(attrView)
4217 }
4218}
4219
4220func (tx *Transaction) doAddAttrViewColumn(operation *Operation) (ret *TxErr) {
4221 var icon string
4222 if nil != operation.Data {
4223 icon = operation.Data.(string)
4224 }
4225 err := AddAttributeViewKey(operation.AvID, operation.ID, operation.Name, operation.Typ, icon, operation.PreviousID)
4226
4227 if err != nil {
4228 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
4229 }
4230 return
4231}
4232
4233func AddAttributeViewKey(avID, keyID, keyName, keyType, keyIcon, previousKeyID string) (err error) {
4234 attrView, err := av.ParseAttributeView(avID)
4235 if err != nil {
4236 return
4237 }
4238
4239 currentView, err := attrView.GetCurrentView(attrView.ViewID)
4240 if nil != err {
4241 return
4242 }
4243
4244 keyTyp := av.KeyType(keyType)
4245 switch keyTyp {
4246 case av.KeyTypeText, av.KeyTypeNumber, av.KeyTypeDate, av.KeyTypeSelect, av.KeyTypeMSelect, av.KeyTypeURL, av.KeyTypeEmail,
4247 av.KeyTypePhone, av.KeyTypeMAsset, av.KeyTypeTemplate, av.KeyTypeCreated, av.KeyTypeUpdated, av.KeyTypeCheckbox,
4248 av.KeyTypeRelation, av.KeyTypeRollup, av.KeyTypeLineNumber:
4249
4250 key := av.NewKey(keyID, keyName, keyIcon, keyTyp)
4251 if av.KeyTypeRollup == keyTyp {
4252 key.Rollup = &av.Rollup{Calc: &av.RollupCalc{Operator: av.CalcOperatorNone}}
4253 }
4254
4255 attrView.KeyValues = append(attrView.KeyValues, &av.KeyValues{Key: key})
4256
4257 for _, view := range attrView.Views {
4258 newField := &av.BaseField{ID: key.ID}
4259 if nil != view.Table {
4260 newField.Wrap = view.Table.WrapField
4261
4262 if "" == previousKeyID {
4263 if av.LayoutTypeGallery == currentView.LayoutType || av.LayoutTypeKanban == currentView.LayoutType {
4264 // 如果当前视图是卡片或看板视图则添加到最后
4265 view.Table.Columns = append(view.Table.Columns, &av.ViewTableColumn{BaseField: newField})
4266 } else {
4267 view.Table.Columns = append([]*av.ViewTableColumn{{BaseField: newField}}, view.Table.Columns...)
4268 }
4269 } else {
4270 added := false
4271 for i, column := range view.Table.Columns {
4272 if column.ID == previousKeyID {
4273 view.Table.Columns = append(view.Table.Columns[:i+1], append([]*av.ViewTableColumn{{BaseField: newField}}, view.Table.Columns[i+1:]...)...)
4274 added = true
4275 break
4276 }
4277 }
4278 if !added {
4279 view.Table.Columns = append(view.Table.Columns, &av.ViewTableColumn{BaseField: newField})
4280 }
4281 }
4282 }
4283
4284 if nil != view.Gallery {
4285 newField.Wrap = view.Gallery.WrapField
4286
4287 if "" == previousKeyID {
4288 view.Gallery.CardFields = append(view.Gallery.CardFields, &av.ViewGalleryCardField{BaseField: newField})
4289 } else {
4290 added := false
4291 for i, field := range view.Gallery.CardFields {
4292 if field.ID == previousKeyID {
4293 view.Gallery.CardFields = append(view.Gallery.CardFields[:i+1], append([]*av.ViewGalleryCardField{{BaseField: newField}}, view.Gallery.CardFields[i+1:]...)...)
4294 added = true
4295 break
4296 }
4297 }
4298 if !added {
4299 view.Gallery.CardFields = append(view.Gallery.CardFields, &av.ViewGalleryCardField{BaseField: newField})
4300 }
4301 }
4302 }
4303
4304 if nil != view.Kanban {
4305 newField.Wrap = view.Kanban.WrapField
4306
4307 if "" == previousKeyID {
4308 view.Kanban.Fields = append(view.Kanban.Fields, &av.ViewKanbanField{BaseField: newField})
4309 } else {
4310 added := false
4311 for i, field := range view.Kanban.Fields {
4312 if field.ID == previousKeyID {
4313 view.Kanban.Fields = append(view.Kanban.Fields[:i+1], append([]*av.ViewKanbanField{{BaseField: newField}}, view.Kanban.Fields[i+1:]...)...)
4314 added = true
4315 break
4316 }
4317 }
4318 if !added {
4319 view.Kanban.Fields = append(view.Kanban.Fields, &av.ViewKanbanField{BaseField: newField})
4320 }
4321 }
4322 }
4323 }
4324 }
4325
4326 err = av.SaveAttributeView(attrView)
4327 return
4328}
4329
4330func (tx *Transaction) doUpdateAttrViewColTemplate(operation *Operation) (ret *TxErr) {
4331 err := updateAttributeViewColTemplate(operation)
4332 if err != nil {
4333 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
4334 }
4335 return
4336}
4337
4338func updateAttributeViewColTemplate(operation *Operation) (err error) {
4339 attrView, err := av.ParseAttributeView(operation.AvID)
4340 if err != nil {
4341 return
4342 }
4343
4344 colType := av.KeyType(operation.Typ)
4345 switch colType {
4346 case av.KeyTypeTemplate:
4347 for _, keyValues := range attrView.KeyValues {
4348 if keyValues.Key.ID == operation.ID && av.KeyTypeTemplate == keyValues.Key.Type {
4349 keyValues.Key.Template = operation.Data.(string)
4350 break
4351 }
4352 }
4353 }
4354
4355 regenAttrViewGroups(attrView)
4356 err = av.SaveAttributeView(attrView)
4357 return
4358}
4359
4360func (tx *Transaction) doUpdateAttrViewColNumberFormat(operation *Operation) (ret *TxErr) {
4361 err := updateAttributeViewColNumberFormat(operation)
4362 if err != nil {
4363 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
4364 }
4365 return
4366}
4367
4368func updateAttributeViewColNumberFormat(operation *Operation) (err error) {
4369 attrView, err := av.ParseAttributeView(operation.AvID)
4370 if err != nil {
4371 return
4372 }
4373
4374 colType := av.KeyType(operation.Typ)
4375 switch colType {
4376 case av.KeyTypeNumber:
4377 for _, keyValues := range attrView.KeyValues {
4378 if keyValues.Key.ID == operation.ID && av.KeyTypeNumber == keyValues.Key.Type {
4379 keyValues.Key.NumberFormat = av.NumberFormat(operation.Format)
4380 break
4381 }
4382 }
4383 }
4384
4385 err = av.SaveAttributeView(attrView)
4386 return
4387}
4388
4389func (tx *Transaction) doUpdateAttrViewColumn(operation *Operation) (ret *TxErr) {
4390 err := updateAttributeViewColumn(operation)
4391 if err != nil {
4392 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
4393 }
4394 return
4395}
4396
4397func updateAttributeViewColumn(operation *Operation) (err error) {
4398 attrView, err := av.ParseAttributeView(operation.AvID)
4399 if err != nil {
4400 return
4401 }
4402
4403 colType := av.KeyType(operation.Typ)
4404 changeType := false
4405 switch colType {
4406 case av.KeyTypeBlock, av.KeyTypeText, av.KeyTypeNumber, av.KeyTypeDate, av.KeyTypeSelect, av.KeyTypeMSelect, av.KeyTypeURL, av.KeyTypeEmail,
4407 av.KeyTypePhone, av.KeyTypeMAsset, av.KeyTypeTemplate, av.KeyTypeCreated, av.KeyTypeUpdated, av.KeyTypeCheckbox,
4408 av.KeyTypeRelation, av.KeyTypeRollup, av.KeyTypeLineNumber:
4409 for _, keyValues := range attrView.KeyValues {
4410 if keyValues.Key.ID == operation.ID {
4411 keyValues.Key.Name = strings.TrimSpace(operation.Name)
4412
4413 changeType = keyValues.Key.Type != colType
4414 keyValues.Key.Type = colType
4415
4416 for _, value := range keyValues.Values {
4417 value.Type = colType
4418 }
4419
4420 break
4421 }
4422 }
4423 }
4424
4425 if changeType {
4426 for _, view := range attrView.Views {
4427 if nil != view.Group {
4428 if groupKey := view.GetGroupKey(attrView); nil != groupKey && groupKey.ID == operation.ID {
4429 removeAttributeViewGroup0(view)
4430 }
4431 }
4432 }
4433 }
4434
4435 if err = av.SaveAttributeView(attrView); nil != err {
4436 return
4437 }
4438
4439 if changeType {
4440 relatedAvIDs := av.GetSrcAvIDs(attrView.ID)
4441 for _, relatedAvID := range relatedAvIDs {
4442 destAv, _ := av.ParseAttributeView(relatedAvID)
4443 if nil == destAv {
4444 continue
4445 }
4446
4447 for _, keyValues := range destAv.KeyValues {
4448 if av.KeyTypeRollup == keyValues.Key.Type && keyValues.Key.Rollup.KeyID == operation.ID {
4449 // 置空关联过来的汇总
4450 for _, val := range keyValues.Values {
4451 val.Rollup.Contents = nil
4452 }
4453 keyValues.Key.Rollup.Calc = &av.RollupCalc{Operator: av.CalcOperatorNone}
4454 }
4455 }
4456
4457 regenAttrViewGroups(destAv)
4458 av.SaveAttributeView(destAv)
4459 ReloadAttrView(destAv.ID)
4460 }
4461 }
4462 return
4463}
4464
4465func (tx *Transaction) doRemoveAttrViewColumn(operation *Operation) (ret *TxErr) {
4466 err := RemoveAttributeViewKey(operation.AvID, operation.ID, operation.RemoveDest)
4467 if err != nil {
4468 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
4469 }
4470 return
4471}
4472
4473func RemoveAttributeViewKey(avID, keyID string, removeRelationDest bool) (err error) {
4474 attrView, err := av.ParseAttributeView(avID)
4475 if err != nil {
4476 return
4477 }
4478
4479 var removedKey *av.Key
4480 for i, keyValues := range attrView.KeyValues {
4481 if keyValues.Key.ID == keyID {
4482 attrView.KeyValues = append(attrView.KeyValues[:i], attrView.KeyValues[i+1:]...)
4483 removedKey = keyValues.Key
4484 break
4485 }
4486 }
4487
4488 if nil != removedKey && av.KeyTypeRelation == removedKey.Type && nil != removedKey.Relation {
4489 if removedKey.Relation.IsTwoWay {
4490 var destAv *av.AttributeView
4491 if avID == removedKey.Relation.AvID {
4492 destAv = attrView
4493 } else {
4494 destAv, _ = av.ParseAttributeView(removedKey.Relation.AvID)
4495 }
4496
4497 if nil != destAv {
4498 oldDestKey, _ := destAv.GetKey(removedKey.Relation.BackKeyID)
4499 if nil != oldDestKey && nil != oldDestKey.Relation && oldDestKey.Relation.AvID == attrView.ID && oldDestKey.Relation.IsTwoWay {
4500 oldDestKey.Relation.IsTwoWay = false
4501 oldDestKey.Relation.BackKeyID = ""
4502 }
4503
4504 destAvRelSrcAv := false
4505 for i, keyValues := range destAv.KeyValues {
4506 if keyValues.Key.ID == removedKey.Relation.BackKeyID {
4507 if removeRelationDest { // 删除双向关联的目标字段
4508 destAv.KeyValues = append(destAv.KeyValues[:i], destAv.KeyValues[i+1:]...)
4509 }
4510 continue
4511 }
4512
4513 if av.KeyTypeRelation == keyValues.Key.Type && keyValues.Key.Relation.AvID == attrView.ID {
4514 destAvRelSrcAv = true
4515 }
4516 }
4517
4518 if removeRelationDest {
4519 for _, view := range destAv.Views {
4520 switch view.LayoutType {
4521 case av.LayoutTypeTable:
4522 for i, column := range view.Table.Columns {
4523 if column.ID == removedKey.Relation.BackKeyID {
4524 view.Table.Columns = append(view.Table.Columns[:i], view.Table.Columns[i+1:]...)
4525 break
4526 }
4527 }
4528 case av.LayoutTypeGallery:
4529 for i, field := range view.Gallery.CardFields {
4530 if field.ID == removedKey.Relation.BackKeyID {
4531 view.Gallery.CardFields = append(view.Gallery.CardFields[:i], view.Gallery.CardFields[i+1:]...)
4532 break
4533 }
4534 }
4535 case av.LayoutTypeKanban:
4536 for i, field := range view.Kanban.Fields {
4537 if field.ID == removedKey.Relation.BackKeyID {
4538 view.Kanban.Fields = append(view.Kanban.Fields[:i], view.Kanban.Fields[i+1:]...)
4539 break
4540 }
4541 }
4542 }
4543 }
4544 }
4545
4546 if destAv != attrView {
4547 av.SaveAttributeView(destAv)
4548 ReloadAttrView(destAv.ID)
4549 }
4550
4551 if !destAvRelSrcAv {
4552 av.RemoveAvRel(destAv.ID, attrView.ID)
4553 }
4554 }
4555
4556 srcAvRelDestAv := false
4557 for _, keyValues := range attrView.KeyValues {
4558 if av.KeyTypeRelation == keyValues.Key.Type && nil != keyValues.Key.Relation && keyValues.Key.Relation.AvID == removedKey.Relation.AvID {
4559 srcAvRelDestAv = true
4560 }
4561 }
4562 if !srcAvRelDestAv {
4563 av.RemoveAvRel(attrView.ID, removedKey.Relation.AvID)
4564 }
4565 }
4566 }
4567
4568 for _, view := range attrView.Views {
4569 if nil != view.Table {
4570 for i, column := range view.Table.Columns {
4571 if column.ID == keyID {
4572 view.Table.Columns = append(view.Table.Columns[:i], view.Table.Columns[i+1:]...)
4573 break
4574 }
4575 }
4576 }
4577
4578 if nil != view.Gallery {
4579 for i, field := range view.Gallery.CardFields {
4580 if field.ID == keyID {
4581 view.Gallery.CardFields = append(view.Gallery.CardFields[:i], view.Gallery.CardFields[i+1:]...)
4582 break
4583 }
4584 }
4585 }
4586
4587 if nil != view.Kanban {
4588 for i, field := range view.Kanban.Fields {
4589 if field.ID == keyID {
4590 view.Kanban.Fields = append(view.Kanban.Fields[:i], view.Kanban.Fields[i+1:]...)
4591 break
4592 }
4593 }
4594 }
4595 }
4596
4597 for _, view := range attrView.Views {
4598 if nil != view.Group {
4599 if groupKey := view.GetGroupKey(attrView); nil != groupKey && groupKey.ID == keyID {
4600 removeAttributeViewGroup0(view)
4601 }
4602 }
4603 }
4604
4605 if err = av.SaveAttributeView(attrView); nil != err {
4606 return
4607 }
4608
4609 relatedAvIDs := av.GetSrcAvIDs(avID)
4610 for _, relatedAvID := range relatedAvIDs {
4611 destAv, _ := av.ParseAttributeView(relatedAvID)
4612 if nil == destAv {
4613 continue
4614 }
4615
4616 for _, keyValues := range destAv.KeyValues {
4617 if av.KeyTypeRollup == keyValues.Key.Type && keyValues.Key.Rollup.KeyID == keyID {
4618 // 置空关联过来的汇总
4619 for _, val := range keyValues.Values {
4620 val.Rollup.Contents = nil
4621 }
4622 }
4623 }
4624
4625 regenAttrViewGroups(destAv)
4626 av.SaveAttributeView(destAv)
4627 ReloadAttrView(destAv.ID)
4628 }
4629 return
4630}
4631
4632func (tx *Transaction) doReplaceAttrViewBlock(operation *Operation) (ret *TxErr) {
4633 err := replaceAttributeViewBlock(operation.AvID, operation.PreviousID, operation.NextID, operation.IsDetached, tx)
4634 if err != nil {
4635 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID}
4636 }
4637 return
4638}
4639
4640func replaceAttributeViewBlock(avID, oldBlockID, newBlockID string, isDetached bool, tx *Transaction) (err error) {
4641 attrView, err := av.ParseAttributeView(avID)
4642 if err != nil {
4643 return
4644 }
4645
4646 if err = replaceAttributeViewBlock0(attrView, oldBlockID, newBlockID, isDetached, tx); nil != err {
4647 return
4648 }
4649
4650 if err = av.SaveAttributeView(attrView); nil != err {
4651 return
4652 }
4653 return
4654}
4655
4656func replaceAttributeViewBlock0(attrView *av.AttributeView, oldBlockID, newNodeID string, isDetached bool, tx *Transaction) (err error) {
4657 avID := attrView.ID
4658 var tree *parse.Tree
4659 var node *ast.Node
4660 if !isDetached {
4661 node, tree, _ = getNodeByBlockID(tx, newNodeID)
4662 }
4663
4664 now := util.CurrentTimeMillis()
4665 // 检查是否已经存在绑定块,如果存在的话则重新绑定
4666 for _, blockVal := range attrView.GetBlockKeyValues().Values {
4667 if !isDetached && blockVal.Block.ID == newNodeID && nil != node && nil != tree {
4668 bindBlockAv0(tx, avID, node, tree)
4669 blockVal.IsDetached = false
4670 icon, content := getNodeAvBlockText(node, "")
4671 content = util.UnescapeHTML(content)
4672 blockVal.Block.Icon, blockVal.Block.Content = icon, content
4673 blockVal.UpdatedAt = now
4674 regenAttrViewGroups(attrView)
4675 return
4676 }
4677 }
4678
4679 for _, blockVal := range attrView.GetBlockKeyValues().Values {
4680 if blockVal.BlockID != oldBlockID {
4681 continue
4682 }
4683
4684 if av.KeyTypeBlock == blockVal.Type {
4685 blockVal.IsDetached = isDetached
4686 if !isDetached {
4687 if "" != blockVal.Block.ID && blockVal.Block.ID != newNodeID {
4688 unbindBlockAv(tx, avID, blockVal.Block.ID)
4689 }
4690 bindBlockAv(tx, avID, newNodeID)
4691
4692 blockVal.Block.ID = newNodeID
4693 icon, content := getNodeAvBlockText(node, "")
4694 content = util.UnescapeHTML(content)
4695 blockVal.Block.Icon, blockVal.Block.Content = icon, content
4696
4697 refreshRelatedSrcAvs(avID)
4698 } else {
4699 blockVal.Block.ID = ""
4700 }
4701 }
4702 }
4703
4704 regenAttrViewGroups(attrView)
4705 return
4706}
4707
4708func BatchReplaceAttributeViewBlocks(avID string, isDetached bool, oldNew []map[string]string) (err error) {
4709 attrView, err := av.ParseAttributeView(avID)
4710 if err != nil {
4711 return
4712 }
4713
4714 for _, oldNewMap := range oldNew {
4715 for oldBlockID, newNodeID := range oldNewMap {
4716 if err = replaceAttributeViewBlock0(attrView, oldBlockID, newNodeID, isDetached, nil); nil != err {
4717 return
4718 }
4719 }
4720 }
4721
4722 if err = av.SaveAttributeView(attrView); nil != err {
4723 return
4724 }
4725 return
4726}
4727
4728func (tx *Transaction) doUpdateAttrViewCell(operation *Operation) (ret *TxErr) {
4729 _, err := UpdateAttributeViewCell(tx, operation.AvID, operation.KeyID, operation.RowID, operation.Data)
4730 if err != nil {
4731 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
4732 }
4733 return
4734}
4735
4736func BatchUpdateAttributeViewCells(tx *Transaction, avID string, values []interface{}) (err error) {
4737 attrView, err := av.ParseAttributeView(avID)
4738 if err != nil {
4739 return
4740 }
4741
4742 for _, value := range values {
4743 v := value.(map[string]interface{})
4744 keyID := v["keyID"].(string)
4745 var itemID string
4746 if _, ok := v["itemID"]; ok {
4747 itemID = v["itemID"].(string)
4748 } else if _, ok := v["rowID"]; ok {
4749 // TODO 划于 2026 年 6 月 30 日后删除 https://github.com/siyuan-note/siyuan/issues/15708#issuecomment-3239694546
4750 itemID = v["rowID"].(string)
4751 }
4752 valueData := v["value"]
4753 _, err = updateAttributeViewValue(tx, attrView, keyID, itemID, valueData)
4754 if err != nil {
4755 return
4756 }
4757 }
4758 return
4759}
4760
4761func UpdateAttributeViewCell(tx *Transaction, avID, keyID, itemID string, valueData interface{}) (val *av.Value, err error) {
4762 attrView, err := av.ParseAttributeView(avID)
4763 if err != nil {
4764 return
4765 }
4766
4767 val, err = updateAttributeViewValue(tx, attrView, keyID, itemID, valueData)
4768 if nil != err {
4769 return
4770 }
4771 return
4772}
4773
4774func updateAttributeViewValue(tx *Transaction, attrView *av.AttributeView, keyID, itemID string, valueData interface{}) (val *av.Value, err error) {
4775 avID := attrView.ID
4776 var blockVal *av.Value
4777 for _, kv := range attrView.KeyValues {
4778 if av.KeyTypeBlock == kv.Key.Type {
4779 for _, v := range kv.Values {
4780 if itemID == v.BlockID {
4781 blockVal = v
4782 break
4783 }
4784 }
4785 break
4786 }
4787 }
4788
4789 now := time.Now().UnixMilli()
4790 oldIsDetached := true
4791 var oldBoundBlockID string
4792 if nil != blockVal {
4793 oldIsDetached = blockVal.IsDetached
4794 oldBoundBlockID = blockVal.Block.ID
4795 }
4796 for _, keyValues := range attrView.KeyValues {
4797 if keyID != keyValues.Key.ID {
4798 continue
4799 }
4800
4801 for _, value := range keyValues.Values {
4802 if itemID == value.BlockID {
4803 val = value
4804 val.Type = keyValues.Key.Type
4805 break
4806 }
4807 }
4808
4809 if nil == val {
4810 val = &av.Value{ID: ast.NewNodeID(), KeyID: keyID, BlockID: itemID, Type: keyValues.Key.Type, CreatedAt: now, UpdatedAt: now}
4811 keyValues.Values = append(keyValues.Values, val)
4812 }
4813 break
4814 }
4815
4816 isUpdatingBlockKey := av.KeyTypeBlock == val.Type
4817 var oldRelationBlockIDs []string
4818 if av.KeyTypeRelation == val.Type {
4819 if nil != val.Relation {
4820 for _, bID := range val.Relation.BlockIDs {
4821 oldRelationBlockIDs = append(oldRelationBlockIDs, bID)
4822 }
4823 }
4824 }
4825 data, err := gulu.JSON.MarshalJSON(valueData)
4826 if err != nil {
4827 logging.LogErrorf("marshal value [%+v] failed: %s", valueData, err)
4828 return
4829 }
4830 if err = gulu.JSON.UnmarshalJSON(data, &val); err != nil {
4831 logging.LogErrorf("unmarshal data [%s] failed: %s", data, err)
4832 return
4833 }
4834
4835 key, _ := attrView.GetKey(keyID)
4836
4837 if av.KeyTypeNumber == val.Type {
4838 if nil != val.Number {
4839 if !val.Number.IsNotEmpty {
4840 val.Number.Content = 0
4841 val.Number.FormattedContent = ""
4842 } else {
4843 val.Number.FormatNumber()
4844 }
4845 }
4846 } else if av.KeyTypeDate == val.Type {
4847 if nil != val.Date && !val.Date.IsNotEmpty {
4848 val.Date.Content = 0
4849 val.Date.FormattedContent = ""
4850 }
4851 } else if av.KeyTypeSelect == val.Type || av.KeyTypeMSelect == val.Type {
4852 if nil != key && 0 < len(val.MSelect) {
4853 var tmp []*av.ValueSelect
4854 // 移除空选项 https://github.com/siyuan-note/siyuan/issues/15533
4855 for _, v := range val.MSelect {
4856 if "" != v.Content {
4857 tmp = append(tmp, v)
4858 }
4859 }
4860 val.MSelect = tmp
4861
4862 if 1 > len(val.MSelect) {
4863 return
4864 }
4865
4866 // The selection options are inconsistent after pasting data into the database https://github.com/siyuan-note/siyuan/issues/11409
4867 for _, valOpt := range val.MSelect {
4868 if opt := key.GetOption(valOpt.Content); nil == opt {
4869 // 不存在的选项新建保存
4870 color := valOpt.Color
4871 if "" == color {
4872 color = fmt.Sprintf("%d", 1+rand.Intn(14))
4873 }
4874 opt = &av.SelectOption{Name: valOpt.Content, Color: color}
4875 key.Options = append(key.Options, opt)
4876 } else {
4877 // 已经存在的选项颜色需要保持不变
4878 valOpt.Color = opt.Color
4879 }
4880 }
4881 }
4882 }
4883
4884 relationChangeMode := 0 // 0:不变(仅排序),1:增加,2:减少
4885 if av.KeyTypeRelation == val.Type {
4886 // 关联字段得 content 是自动渲染的,所以不需要保存
4887 val.Relation.Contents = nil
4888
4889 // 去重
4890 val.Relation.BlockIDs = gulu.Str.RemoveDuplicatedElem(val.Relation.BlockIDs)
4891
4892 // 计算关联变更模式
4893 if len(oldRelationBlockIDs) == len(val.Relation.BlockIDs) {
4894 relationChangeMode = 0
4895 } else {
4896 if len(oldRelationBlockIDs) > len(val.Relation.BlockIDs) {
4897 relationChangeMode = 2
4898 } else {
4899 relationChangeMode = 1
4900 }
4901 }
4902 }
4903
4904 // val.IsDetached 只有更新主键的时候才会传入,所以下面需要结合 isUpdatingBlockKey 来判断
4905
4906 if isUpdatingBlockKey {
4907 if oldIsDetached {
4908 // 之前是非绑定块
4909
4910 if !val.IsDetached { // 现在绑定了块
4911 bindBlockAv(tx, avID, val.Block.ID)
4912 }
4913 } else {
4914 // 之前绑定了块
4915
4916 if val.IsDetached { // 现在是非绑定块
4917 unbindBlockAv(tx, avID, val.Block.ID)
4918 val.Block.ID = ""
4919 } else {
4920 // 现在也绑定了块
4921
4922 if oldBoundBlockID != val.Block.ID { // 之前绑定的块和现在绑定的块不一样
4923 // 换绑块
4924 unbindBlockAv(tx, avID, oldBoundBlockID)
4925 bindBlockAv(tx, avID, val.Block.ID)
4926 val.Block.Content = util.UnescapeHTML(val.Block.Content)
4927 } else { // 之前绑定的块和现在绑定的块一样
4928 content := strings.TrimSpace(val.Block.Content)
4929 node, tree, _ := getNodeByBlockID(tx, val.Block.ID)
4930 _, blockText := getNodeAvBlockText(node, "")
4931 if "" == content {
4932 // 使用动态锚文本
4933 val.Block.Content = util.UnescapeHTML(blockText)
4934 updateBlockValueStaticText(tx, node, tree, avID, "")
4935 } else {
4936 val.Block.Content = content
4937 updateBlockValueStaticText(tx, node, tree, avID, content)
4938 }
4939 }
4940 }
4941 }
4942 }
4943
4944 if nil != blockVal {
4945 blockVal.Block.Updated = now
4946 blockVal.SetUpdatedAt(now)
4947 if isUpdatingBlockKey {
4948 blockVal.IsDetached = val.IsDetached
4949 }
4950 }
4951 val.SetUpdatedAt(now)
4952
4953 if nil != key && av.KeyTypeRelation == key.Type && nil != key.Relation && key.Relation.IsTwoWay {
4954 // 双向关联需要同时更新目标字段的值
4955 updateTwoWayRelationDestAttrView(attrView, key, val, relationChangeMode, oldRelationBlockIDs)
4956 }
4957
4958 regenAttrViewGroups(attrView)
4959 if err = av.SaveAttributeView(attrView); nil != err {
4960 return
4961 }
4962
4963 refreshRelatedSrcAvs(avID)
4964 return
4965}
4966
4967func refreshRelatedSrcAvs(destAvID string) {
4968 relatedAvIDs := av.GetSrcAvIDs(destAvID)
4969 for _, relatedAvID := range relatedAvIDs {
4970 destAv, _ := av.ParseAttributeView(relatedAvID)
4971 if nil == destAv {
4972 continue
4973 }
4974
4975 regenAttrViewGroups(destAv)
4976 av.SaveAttributeView(destAv)
4977 ReloadAttrView(relatedAvID)
4978 }
4979}
4980
4981// relationChangeMode
4982// 0:关联字段值不变(仅排序),不影响目标值
4983// 1:关联字段值增加,增加目标值
4984// 2:关联字段值减少,减少目标值
4985func updateTwoWayRelationDestAttrView(attrView *av.AttributeView, relKey *av.Key, val *av.Value, relationChangeMode int, oldRelationBlockIDs []string) {
4986 var destAv *av.AttributeView
4987 if attrView.ID == relKey.Relation.AvID {
4988 destAv = attrView
4989 } else {
4990 destAv, _ = av.ParseAttributeView(relKey.Relation.AvID)
4991 }
4992
4993 if nil == destAv {
4994 return
4995 }
4996
4997 now := util.CurrentTimeMillis()
4998 if 1 == relationChangeMode {
4999 addBlockIDs := val.Relation.BlockIDs
5000 for _, bID := range oldRelationBlockIDs {
5001 addBlockIDs = gulu.Str.RemoveElem(addBlockIDs, bID)
5002 }
5003
5004 for _, blockID := range addBlockIDs {
5005 for _, keyValues := range destAv.KeyValues {
5006 if keyValues.Key.ID != relKey.Relation.BackKeyID {
5007 continue
5008 }
5009
5010 destVal := keyValues.GetValue(blockID)
5011 if nil == destVal {
5012 destVal = &av.Value{ID: ast.NewNodeID(), KeyID: keyValues.Key.ID, BlockID: blockID, Type: keyValues.Key.Type, Relation: &av.ValueRelation{}, CreatedAt: now, UpdatedAt: now + 1000}
5013 keyValues.Values = append(keyValues.Values, destVal)
5014 }
5015
5016 destVal.Relation.BlockIDs = append(destVal.Relation.BlockIDs, val.BlockID)
5017 destVal.Relation.BlockIDs = gulu.Str.RemoveDuplicatedElem(destVal.Relation.BlockIDs)
5018 break
5019 }
5020 }
5021 } else if 2 == relationChangeMode {
5022 removeBlockIDs := oldRelationBlockIDs
5023 for _, bID := range val.Relation.BlockIDs {
5024 removeBlockIDs = gulu.Str.RemoveElem(removeBlockIDs, bID)
5025 }
5026
5027 for _, blockID := range removeBlockIDs {
5028 for _, keyValues := range destAv.KeyValues {
5029 if keyValues.Key.ID != relKey.Relation.BackKeyID {
5030 continue
5031 }
5032
5033 for _, value := range keyValues.Values {
5034 if value.BlockID == blockID {
5035 value.Relation.BlockIDs = gulu.Str.RemoveElem(value.Relation.BlockIDs, val.BlockID)
5036 value.SetUpdatedAt(now)
5037 break
5038 }
5039 }
5040 }
5041 }
5042 }
5043
5044 if destAv != attrView {
5045 regenAttrViewGroups(destAv)
5046 av.SaveAttributeView(destAv)
5047 }
5048}
5049
5050// regenAttrViewGroups 重新生成分组视图。
5051func regenAttrViewGroups(attrView *av.AttributeView) {
5052 for _, view := range attrView.Views {
5053 groupKey := view.GetGroupKey(attrView)
5054 if nil == groupKey {
5055 continue
5056 }
5057
5058 genAttrViewGroups(view, attrView)
5059 }
5060}
5061
5062func unbindBlockAv(tx *Transaction, avID, nodeID string) {
5063 node, tree, err := getNodeByBlockID(tx, nodeID)
5064 if err != nil {
5065 return
5066 }
5067
5068 attrs := parse.IAL2Map(node.KramdownIAL)
5069 if "" == attrs[av.NodeAttrNameAvs] {
5070 return
5071 }
5072
5073 avIDs := strings.Split(attrs[av.NodeAttrNameAvs], ",")
5074 avIDs = gulu.Str.RemoveElem(avIDs, avID)
5075 if 0 == len(avIDs) {
5076 attrs[av.NodeAttrNameAvs] = ""
5077 } else {
5078 attrs[av.NodeAttrNameAvs] = strings.Join(avIDs, ",")
5079 }
5080
5081 avNames := getAvNames(attrs[av.NodeAttrNameAvs])
5082 if "" != avNames {
5083 attrs[av.NodeAttrViewNames] = avNames
5084 }
5085
5086 if nil != tx {
5087 err = setNodeAttrsWithTx(tx, node, tree, attrs)
5088 } else {
5089 err = setNodeAttrs(node, tree, attrs)
5090 }
5091 if err != nil {
5092 logging.LogWarnf("set node [%s] attrs failed: %s", nodeID, err)
5093 return
5094 }
5095 return
5096}
5097
5098func bindBlockAv(tx *Transaction, avID, blockID string) {
5099 node, tree, err := getNodeByBlockID(tx, blockID)
5100 if err != nil {
5101 return
5102 }
5103
5104 bindBlockAv0(tx, avID, node, tree)
5105 return
5106}
5107
5108func bindBlockAv0(tx *Transaction, avID string, node *ast.Node, tree *parse.Tree) {
5109 attrs := parse.IAL2Map(node.KramdownIAL)
5110 if "" == attrs[av.NodeAttrNameAvs] {
5111 attrs[av.NodeAttrNameAvs] = avID
5112 } else {
5113 avIDs := strings.Split(attrs[av.NodeAttrNameAvs], ",")
5114 avIDs = append(avIDs, avID)
5115 avIDs = gulu.Str.RemoveDuplicatedElem(avIDs)
5116 attrs[av.NodeAttrNameAvs] = strings.Join(avIDs, ",")
5117 }
5118
5119 avNames := getAvNames(attrs[av.NodeAttrNameAvs])
5120 if "" != avNames {
5121 attrs[av.NodeAttrViewNames] = avNames
5122 }
5123
5124 var err error
5125 if nil != tx {
5126 err = setNodeAttrsWithTx(tx, node, tree, attrs)
5127 } else {
5128 err = setNodeAttrs(node, tree, attrs)
5129 }
5130 if err != nil {
5131 logging.LogWarnf("set node [%s] attrs failed: %s", node.ID, err)
5132 return
5133 }
5134 return
5135}
5136
5137func updateBlockValueStaticText(tx *Transaction, node *ast.Node, tree *parse.Tree, avID, text string) {
5138 // 设置静态锚文本 Database-bound block primary key supports setting static anchor text https://github.com/siyuan-note/siyuan/issues/10049
5139
5140 if nil == node {
5141 return
5142 }
5143
5144 attrs := parse.IAL2Map(node.KramdownIAL)
5145 attrs[av.NodeAttrViewStaticText+"-"+avID] = text
5146 var err error
5147 if nil != tx {
5148 err = setNodeAttrsWithTx(tx, node, tree, attrs)
5149 } else {
5150 err = setNodeAttrs(node, tree, attrs)
5151 }
5152 if err != nil {
5153 logging.LogWarnf("set node [%s] attrs failed: %s", node.ID, err)
5154 return
5155 }
5156}
5157
5158func getNodeByBlockID(tx *Transaction, blockID string) (node *ast.Node, tree *parse.Tree, err error) {
5159 if nil != tx {
5160 tree, err = tx.loadTree(blockID)
5161 } else {
5162 tree, err = LoadTreeByBlockID(blockID)
5163 }
5164 if err != nil {
5165 return
5166 }
5167 node = treenode.GetNodeInTree(tree, blockID)
5168 if nil == node {
5169 logging.LogWarnf("node [%s] not found in tree [%s]", blockID, tree.ID)
5170 return
5171 }
5172 return
5173}
5174
5175func (tx *Transaction) doUpdateAttrViewColOptions(operation *Operation) (ret *TxErr) {
5176 err := updateAttributeViewColumnOptions(operation)
5177 if err != nil {
5178 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
5179 }
5180 return
5181}
5182
5183func updateAttributeViewColumnOptions(operation *Operation) (err error) {
5184 attrView, err := av.ParseAttributeView(operation.AvID)
5185 if err != nil {
5186 return
5187 }
5188
5189 jsonData, err := gulu.JSON.MarshalJSON(operation.Data)
5190 if err != nil {
5191 return
5192 }
5193
5194 options := []*av.SelectOption{}
5195 if err = gulu.JSON.UnmarshalJSON(jsonData, &options); err != nil {
5196 return
5197 }
5198
5199 // 移除空选项 https://github.com/siyuan-note/siyuan/issues/15533
5200 var tmp []*av.SelectOption
5201 for _, opt := range options {
5202 if "" != opt.Name {
5203 tmp = append(tmp, opt)
5204 }
5205 }
5206 options = tmp
5207 if 1 > len(options) {
5208 return
5209 }
5210
5211 optionSorts := map[string]int{}
5212 for i, opt := range options {
5213 optionSorts[opt.Name] = i
5214 }
5215
5216 addNew := false
5217 selectKey, _ := attrView.GetKey(operation.ID)
5218 if nil == selectKey {
5219 return
5220 }
5221 existingOptions := map[string]*av.SelectOption{}
5222 for _, opt := range selectKey.Options {
5223 existingOptions[opt.Name] = opt
5224 }
5225 for _, opt := range options {
5226 if existingOpt, exists := existingOptions[opt.Name]; exists {
5227 // 如果选项已经存在则更新颜色和描述
5228 existingOpt.Color = opt.Color
5229 existingOpt.Desc = opt.Desc
5230 } else {
5231 // 如果选项不存在则添加新选项
5232 selectKey.Options = append(selectKey.Options, &av.SelectOption{
5233 Name: opt.Name,
5234 Color: opt.Color,
5235 Desc: opt.Desc,
5236 })
5237 addNew = true
5238 }
5239 }
5240
5241 if !addNew {
5242 sort.SliceStable(selectKey.Options, func(i, j int) bool {
5243 return optionSorts[selectKey.Options[i].Name] < optionSorts[selectKey.Options[j].Name]
5244 })
5245 }
5246
5247 regenAttrViewGroups(attrView)
5248 err = av.SaveAttributeView(attrView)
5249 return
5250}
5251
5252func (tx *Transaction) doRemoveAttrViewColOption(operation *Operation) (ret *TxErr) {
5253 err := removeAttributeViewColumnOption(operation)
5254 if err != nil {
5255 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
5256 }
5257 return
5258}
5259
5260func removeAttributeViewColumnOption(operation *Operation) (err error) {
5261 attrView, err := av.ParseAttributeView(operation.AvID)
5262 if err != nil {
5263 return
5264 }
5265
5266 optName := operation.Data.(string)
5267
5268 key, err := attrView.GetKey(operation.ID)
5269 if err != nil {
5270 return
5271 }
5272
5273 for i, opt := range key.Options {
5274 if optName == opt.Name {
5275 key.Options = append(key.Options[:i], key.Options[i+1:]...)
5276 break
5277 }
5278 }
5279
5280 for _, keyValues := range attrView.KeyValues {
5281 if keyValues.Key.ID != operation.ID {
5282 continue
5283 }
5284
5285 for _, value := range keyValues.Values {
5286 if nil == value || nil == value.MSelect {
5287 continue
5288 }
5289
5290 for i, opt := range value.MSelect {
5291 if optName == opt.Content {
5292 value.MSelect = append(value.MSelect[:i], value.MSelect[i+1:]...)
5293 break
5294 }
5295 }
5296 }
5297 break
5298 }
5299
5300 // 如果存在选项对应的过滤条件,则删除过滤条件中设置的选项值 https://github.com/siyuan-note/siyuan/issues/15536
5301 for _, view := range attrView.Views {
5302 for _, filter := range view.Filters {
5303 if filter.Column != operation.ID {
5304 continue
5305 }
5306
5307 if nil != filter.Value && (av.KeyTypeSelect == filter.Value.Type || av.KeyTypeMSelect == filter.Value.Type) {
5308 if av.FilterOperatorIsEmpty == filter.Operator || av.FilterOperatorIsNotEmpty == filter.Operator {
5309 continue
5310 }
5311
5312 for i, opt := range filter.Value.MSelect {
5313 if optName == opt.Content {
5314 filter.Value.MSelect = append(filter.Value.MSelect[:i], filter.Value.MSelect[i+1:]...)
5315 break
5316 }
5317 }
5318 if 1 > len(filter.Value.MSelect) {
5319 // 如果删除后选项值为空,则删除过滤条件
5320 for i, f := range view.Filters {
5321 if f.Column == operation.ID && f.Value == filter.Value {
5322 view.Filters = append(view.Filters[:i], view.Filters[i+1:]...)
5323 break
5324 }
5325 }
5326 }
5327 }
5328 }
5329 }
5330
5331 regenAttrViewGroups(attrView)
5332 err = av.SaveAttributeView(attrView)
5333 return
5334}
5335
5336func (tx *Transaction) doUpdateAttrViewColOption(operation *Operation) (ret *TxErr) {
5337 err := updateAttributeViewColumnOption(operation)
5338 if err != nil {
5339 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
5340 }
5341 return
5342}
5343
5344func updateAttributeViewColumnOption(operation *Operation) (err error) {
5345 attrView, err := av.ParseAttributeView(operation.AvID)
5346 if err != nil {
5347 return
5348 }
5349
5350 key, err := attrView.GetKey(operation.ID)
5351 if err != nil {
5352 return
5353 }
5354
5355 data := operation.Data.(map[string]interface{})
5356
5357 rename := false
5358 oldName := strings.TrimSpace(data["oldName"].(string))
5359 newName := strings.TrimSpace(data["newName"].(string))
5360 newDesc := strings.TrimSpace(data["newDesc"].(string))
5361 newColor := data["newColor"].(string)
5362
5363 found := false
5364 if oldName != newName {
5365 rename = true
5366
5367 for _, opt := range key.Options {
5368 if newName == opt.Name { // 如果选项已经存在则直接使用
5369 found = true
5370 newColor = opt.Color
5371 newDesc = opt.Desc
5372 break
5373 }
5374 }
5375 }
5376
5377 if !found {
5378 for i, opt := range key.Options {
5379 if oldName == opt.Name {
5380 key.Options[i].Name = newName
5381 key.Options[i].Color = newColor
5382 key.Options[i].Desc = newDesc
5383 break
5384 }
5385 }
5386 }
5387
5388 // 如果存在选项对应的值,需要更新值中的选项
5389 for _, keyValues := range attrView.KeyValues {
5390 if keyValues.Key.ID != operation.ID {
5391 continue
5392 }
5393
5394 for _, value := range keyValues.Values {
5395 if nil == value || nil == value.MSelect {
5396 continue
5397 }
5398
5399 found = false
5400 for _, opt := range value.MSelect {
5401 if newName == opt.Content {
5402 found = true
5403 break
5404 }
5405 }
5406 if found && rename {
5407 idx := -1
5408 for i, opt := range value.MSelect {
5409 if oldName == opt.Content {
5410 idx = i
5411 break
5412 }
5413 }
5414 if 0 <= idx {
5415 value.MSelect = util.RemoveElem(value.MSelect, idx)
5416 }
5417 } else {
5418 for i, opt := range value.MSelect {
5419 if oldName == opt.Content {
5420 value.MSelect[i].Content = newName
5421 value.MSelect[i].Color = newColor
5422 break
5423 }
5424 }
5425 }
5426 }
5427 break
5428 }
5429
5430 // 如果存在选项对应的过滤条件,需要更新过滤条件中设置的选项值
5431 // Database select field filters follow option editing changes https://github.com/siyuan-note/siyuan/issues/10881
5432 for _, view := range attrView.Views {
5433 for _, filter := range view.Filters {
5434 if filter.Column != key.ID {
5435 continue
5436 }
5437
5438 if nil != filter.Value && (av.KeyTypeSelect == filter.Value.Type || av.KeyTypeMSelect == filter.Value.Type) {
5439 for i, opt := range filter.Value.MSelect {
5440 if oldName == opt.Content {
5441 filter.Value.MSelect[i].Content = newName
5442 filter.Value.MSelect[i].Color = newColor
5443 break
5444 }
5445 }
5446 }
5447 }
5448 }
5449
5450 regenAttrViewGroups(attrView)
5451 err = av.SaveAttributeView(attrView)
5452 return
5453}
5454
5455func (tx *Transaction) doSetAttrViewColOptionDesc(operation *Operation) (ret *TxErr) {
5456 err := setAttributeViewColumnOptionDesc(operation)
5457 if err != nil {
5458 return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()}
5459 }
5460 return
5461}
5462
5463func setAttributeViewColumnOptionDesc(operation *Operation) (err error) {
5464 attrView, err := av.ParseAttributeView(operation.AvID)
5465 if err != nil {
5466 return
5467 }
5468
5469 key, err := attrView.GetKey(operation.ID)
5470 if err != nil {
5471 return
5472 }
5473
5474 data := operation.Data.(map[string]interface{})
5475 name := data["name"].(string)
5476 desc := data["desc"].(string)
5477
5478 for i, opt := range key.Options {
5479 if name == opt.Name {
5480 key.Options[i].Desc = desc
5481 break
5482 }
5483 }
5484
5485 err = av.SaveAttributeView(attrView)
5486 return
5487}
5488
5489func getAttrViewViewByBlockID(attrView *av.AttributeView, blockID string) (ret *av.View, err error) {
5490 var viewID string
5491 var node *ast.Node
5492 if "" != blockID {
5493 node, _, _ = getNodeByBlockID(nil, blockID)
5494 }
5495 if nil != node {
5496 viewID = node.IALAttr(av.NodeAttrView)
5497 }
5498 return attrView.GetCurrentView(viewID)
5499}
5500
5501func getAttrViewName(attrView *av.AttributeView) string {
5502 ret := strings.TrimSpace(attrView.Name)
5503 if "" == ret {
5504 ret = Conf.language(105)
5505 }
5506 return ret
5507}
5508
5509func updateBoundBlockAvsAttribute(avIDs []string) {
5510 // 更新指定 avIDs 中绑定块的 avs 属性
5511
5512 cachedTrees, saveTrees := map[string]*parse.Tree{}, map[string]*parse.Tree{}
5513 luteEngine := util.NewLute()
5514 for _, avID := range avIDs {
5515 attrView, _ := av.ParseAttributeView(avID)
5516 if nil == attrView {
5517 continue
5518 }
5519
5520 blockKeyValues := attrView.GetBlockKeyValues()
5521 for _, blockValue := range blockKeyValues.Values {
5522 if blockValue.IsDetached {
5523 continue
5524 }
5525 bt := treenode.GetBlockTree(blockValue.BlockID)
5526 if nil == bt {
5527 continue
5528 }
5529
5530 tree := cachedTrees[bt.RootID]
5531 if nil == tree {
5532 tree, _ = filesys.LoadTree(bt.BoxID, bt.Path, luteEngine)
5533 if nil == tree {
5534 continue
5535 }
5536 cachedTrees[bt.RootID] = tree
5537 }
5538
5539 node := treenode.GetNodeInTree(tree, blockValue.BlockID)
5540 if nil == node {
5541 continue
5542 }
5543
5544 attrs := parse.IAL2Map(node.KramdownIAL)
5545 if "" == attrs[av.NodeAttrNameAvs] {
5546 attrs[av.NodeAttrNameAvs] = avID
5547 } else {
5548 nodeAvIDs := strings.Split(attrs[av.NodeAttrNameAvs], ",")
5549 nodeAvIDs = append(nodeAvIDs, avID)
5550 nodeAvIDs = gulu.Str.RemoveDuplicatedElem(nodeAvIDs)
5551 attrs[av.NodeAttrNameAvs] = strings.Join(nodeAvIDs, ",")
5552 saveTrees[bt.RootID] = tree
5553 }
5554
5555 avNames := getAvNames(attrs[av.NodeAttrNameAvs])
5556 if "" != avNames {
5557 attrs[av.NodeAttrViewNames] = avNames
5558 }
5559
5560 oldAttrs, setErr := setNodeAttrs0(node, attrs)
5561 if nil != setErr {
5562 continue
5563 }
5564 cache.PutBlockIAL(node.ID, parse.IAL2Map(node.KramdownIAL))
5565 pushBroadcastAttrTransactions(oldAttrs, node)
5566 }
5567 }
5568
5569 for _, saveTree := range saveTrees {
5570 if treeErr := indexWriteTreeUpsertQueue(saveTree); nil != treeErr {
5571 logging.LogErrorf("index write tree upsert queue failed: %s", treeErr)
5572 }
5573
5574 avNodes := saveTree.Root.ChildrenByType(ast.NodeAttributeView)
5575 av.BatchUpsertBlockRel(avNodes)
5576 }
5577}