A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 5577 lines 156 kB view raw
1// SiYuan - Refactor your thinking 2// Copyright (c) 2020-present, b3log.org 3// 4// This program is free software: you can redistribute it and/or modify 5// it under the terms of the GNU Affero General Public License as published by 6// the Free Software Foundation, either version 3 of the License, or 7// (at your option) any later version. 8// 9// This program is distributed in the hope that it will be useful, 10// but WITHOUT ANY WARRANTY; without even the implied warranty of 11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12// GNU Affero General Public License for more details. 13// 14// You should have received a copy of the GNU Affero General Public License 15// along with this program. If not, see <https://www.gnu.org/licenses/>. 16 17package model 18 19import ( 20 "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("&nbsp;") 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}