A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 997 lines 30 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 sql 18 19import ( 20 "bytes" 21 "fmt" 22 "sort" 23 "strings" 24 "text/template" 25 "text/template/parse" 26 "time" 27 28 "github.com/88250/gulu" 29 "github.com/jinzhu/copier" 30 "github.com/siyuan-note/logging" 31 "github.com/siyuan-note/siyuan/kernel/av" 32 "github.com/siyuan-note/siyuan/kernel/filesys" 33 "github.com/siyuan-note/siyuan/kernel/treenode" 34 "github.com/siyuan-note/siyuan/kernel/util" 35) 36 37func RenderGroupView(attrView *av.AttributeView, view, groupView *av.View, query string) (ret av.Viewable) { 38 var err error 39 switch groupView.LayoutType { 40 case av.LayoutTypeTable: 41 // 这里需要使用深拷贝,因为字段上可能会带有计算(FieldCalc),每个分组视图的计算结果都需要分别存储在不同的字段实例上 42 err = copier.CopyWithOption(&groupView.Table.Columns, &view.Table.Columns, copier.Option{DeepCopy: true}) 43 groupView.Table.ShowIcon = view.Table.ShowIcon 44 groupView.Table.WrapField = view.Table.WrapField 45 case av.LayoutTypeGallery: 46 err = copier.CopyWithOption(&groupView.Gallery.CardFields, &view.Gallery.CardFields, copier.Option{DeepCopy: true}) 47 groupView.Gallery.ShowIcon = view.Gallery.ShowIcon 48 groupView.Gallery.WrapField = view.Gallery.WrapField 49 50 groupView.Gallery.CoverFrom = view.Gallery.CoverFrom 51 groupView.Gallery.CoverFromAssetKeyID = view.Gallery.CoverFromAssetKeyID 52 groupView.Gallery.CardAspectRatio = view.Gallery.CardAspectRatio 53 groupView.Gallery.CardSize = view.Gallery.CardSize 54 groupView.Gallery.FitImage = view.Gallery.FitImage 55 groupView.Gallery.DisplayFieldName = view.Gallery.DisplayFieldName 56 case av.LayoutTypeKanban: 57 err = copier.CopyWithOption(&groupView.Kanban.Fields, &view.Kanban.Fields, copier.Option{DeepCopy: true}) 58 groupView.Kanban.ShowIcon = view.Kanban.ShowIcon 59 groupView.Kanban.WrapField = view.Kanban.WrapField 60 61 groupView.Kanban.CoverFrom = view.Kanban.CoverFrom 62 groupView.Kanban.CoverFromAssetKeyID = view.Kanban.CoverFromAssetKeyID 63 groupView.Kanban.CardAspectRatio = view.Kanban.CardAspectRatio 64 groupView.Kanban.CardSize = view.Kanban.CardSize 65 groupView.Kanban.FitImage = view.Kanban.FitImage 66 groupView.Kanban.DisplayFieldName = view.Kanban.DisplayFieldName 67 } 68 if nil != err { 69 logging.LogErrorf("copy view fields [%s] to group [%s] failed: %s", view.ID, groupView.ID, err) 70 switch groupView.LayoutType { 71 case av.LayoutTypeTable: 72 groupView.Table.Columns = view.Table.Columns 73 case av.LayoutTypeGallery: 74 groupView.Gallery.CardFields = view.Gallery.CardFields 75 case av.LayoutTypeKanban: 76 groupView.Kanban.Fields = view.Kanban.Fields 77 } 78 } 79 80 groupView.Filters = view.Filters 81 groupView.Sorts = view.Sorts 82 return RenderView(attrView, groupView, query) 83} 84 85func RenderView(attrView *av.AttributeView, view *av.View, query string) (ret av.Viewable) { 86 depth := 1 87 renderedAttrViews := map[string]*av.AttributeView{} 88 renderedAttrViews[attrView.ID] = attrView 89 ret = renderView(attrView, view, query, &depth, renderedAttrViews) 90 91 attrView.RenderedViewables[ret.GetID()] = ret 92 renderedAttrViews[attrView.ID] = attrView 93 return 94} 95 96func renderView(attrView *av.AttributeView, view *av.View, query string, depth *int, cachedAttrViews map[string]*av.AttributeView) (ret av.Viewable) { 97 if 7 < *depth { 98 return 99 } 100 101 *depth++ 102 switch view.LayoutType { 103 case av.LayoutTypeTable: 104 ret = RenderAttributeViewTable(attrView, view, query, depth, cachedAttrViews) 105 case av.LayoutTypeGallery: 106 ret = RenderAttributeViewGallery(attrView, view, query, depth, cachedAttrViews) 107 case av.LayoutTypeKanban: 108 ret = RenderAttributeViewKanban(attrView, view, query, depth, cachedAttrViews) 109 } 110 return 111} 112 113func RenderTemplateField(ial map[string]string, keyValues []*av.KeyValues, tplContent string) (ret string, err error) { 114 if "" == ial["id"] { 115 block := getBlockValue(keyValues) 116 if nil != block { 117 if nil != block.Block { 118 ial["id"] = block.Block.ID 119 } 120 if "" == ial["id"] { 121 ial["id"] = block.BlockID 122 } 123 } 124 } 125 if "" == ial["updated"] { 126 block := getBlockValue(keyValues) 127 if nil != block && nil != block.Block { 128 ial["updated"] = time.UnixMilli(block.Block.Updated).Format("20060102150405") 129 } 130 } 131 132 goTpl := template.New("").Delims(".action{", "}") 133 tplFuncMap := filesys.BuiltInTemplateFuncs() 134 SQLTemplateFuncs(&tplFuncMap) 135 goTpl = goTpl.Funcs(tplFuncMap) 136 tpl, err := goTpl.Parse(tplContent) 137 if err != nil { 138 logging.LogWarnf("parse template [%s] failed: %s", tplContent, err) 139 return 140 } 141 142 buf := &bytes.Buffer{} 143 dataModel := map[string]interface{}{} // 复制一份 IAL 以避免修改原始数据 144 for k, v := range ial { 145 dataModel[k] = v 146 147 // Database template column supports `created` and `updated` built-in variables https://github.com/siyuan-note/siyuan/issues/9364 148 createdStr := ial["id"] 149 if "" != createdStr { 150 createdStr = createdStr[:len("20060102150405")] 151 } 152 created, parseErr := time.ParseInLocation("20060102150405", createdStr, time.Local) 153 if nil == parseErr { 154 dataModel["created"] = created 155 } else { 156 errMsg := parseErr.Error() 157 //logging.LogWarnf("parse created [%s] failed: %s", createdStr, errMsg) 158 if strings.Contains(errMsg, "minute out of range") { 159 // parsing time "20240709158553": minute out of range 160 // 将分秒部分置为 0000 161 createdStr = createdStr[:len("2006010215")] + "0000" 162 } else if strings.Contains(errMsg, "second out of range") { 163 // parsing time "20240709154592": second out of range 164 // 将秒部分置为 00 165 createdStr = createdStr[:len("200601021504")] + "00" 166 } 167 created, parseErr = time.ParseInLocation("20060102150405", createdStr, time.Local) 168 } 169 if nil != parseErr { 170 logging.LogWarnf("parse created [%s] failed: %s", createdStr, parseErr) 171 dataModel["created"] = time.Now() 172 } 173 updatedStr := ial["updated"] 174 updated, parseErr := time.ParseInLocation("20060102150405", updatedStr, time.Local) 175 if nil == parseErr { 176 dataModel["updated"] = updated 177 } else { 178 dataModel["updated"] = time.Now() 179 } 180 } 181 182 dataModel["id_mod"] = map[string]any{} 183 dataModel["id_mod_raw"] = map[string]any{} 184 for _, keyValue := range keyValues { 185 if 1 > len(keyValue.Values) { 186 continue 187 } 188 189 v := keyValue.Values[0] 190 if av.KeyTypeNumber == v.Type { 191 if nil != v.Number && v.Number.IsNotEmpty { 192 dataModel[keyValue.Key.Name] = v.Number.Content 193 } 194 } else if av.KeyTypeDate == v.Type { 195 if nil != v.Date { 196 if v.Date.IsNotEmpty { 197 dataModel[keyValue.Key.Name] = time.UnixMilli(v.Date.Content) 198 } 199 if v.Date.IsNotEmpty2 { 200 dataModel[keyValue.Key.Name+"_end"] = time.UnixMilli(v.Date.Content2) 201 } 202 } 203 } else if av.KeyTypeRollup == v.Type { 204 if 0 < len(v.Rollup.Contents) { 205 var numbers []float64 206 var contents []string 207 for _, content := range v.Rollup.Contents { 208 if av.KeyTypeNumber == content.Type { 209 numbers = append(numbers, content.Number.Content) 210 } else if av.KeyTypeMSelect == content.Type { 211 for _, s := range content.MSelect { 212 contents = append(contents, s.Content) 213 } 214 } else { 215 contents = append(contents, content.String(true)) 216 } 217 } 218 219 if 0 < len(numbers) { 220 dataModel[keyValue.Key.Name] = numbers 221 } else { 222 dataModel[keyValue.Key.Name] = contents 223 } 224 } 225 } else if av.KeyTypeRelation == v.Type { 226 if 0 < len(v.Relation.Contents) { 227 var contents []string 228 for _, content := range v.Relation.Contents { 229 contents = append(contents, content.String(true)) 230 } 231 dataModel[keyValue.Key.Name] = contents 232 } 233 } else if av.KeyTypeBlock == v.Type { 234 dataModel[keyValue.Key.Name+"_created"] = time.Now() 235 if nil != v.Block { 236 dataModel["entryCreated"] = time.UnixMilli(v.Block.Created) 237 } 238 dataModel["entryUpdated"] = time.Now() 239 if nil != v.Block { 240 dataModel["entryUpdated"] = time.UnixMilli(v.Block.Updated) 241 } 242 dataModel[keyValue.Key.Name] = v.String(true) 243 } else if av.KeyTypeMSelect == v.Type { 244 dataModel[keyValue.Key.Name+"_str"] = v.String(true) 245 var contents []string 246 for _, s := range v.MSelect { 247 contents = append(contents, s.Content) 248 } 249 dataModel[keyValue.Key.Name] = contents 250 } else { 251 dataModel[keyValue.Key.Name] = v.String(true) 252 } 253 254 // Database template fields support access to the raw value https://github.com/siyuan-note/siyuan/issues/14903 255 dataModel[keyValue.Key.Name+"_raw"] = v 256 257 // Database template fields support access by ID https://github.com/siyuan-note/siyuan/issues/11237 258 dataModel["id_mod"].(map[string]any)[keyValue.Key.ID] = dataModel[keyValue.Key.Name] 259 dataModel["id_mod_raw"].(map[string]any)[keyValue.Key.ID] = v 260 } 261 262 if err = tpl.Execute(buf, dataModel); err != nil { 263 logging.LogWarnf("execute template [%s] failed: %s", tplContent, err) 264 return 265 } 266 ret = buf.String() 267 if ret == "<no value>" { 268 ret = "" 269 } 270 return 271} 272 273func generateAttrViewItems(attrView *av.AttributeView, view *av.View) (ret map[string][]*av.KeyValues) { 274 ret = map[string][]*av.KeyValues{} 275 for _, keyValues := range attrView.KeyValues { 276 for _, val := range keyValues.Values { 277 values := ret[val.BlockID] 278 if nil == values { 279 values = []*av.KeyValues{{Key: keyValues.Key, Values: []*av.Value{val}}} 280 } else { 281 values = append(values, &av.KeyValues{Key: keyValues.Key, Values: []*av.Value{val}}) 282 } 283 ret[val.BlockID] = values 284 } 285 } 286 287 // 如果是分组视图,则需要过滤掉不在分组中的项目 288 if nil != view.GroupItemIDs { 289 tmp := map[string][]*av.KeyValues{} 290 for _, groupItemID := range view.GroupItemIDs { 291 if _, ok := ret[groupItemID]; ok { 292 tmp[groupItemID] = ret[groupItemID] 293 } 294 } 295 ret = tmp 296 } 297 return 298} 299 300func filterNotFoundAttrViewItems(keyValuesMap map[string][]*av.KeyValues) { 301 var notFound []string 302 var toCheckBlockIDs []string 303 for blockID, keyValues := range keyValuesMap { 304 blockValue := getBlockValue(keyValues) 305 if nil == blockValue || nil == blockValue.Block { 306 notFound = append(notFound, blockID) 307 continue 308 } 309 310 if blockValue.IsDetached { 311 continue 312 } 313 314 if "" == blockValue.Block.ID { 315 notFound = append(notFound, blockID) 316 continue 317 } 318 319 toCheckBlockIDs = append(toCheckBlockIDs, blockValue.Block.ID) 320 } 321 checkRet := treenode.ExistBlockTrees(toCheckBlockIDs) 322 for blockID, exist := range checkRet { 323 if !exist { 324 notFound = append(notFound, blockID) 325 } 326 } 327 for _, blockID := range notFound { 328 delete(keyValuesMap, blockID) 329 } 330} 331 332func fillAttributeViewBaseValue(baseValue *av.BaseValue, fieldID, itemID string, fieldNumberFormat av.NumberFormat, fieldTemplate string, fieldDateIsTime bool) { 333 switch baseValue.ValueType { 334 case av.KeyTypeNumber: // 格式化数字 335 if nil != baseValue.Value && nil != baseValue.Value.Number && baseValue.Value.Number.IsNotEmpty { 336 baseValue.Value.Number.Format = fieldNumberFormat 337 baseValue.Value.Number.FormatNumber() 338 } 339 case av.KeyTypeTemplate: // 渲染模板字段 340 baseValue.Value = &av.Value{ID: baseValue.ID, KeyID: fieldID, BlockID: itemID, Type: av.KeyTypeTemplate, Template: &av.ValueTemplate{Content: fieldTemplate}} 341 case av.KeyTypeCreated: // 填充创建时间字段值,后面再渲染 342 baseValue.Value = &av.Value{ID: baseValue.ID, KeyID: fieldID, BlockID: itemID, Type: av.KeyTypeCreated} 343 case av.KeyTypeUpdated: // 填充更新时间字段值,后面再渲染 344 baseValue.Value = &av.Value{ID: baseValue.ID, KeyID: fieldID, BlockID: itemID, Type: av.KeyTypeUpdated} 345 } 346 347 if nil == baseValue.Value { 348 baseValue.Value = av.GetAttributeViewDefaultValue(baseValue.ID, fieldID, itemID, baseValue.ValueType, fieldDateIsTime) 349 } else { 350 FillAttributeViewNilValue(baseValue.Value, baseValue.ValueType) 351 } 352} 353 354func fillAttributeViewAutoGeneratedValues(attrView *av.AttributeView, collection av.Collection, ials map[string]map[string]string, depth *int, cachedAttrViews map[string]*av.AttributeView) { 355 // 渲染主键、创建时间、更新时间 356 357 for _, item := range collection.GetItems() { 358 for _, value := range item.GetValues() { 359 itemID := item.GetID() 360 361 switch value.Type { 362 case av.KeyTypeBlock: // 对于主键可能需要填充静态锚文本 Database-bound block primary key supports setting static anchor text https://github.com/siyuan-note/siyuan/issues/10049 363 if nil != value.Block { 364 for k, v := range ials[value.Block.ID] { 365 if k == av.NodeAttrViewStaticText+"-"+attrView.ID { 366 value.Block.Content = v 367 break 368 } 369 } 370 } 371 case av.KeyTypeCreated: // 渲染创建时间 372 ial := map[string]string{} 373 block := item.GetBlockValue() 374 if nil != block { 375 ial = ials[block.Block.ID] 376 } 377 if nil == ial { 378 ial = map[string]string{} 379 } 380 id := itemID 381 if "" != ial["id"] { 382 id = ial["id"] 383 } 384 createdStr := id[:len("20060102150405")] 385 created, parseErr := time.ParseInLocation("20060102150405", createdStr, time.Local) 386 if nil == parseErr { 387 value.Created = av.NewFormattedValueCreated(created.UnixMilli(), 0, av.CreatedFormatNone) 388 value.Created.IsNotEmpty = true 389 } else { 390 value.Created = av.NewFormattedValueCreated(time.Now().UnixMilli(), 0, av.CreatedFormatNone) 391 } 392 case av.KeyTypeUpdated: // 渲染更新时间 393 ial := map[string]string{} 394 block := item.GetBlockValue() 395 if nil != block { 396 ial = ials[block.Block.ID] 397 } 398 if nil == ial { 399 ial = map[string]string{} 400 } 401 updatedStr := ial["updated"] 402 if "" == updatedStr && nil != block { 403 value.Updated = av.NewFormattedValueUpdated(block.Block.Updated, 0, av.UpdatedFormatNone) 404 value.Updated.IsNotEmpty = true 405 } else { 406 updated, parseErr := time.ParseInLocation("20060102150405", updatedStr, time.Local) 407 if nil == parseErr { 408 value.Updated = av.NewFormattedValueUpdated(updated.UnixMilli(), 0, av.UpdatedFormatNone) 409 value.Updated.IsNotEmpty = true 410 } else { 411 value.Updated = av.NewFormattedValueUpdated(time.Now().UnixMilli(), 0, av.UpdatedFormatNone) 412 } 413 } 414 } 415 } 416 } 417 418 // 渲染关联 419 for _, item := range collection.GetItems() { 420 for _, value := range item.GetValues() { 421 if av.KeyTypeRelation != value.Type { 422 continue 423 } 424 425 value.Relation.Contents = nil 426 relKey, _ := attrView.GetKey(value.KeyID) 427 if nil != relKey && nil != relKey.Relation { 428 destAv := cachedAttrViews[relKey.Relation.AvID] 429 if nil == destAv { 430 destAv, _ = av.ParseAttributeView(relKey.Relation.AvID) 431 if nil != destAv { 432 cachedAttrViews[relKey.Relation.AvID] = destAv 433 } 434 } 435 if nil != destAv { 436 blocks := map[string]*av.Value{} 437 blockValues := destAv.GetBlockKeyValues() 438 if nil != blockValues { 439 for _, blockValue := range blockValues.Values { 440 blocks[blockValue.BlockID] = blockValue 441 } 442 for _, blockID := range value.Relation.BlockIDs { 443 if val := blocks[blockID]; nil != val { 444 value.Relation.Contents = append(value.Relation.Contents, val) 445 } 446 } 447 } 448 } 449 } 450 } 451 } 452 453 // 渲染汇总 454 rollupFurtherCollections := map[string]av.Collection{} 455 for _, field := range collection.GetFields() { 456 if av.KeyTypeRollup != field.GetType() { 457 continue 458 } 459 460 rollupKey, _ := attrView.GetKey(field.GetID()) 461 if nil == rollupKey || nil == rollupKey.Rollup { 462 continue 463 } 464 465 relKey, _ := attrView.GetKey(rollupKey.Rollup.RelationKeyID) 466 if nil == relKey || nil == relKey.Relation { 467 continue 468 } 469 470 destAv := cachedAttrViews[relKey.Relation.AvID] 471 if nil == destAv { 472 destAv, _ = av.ParseAttributeView(relKey.Relation.AvID) 473 if nil != destAv { 474 cachedAttrViews[relKey.Relation.AvID] = destAv 475 } 476 } 477 if nil == destAv { 478 continue 479 } 480 481 destKey, _ := destAv.GetKey(rollupKey.Rollup.KeyID) 482 if nil == destKey { 483 continue 484 } 485 486 isSameAv := destAv.ID == attrView.ID 487 var furtherCollection av.Collection 488 if av.KeyTypeTemplate == destKey.Type || (!isSameAv && (av.KeyTypeUpdated == destKey.Type || av.KeyTypeCreated == destKey.Type || av.KeyTypeRelation == destKey.Type)) { 489 viewable := renderView(destAv, destAv.Views[0], "", depth, cachedAttrViews) 490 if nil != viewable { 491 furtherCollection = viewable.(av.Collection) 492 } else { 493 fillAttributeViewTemplateValues(destAv, destAv.Views[0], collection, ials) 494 furtherCollection = collection 495 } 496 } 497 rollupFurtherCollections[rollupKey.ID] = furtherCollection 498 } 499 500 for _, item := range collection.GetItems() { 501 for _, value := range item.GetValues() { 502 if av.KeyTypeRollup != value.Type { 503 continue 504 } 505 506 rollupKey, _ := attrView.GetKey(value.KeyID) 507 if nil == rollupKey || nil == rollupKey.Rollup { 508 break 509 } 510 511 relKey, _ := attrView.GetKey(rollupKey.Rollup.RelationKeyID) 512 if nil == relKey || nil == relKey.Relation { 513 break 514 } 515 516 relVal := attrView.GetValue(relKey.ID, item.GetID()) 517 if nil == relVal || nil == relVal.Relation { 518 break 519 } 520 521 destAv := cachedAttrViews[relKey.Relation.AvID] 522 if nil == destAv { 523 destAv, _ = av.ParseAttributeView(relKey.Relation.AvID) 524 if nil != destAv { 525 cachedAttrViews[relKey.Relation.AvID] = destAv 526 } 527 } 528 if nil == destAv { 529 break 530 } 531 532 destKey, _ := destAv.GetKey(rollupKey.Rollup.KeyID) 533 if nil == destKey { 534 break 535 } 536 537 furtherCollection := rollupFurtherCollections[rollupKey.ID] 538 value.Rollup.BuildContents(destAv.KeyValues, destKey, relVal, rollupKey.Rollup.Calc, furtherCollection) 539 } 540 } 541} 542 543func GetFurtherCollections(attrView *av.AttributeView, cachedAttrViews map[string]*av.AttributeView) (ret map[string]av.Collection) { 544 ret = map[string]av.Collection{} 545 for _, kv := range attrView.KeyValues { 546 if av.KeyTypeRollup != kv.Key.Type { 547 continue 548 } 549 550 relKey, _ := attrView.GetKey(kv.Key.Rollup.RelationKeyID) 551 if nil == relKey { 552 continue 553 } 554 555 destAv := cachedAttrViews[relKey.Relation.AvID] 556 if nil == destAv { 557 destAv, _ = av.ParseAttributeView(relKey.Relation.AvID) 558 if nil == destAv { 559 continue 560 } 561 cachedAttrViews[relKey.Relation.AvID] = destAv 562 } 563 564 destKey, _ := destAv.GetKey(kv.Key.Rollup.KeyID) 565 if nil == destKey { 566 continue 567 } 568 isSameAv := destAv.ID == attrView.ID 569 570 var furtherCollection av.Collection 571 if av.KeyTypeTemplate == destKey.Type || (!isSameAv && (av.KeyTypeUpdated == destKey.Type || av.KeyTypeCreated == destKey.Type || av.KeyTypeRelation == destKey.Type)) { 572 viewable := RenderView(destAv, destAv.Views[0], "") 573 if nil != viewable { 574 furtherCollection = viewable.(av.Collection) 575 } 576 } 577 ret[kv.Key.ID] = furtherCollection 578 } 579 return 580} 581 582func fillAttributeViewTemplateValues(attrView *av.AttributeView, view *av.View, collection av.Collection, ials map[string]map[string]string) (err error) { 583 items := generateAttrViewItems(attrView, view) 584 existTemplateField := false 585 for _, kVals := range attrView.KeyValues { 586 if av.KeyTypeTemplate == kVals.Key.Type { 587 existTemplateField = true 588 break 589 } 590 } 591 if !existTemplateField { 592 return 593 } 594 595 templateKeys, _ := GetTemplateKeysByResolutionOrder(attrView) 596 for _, templateKey := range templateKeys { 597 for _, item := range collection.GetItems() { 598 value := item.GetValue(templateKey.ID) 599 if nil == value || nil == value.Template { 600 continue 601 } 602 603 keyValues := items[item.GetID()] 604 var ial map[string]string 605 blockVal := item.GetBlockValue() 606 if nil != blockVal { 607 ial = ials[blockVal.Block.ID] 608 } 609 if nil == ial { 610 ial = map[string]string{} 611 } 612 613 content, renderErr := RenderTemplateField(ial, keyValues, value.Template.Content) 614 if nil != renderErr { 615 key, _ := attrView.GetKey(value.KeyID) 616 keyName := "" 617 if nil != key { 618 keyName = key.Name 619 } 620 err = fmt.Errorf("database [%s] template field [%s] rendering failed: %s", getAttrViewName(attrView), keyName, renderErr) 621 } 622 623 value.Template.Content = content 624 items[item.GetID()] = append(keyValues, &av.KeyValues{Key: templateKey, Values: []*av.Value{value}}) 625 } 626 } 627 return 628} 629 630func fillAttributeViewKeyValues(attrView *av.AttributeView, collection av.Collection) { 631 fieldValues := map[string][]*av.Value{} 632 for _, item := range collection.GetItems() { 633 for _, val := range item.GetValues() { 634 keyID := val.KeyID 635 fieldValues[keyID] = append(fieldValues[keyID], val) 636 } 637 } 638 for keyID, values := range fieldValues { 639 keyValues, _ := attrView.GetKeyValues(keyID) 640 for _, val := range values { 641 exist := false 642 for _, kv := range keyValues.Values { 643 if kv.ID == val.ID { 644 exist = true 645 break 646 } 647 } 648 if !exist { 649 val.IsRenderAutoFill = true 650 keyValues.Values = append(keyValues.Values, val) 651 } 652 } 653 } 654} 655 656func FillAttributeViewNilValue(value *av.Value, typ av.KeyType) { 657 value.Type = typ 658 switch typ { 659 case av.KeyTypeText: 660 if nil == value.Text { 661 value.Text = &av.ValueText{} 662 } 663 case av.KeyTypeNumber: 664 if nil == value.Number { 665 value.Number = &av.ValueNumber{} 666 } 667 case av.KeyTypeDate: 668 if nil == value.Date { 669 value.Date = &av.ValueDate{} 670 } 671 case av.KeyTypeSelect: 672 if 1 > len(value.MSelect) { 673 value.MSelect = []*av.ValueSelect{} 674 } 675 case av.KeyTypeMSelect: 676 if 1 > len(value.MSelect) { 677 value.MSelect = []*av.ValueSelect{} 678 } 679 case av.KeyTypeURL: 680 if nil == value.URL { 681 value.URL = &av.ValueURL{} 682 } 683 case av.KeyTypeEmail: 684 if nil == value.Email { 685 value.Email = &av.ValueEmail{} 686 } 687 case av.KeyTypePhone: 688 if nil == value.Phone { 689 value.Phone = &av.ValuePhone{} 690 } 691 case av.KeyTypeMAsset: 692 if 1 > len(value.MAsset) { 693 value.MAsset = []*av.ValueAsset{} 694 } 695 case av.KeyTypeTemplate: 696 if nil == value.Template { 697 value.Template = &av.ValueTemplate{} 698 } 699 case av.KeyTypeCreated: 700 if nil == value.Created { 701 value.Created = &av.ValueCreated{} 702 } 703 case av.KeyTypeUpdated: 704 if nil == value.Updated { 705 value.Updated = &av.ValueUpdated{} 706 } 707 case av.KeyTypeCheckbox: 708 if nil == value.Checkbox { 709 value.Checkbox = &av.ValueCheckbox{} 710 } 711 case av.KeyTypeRelation: 712 if nil == value.Relation { 713 value.Relation = &av.ValueRelation{} 714 } 715 case av.KeyTypeRollup: 716 if nil == value.Rollup { 717 value.Rollup = &av.ValueRollup{} 718 } 719 } 720} 721 722func getAttributeViewContent(avID string) (content string) { 723 if "" == avID { 724 return 725 } 726 727 attrView, err := av.ParseAttributeView(avID) 728 if err != nil { 729 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err) 730 return 731 } 732 733 buf := bytes.Buffer{} 734 buf.WriteString(attrView.Name) 735 buf.WriteByte(' ') 736 for _, v := range attrView.Views { 737 buf.WriteString(v.Name) 738 buf.WriteByte(' ') 739 } 740 741 for _, keyValues := range attrView.KeyValues { 742 buf.WriteString(keyValues.Key.Name) 743 buf.WriteByte(' ') 744 for _, value := range keyValues.Values { 745 if nil != value { 746 buf.WriteString(value.String(true)) 747 buf.WriteByte(' ') 748 } 749 } 750 } 751 752 content = strings.TrimSpace(buf.String()) 753 return 754} 755 756func getBlockValue(keyValues []*av.KeyValues) (ret *av.Value) { 757 for _, kv := range keyValues { 758 if av.KeyTypeBlock == kv.Key.Type && 0 < len(kv.Values) { 759 ret = kv.Values[0] 760 break 761 } 762 } 763 return 764} 765 766func getAttrViewName(attrView *av.AttributeView) string { 767 ret := strings.TrimSpace(attrView.Name) 768 if "" == ret { 769 ret = util.Langs[util.Lang][105] 770 } 771 return ret 772} 773 774func removeMissingField(attrView *av.AttributeView, view *av.View, missingKeyID string) { 775 logging.LogWarnf("key [%s] is missing", missingKeyID) 776 777 changed := false 778 if nil != view.Table { 779 for i, column := range view.Table.Columns { 780 if column.ID == missingKeyID { 781 view.Table.Columns = append(view.Table.Columns[:i], view.Table.Columns[i+1:]...) 782 changed = true 783 break 784 } 785 } 786 } 787 788 if nil != view.Gallery { 789 for i, cardField := range view.Gallery.CardFields { 790 if cardField.ID == missingKeyID { 791 view.Gallery.CardFields = append(view.Gallery.CardFields[:i], view.Gallery.CardFields[i+1:]...) 792 changed = true 793 break 794 } 795 } 796 } 797 798 if nil != view.Kanban { 799 for i, kanbanField := range view.Kanban.Fields { 800 if kanbanField.ID == missingKeyID { 801 view.Kanban.Fields = append(view.Kanban.Fields[:i], view.Kanban.Fields[i+1:]...) 802 changed = true 803 break 804 } 805 } 806 } 807 808 if changed { 809 av.SaveAttributeView(attrView) 810 } 811} 812 813// filterByQuery 根据搜索条件过滤 814func filterByQuery(query string, collection av.Collection) { 815 query = strings.TrimSpace(query) 816 if "" != query { 817 query = strings.Join(strings.Fields(query), " ") // 将连续空格转换为一个空格 818 keywords := strings.Split(query, " ") // 按空格分割关键字 819 820 // 使用 AND 逻辑 https://github.com/siyuan-note/siyuan/issues/11535 821 var hitItems []av.Item 822 for _, item := range collection.GetItems() { 823 hit := false 824 for _, cell := range item.GetValues() { 825 allKeywordsHit := true 826 for _, keyword := range keywords { 827 if !strings.Contains(strings.ToLower(cell.String(true)), strings.ToLower(keyword)) { 828 allKeywordsHit = false 829 break 830 } 831 } 832 if allKeywordsHit { 833 hit = true 834 break 835 } 836 } 837 if hit { 838 hitItems = append(hitItems, item) 839 } 840 } 841 collection.SetItems(hitItems) 842 if 1 > len(collection.GetItems()) { 843 collection.SetItems([]av.Item{}) 844 } 845 } 846} 847 848// manualSort 处理用户手动排序。 849func manualSort(view *av.View, collection av.Collection) { 850 itemIDs := view.ItemIDs 851 // 如果是分组视图,则需要根据分组项的顺序进行排序 852 if 0 < len(view.GroupItemIDs) { 853 itemIDs = view.GroupItemIDs 854 } 855 856 sortItemIDs := map[string]int{} 857 for i, itemID := range itemIDs { 858 sortItemIDs[itemID] = i 859 } 860 861 items := collection.GetItems() 862 sort.Slice(items, func(i, j int) bool { 863 iv := sortItemIDs[items[i].GetID()] 864 jv := sortItemIDs[items[j].GetID()] 865 if iv == jv { 866 return items[i].GetID() < items[j].GetID() 867 } 868 return iv < jv 869 }) 870 collection.SetItems(items) 871} 872 873func GetTemplateKeysByResolutionOrder(attrView *av.AttributeView) (ret []*av.Key, resolved bool) { 874 ret = []*av.Key{} 875 876 resolvedTemplateKeys := map[string]bool{} 877 for i := 0; i < 7; i++ { 878 templateKeyCount := 0 879 for _, keyValues := range attrView.KeyValues { 880 if av.KeyTypeTemplate != keyValues.Key.Type { 881 continue 882 } 883 884 templateKeyCount++ 885 vars, err := getTemplateVars(keyValues.Key.Template) 886 if nil != err { 887 resolvedTemplateKeys[keyValues.Key.ID] = true 888 ret = append(ret, keyValues.Key) 889 continue 890 } 891 892 currentTemplateKeyResolved := true 893 for _, kValues := range attrView.KeyValues { 894 if gulu.Str.Contains(kValues.Key.Name, vars) { 895 if av.KeyTypeTemplate == kValues.Key.Type { 896 if _, ok := resolvedTemplateKeys[kValues.Key.ID]; !ok { 897 currentTemplateKeyResolved = false 898 break 899 } 900 } 901 } 902 } 903 if currentTemplateKeyResolved { 904 resolvedTemplateKeys[keyValues.Key.ID] = true 905 ret = append(ret, keyValues.Key) 906 } 907 } 908 909 resolved = len(resolvedTemplateKeys) == templateKeyCount 910 if resolved { 911 break 912 } 913 } 914 return 915} 916 917func GetTemplateKeyRelevantKeys(attrView *av.AttributeView, templateKey *av.Key) (ret []*av.Key) { 918 ret = []*av.Key{} 919 if nil == templateKey || "" == templateKey.Template { 920 return 921 } 922 923 vars, err := getTemplateVars(templateKey.Template) 924 if nil != err { 925 return 926 } 927 928 for _, kValues := range attrView.KeyValues { 929 if gulu.Str.Contains(kValues.Key.Name, vars) { 930 ret = append(ret, kValues.Key) 931 } 932 } 933 934 if 1 > len(ret) { 935 // 没有相关字段情况下直接尝试解析模板,如果能解析成功则返回模板字段本身 https://github.com/siyuan-note/siyuan/issues/15560#issuecomment-3182691193 936 goTpl := template.New("").Delims(".action{", "}") 937 tplFuncMap := filesys.BuiltInTemplateFuncs() 938 SQLTemplateFuncs(&tplFuncMap) 939 goTpl = goTpl.Funcs(tplFuncMap) 940 _, parseErr := goTpl.Funcs(tplFuncMap).Parse(templateKey.Template) 941 if nil != parseErr { 942 return 943 } 944 ret = append(ret, templateKey) 945 } 946 return 947} 948 949func getTemplateVars(tplContent string) ([]string, error) { 950 goTpl := template.New("").Delims(".action{", "}") 951 tplFuncMap := filesys.BuiltInTemplateFuncs() 952 SQLTemplateFuncs(&tplFuncMap) 953 goTpl = goTpl.Funcs(tplFuncMap) 954 tpl, parseErr := goTpl.Funcs(tplFuncMap).Parse(tplContent) 955 if parseErr != nil { 956 return nil, parseErr 957 } 958 vars := make(map[string]struct{}) 959 collectVars(tpl.Tree.Root, vars) 960 var result []string 961 for v := range vars { 962 result = append(result, v) 963 } 964 return result, nil 965} 966 967func collectVars(node parse.Node, vars map[string]struct{}) { 968 switch n := node.(type) { 969 case *parse.ListNode: 970 for _, child := range n.Nodes { 971 collectVars(child, vars) 972 } 973 case *parse.ActionNode: 974 collectVars(n.Pipe, vars) 975 case *parse.PipeNode: 976 for _, cmd := range n.Cmds { 977 collectVars(cmd, vars) 978 } 979 case *parse.CommandNode: 980 for _, arg := range n.Args { 981 collectVars(arg, vars) 982 } 983 984 if 3 <= len(n.Args) && n.Args[0].Type() == parse.NodeIdentifier && n.Args[1].Type() == parse.NodeDot && n.Args[2].Type() == parse.NodeString { 985 vars[n.Args[2].(*parse.StringNode).Text] = struct{}{} 986 } 987 988 case *parse.FieldNode: 989 if len(n.Ident) > 0 { 990 vars[n.Ident[0]] = struct{}{} 991 } 992 case *parse.VariableNode: 993 if len(n.Ident) > 0 { 994 vars[n.Ident[0]] = struct{}{} 995 } 996 } 997}