From c0424caf679ae463aef8b39e003b256263f0e978 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Sun, 1 Oct 2023 10:42:12 +0800 Subject: [PATCH 1/2] :art: Add template type column to Attribute View https://github.com/siyuan-note/siyuan/issues/8766 --- kernel/av/av.go | 47 ++++++++++++++---------- kernel/av/table.go | 65 ++++++++++++++++++++++++++++++++++ kernel/model/attribute_view.go | 4 +-- 3 files changed, 95 insertions(+), 21 deletions(-) diff --git a/kernel/av/av.go b/kernel/av/av.go index 2d50e2848..a8794f382 100644 --- a/kernel/av/av.go +++ b/kernel/av/av.go @@ -55,16 +55,17 @@ type KeyValues struct { type KeyType string const ( - KeyTypeBlock KeyType = "block" - KeyTypeText KeyType = "text" - KeyTypeNumber KeyType = "number" - KeyTypeDate KeyType = "date" - KeyTypeSelect KeyType = "select" - KeyTypeMSelect KeyType = "mSelect" - KeyTypeURL KeyType = "url" - KeyTypeEmail KeyType = "email" - KeyTypePhone KeyType = "phone" - KeyTypeMAsset KeyType = "mAsset" + KeyTypeBlock KeyType = "block" + KeyTypeText KeyType = "text" + KeyTypeNumber KeyType = "number" + KeyTypeDate KeyType = "date" + KeyTypeSelect KeyType = "select" + KeyTypeMSelect KeyType = "mSelect" + KeyTypeURL KeyType = "url" + KeyTypeEmail KeyType = "email" + KeyTypePhone KeyType = "phone" + KeyTypeMAsset KeyType = "mAsset" + KeyTypeTemplate KeyType = "template" ) // Key 描述了属性视图属性列的基础结构。 @@ -100,15 +101,16 @@ type Value struct { Type KeyType `json:"type,omitempty"` IsDetached bool `json:"isDetached,omitempty"` - Block *ValueBlock `json:"block,omitempty"` - Text *ValueText `json:"text,omitempty"` - Number *ValueNumber `json:"number,omitempty"` - Date *ValueDate `json:"date,omitempty"` - MSelect []*ValueSelect `json:"mSelect,omitempty"` - URL *ValueURL `json:"url,omitempty"` - Email *ValueEmail `json:"email,omitempty"` - Phone *ValuePhone `json:"phone,omitempty"` - MAsset []*ValueAsset `json:"mAsset,omitempty"` + Block *ValueBlock `json:"block,omitempty"` + Text *ValueText `json:"text,omitempty"` + Number *ValueNumber `json:"number,omitempty"` + Date *ValueDate `json:"date,omitempty"` + MSelect []*ValueSelect `json:"mSelect,omitempty"` + URL *ValueURL `json:"url,omitempty"` + Email *ValueEmail `json:"email,omitempty"` + Phone *ValuePhone `json:"phone,omitempty"` + MAsset []*ValueAsset `json:"mAsset,omitempty"` + Template *ValueTemplate `json:"template,omitempty"` } func (value *Value) String() string { @@ -139,6 +141,8 @@ func (value *Value) String() string { ret = append(ret, v.Content) } return strings.Join(ret, " ") + case KeyTypeTemplate: + return value.Template.RenderedContent default: return "" } @@ -345,6 +349,11 @@ type ValueAsset struct { Content string `json:"content"` } +type ValueTemplate struct { + Content string `json:"content"` + RenderedContent string `json:"renderedContent"` +} + // View 描述了视图的结构。 type View struct { ID string `json:"id"` // 视图 ID diff --git a/kernel/av/table.go b/kernel/av/table.go index 6bc97e8bc..f8116e691 100644 --- a/kernel/av/table.go +++ b/kernel/av/table.go @@ -509,6 +509,71 @@ func (table *Table) CalcCols() { table.calcColPhone(col, i) case KeyTypeMAsset: table.calcColMAsset(col, i) + case KeyTypeTemplate: + table.calcColTemplate(col, i) + } + } +} + +func (table *Table) calcColTemplate(col *TableColumn, colIndex int) { + switch col.Calc.Operator { + case CalcOperatorCountAll: + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(len(table.Rows)), NumberFormatNone)} + case CalcOperatorCountValues: + countValues := 0 + for _, row := range table.Rows { + if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Template && "" != row.Cells[colIndex].Value.Template.RenderedContent { + countValues++ + } + } + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countValues), NumberFormatNone)} + case CalcOperatorCountUniqueValues: + countUniqueValues := 0 + uniqueValues := map[string]bool{} + for _, row := range table.Rows { + if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Template && "" != row.Cells[colIndex].Value.Template.RenderedContent { + if !uniqueValues[row.Cells[colIndex].Value.Template.RenderedContent] { + uniqueValues[row.Cells[colIndex].Value.Template.RenderedContent] = true + countUniqueValues++ + } + } + } + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countUniqueValues), NumberFormatNone)} + case CalcOperatorCountEmpty: + countEmpty := 0 + for _, row := range table.Rows { + if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Template || "" == row.Cells[colIndex].Value.Template.RenderedContent { + countEmpty++ + } + } + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countEmpty), NumberFormatNone)} + case CalcOperatorCountNotEmpty: + countNotEmpty := 0 + for _, row := range table.Rows { + if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Template && "" != row.Cells[colIndex].Value.Template.RenderedContent { + countNotEmpty++ + } + } + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty), NumberFormatNone)} + case CalcOperatorPercentEmpty: + countEmpty := 0 + for _, row := range table.Rows { + if nil == row.Cells[colIndex] || nil == row.Cells[colIndex].Value || nil == row.Cells[colIndex].Value.Template || "" == row.Cells[colIndex].Value.Template.RenderedContent { + countEmpty++ + } + } + if 0 < len(table.Rows) { + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countEmpty)/float64(len(table.Rows)), NumberFormatPercent)} + } + case CalcOperatorPercentNotEmpty: + countNotEmpty := 0 + for _, row := range table.Rows { + if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Template && "" != row.Cells[colIndex].Value.Template.RenderedContent { + countNotEmpty++ + } + } + if 0 < len(table.Rows) { + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)} } } } diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go index ea549adbe..9278b8458 100644 --- a/kernel/model/attribute_view.go +++ b/kernel/model/attribute_view.go @@ -788,7 +788,7 @@ func addAttributeViewColumn(operation *Operation) (err error) { keyType := av.KeyType(operation.Typ) switch keyType { - case av.KeyTypeText, av.KeyTypeNumber, av.KeyTypeDate, av.KeyTypeSelect, av.KeyTypeMSelect, av.KeyTypeURL, av.KeyTypeEmail, av.KeyTypePhone, av.KeyTypeMAsset: + case av.KeyTypeText, av.KeyTypeNumber, av.KeyTypeDate, av.KeyTypeSelect, av.KeyTypeMSelect, av.KeyTypeURL, av.KeyTypeEmail, av.KeyTypePhone, av.KeyTypeMAsset, av.KeyTypeTemplate: key := av.NewKey(operation.ID, operation.Name, keyType) attrView.KeyValues = append(attrView.KeyValues, &av.KeyValues{Key: key}) @@ -847,7 +847,7 @@ func updateAttributeViewColumn(operation *Operation) (err error) { colType := av.KeyType(operation.Typ) switch colType { - case av.KeyTypeBlock, av.KeyTypeText, av.KeyTypeNumber, av.KeyTypeDate, av.KeyTypeSelect, av.KeyTypeMSelect, av.KeyTypeURL, av.KeyTypeEmail, av.KeyTypePhone, av.KeyTypeMAsset: + case av.KeyTypeBlock, av.KeyTypeText, av.KeyTypeNumber, av.KeyTypeDate, av.KeyTypeSelect, av.KeyTypeMSelect, av.KeyTypeURL, av.KeyTypeEmail, av.KeyTypePhone, av.KeyTypeMAsset, av.KeyTypeTemplate: for _, keyValues := range attrView.KeyValues { if keyValues.Key.ID == operation.ID { keyValues.Key.Name = operation.Name From b69e8d335726fb64e35b4d80b844e4e30e5d6344 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Sun, 1 Oct 2023 10:58:46 +0800 Subject: [PATCH 2/2] :art: Add template type column to Attribute View https://github.com/siyuan-note/siyuan/issues/8766 --- kernel/av/av.go | 4 ++++ kernel/model/attribute_view.go | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/kernel/av/av.go b/kernel/av/av.go index a8794f382..385692a8a 100644 --- a/kernel/av/av.go +++ b/kernel/av/av.go @@ -354,6 +354,10 @@ type ValueTemplate struct { RenderedContent string `json:"renderedContent"` } +func (t *ValueTemplate) Render(blockID string, r func(blockID string) string) { + t.RenderedContent = r(blockID) +} + // View 描述了视图的结构。 type View struct { ID string `json:"id"` // 视图 ID diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go index 9278b8458..e25cb123d 100644 --- a/kernel/model/attribute_view.go +++ b/kernel/model/attribute_view.go @@ -17,12 +17,15 @@ package model import ( + "bytes" "sort" "strings" + "text/template" "github.com/88250/gulu" "github.com/88250/lute/ast" "github.com/88250/lute/parse" + "github.com/Masterminds/sprig/v3" "github.com/siyuan-note/logging" "github.com/siyuan-note/siyuan/kernel/av" "github.com/siyuan-note/siyuan/kernel/treenode" @@ -227,6 +230,27 @@ func renderAttributeViewTable(attrView *av.AttributeView, view *av.View) (ret *a tableCell.Value.Number.FormatNumber() } + // 渲染模板列 + if av.KeyTypeTemplate == tableCell.ValueType && nil != tableCell.Value && nil != tableCell.Value.Template { + render := func(blockID string) string { + funcMap := sprig.TxtFuncMap() + goTpl := template.New("").Delims(".action{", "}") + tpl, tplErr := goTpl.Funcs(funcMap).Parse(tableCell.Value.Template.Content) + if nil != tplErr { + logging.LogWarnf("parse template [%s] failed: %s", tableCell.Value.Template.Content, err) + } + + buf := &bytes.Buffer{} + ial := GetBlockAttrs(blockID) + if err = tpl.Execute(buf, ial); nil != err { + logging.LogWarnf("execute template [%s] failed: %s", tableCell.Value.Template.Content, err) + } + return buf.String() + } + + tableCell.Value.Template.Render(tableCell.Value.BlockID, render) + } + tableRow.Cells = append(tableRow.Cells, tableCell) } ret.Rows = append(ret.Rows, &tableRow)