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