浏览代码

Merge remote-tracking branch 'origin/dev' into dev

Vanessa 1 年之前
父节点
当前提交
df38f89f40
共有 3 个文件被更改,包括 123 次插入21 次删除
  1. 32 19
      kernel/av/av.go
  2. 65 0
      kernel/av/table.go
  3. 26 2
      kernel/model/attribute_view.go

+ 32 - 19
kernel/av/av.go

@@ -55,16 +55,17 @@ type KeyValues struct {
 type KeyType string
 type KeyType string
 
 
 const (
 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 描述了属性视图属性列的基础结构。
 // Key 描述了属性视图属性列的基础结构。
@@ -100,15 +101,16 @@ type Value struct {
 	Type       KeyType `json:"type,omitempty"`
 	Type       KeyType `json:"type,omitempty"`
 	IsDetached bool    `json:"isDetached,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 {
 func (value *Value) String() string {
@@ -139,6 +141,8 @@ func (value *Value) String() string {
 			ret = append(ret, v.Content)
 			ret = append(ret, v.Content)
 		}
 		}
 		return strings.Join(ret, " ")
 		return strings.Join(ret, " ")
+	case KeyTypeTemplate:
+		return value.Template.RenderedContent
 	default:
 	default:
 		return ""
 		return ""
 	}
 	}
@@ -345,6 +349,15 @@ type ValueAsset struct {
 	Content string    `json:"content"`
 	Content string    `json:"content"`
 }
 }
 
 
+type ValueTemplate struct {
+	Content         string `json:"content"`
+	RenderedContent string `json:"renderedContent"`
+}
+
+func (t *ValueTemplate) Render(blockID string, r func(blockID string) string) {
+	t.RenderedContent = r(blockID)
+}
+
 // View 描述了视图的结构。
 // View 描述了视图的结构。
 type View struct {
 type View struct {
 	ID   string `json:"id"`   // 视图 ID
 	ID   string `json:"id"`   // 视图 ID

+ 65 - 0
kernel/av/table.go

@@ -509,6 +509,71 @@ func (table *Table) CalcCols() {
 			table.calcColPhone(col, i)
 			table.calcColPhone(col, i)
 		case KeyTypeMAsset:
 		case KeyTypeMAsset:
 			table.calcColMAsset(col, i)
 			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)}
 		}
 		}
 	}
 	}
 }
 }

+ 26 - 2
kernel/model/attribute_view.go

@@ -17,12 +17,15 @@
 package model
 package model
 
 
 import (
 import (
+	"bytes"
 	"sort"
 	"sort"
 	"strings"
 	"strings"
+	"text/template"
 
 
 	"github.com/88250/gulu"
 	"github.com/88250/gulu"
 	"github.com/88250/lute/ast"
 	"github.com/88250/lute/ast"
 	"github.com/88250/lute/parse"
 	"github.com/88250/lute/parse"
+	"github.com/Masterminds/sprig/v3"
 	"github.com/siyuan-note/logging"
 	"github.com/siyuan-note/logging"
 	"github.com/siyuan-note/siyuan/kernel/av"
 	"github.com/siyuan-note/siyuan/kernel/av"
 	"github.com/siyuan-note/siyuan/kernel/treenode"
 	"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()
 				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)
 			tableRow.Cells = append(tableRow.Cells, tableCell)
 		}
 		}
 		ret.Rows = append(ret.Rows, &tableRow)
 		ret.Rows = append(ret.Rows, &tableRow)
@@ -788,7 +812,7 @@ func addAttributeViewColumn(operation *Operation) (err error) {
 
 
 	keyType := av.KeyType(operation.Typ)
 	keyType := av.KeyType(operation.Typ)
 	switch keyType {
 	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)
 		key := av.NewKey(operation.ID, operation.Name, keyType)
 		attrView.KeyValues = append(attrView.KeyValues, &av.KeyValues{Key: key})
 		attrView.KeyValues = append(attrView.KeyValues, &av.KeyValues{Key: key})
 
 
@@ -847,7 +871,7 @@ func updateAttributeViewColumn(operation *Operation) (err error) {
 
 
 	colType := av.KeyType(operation.Typ)
 	colType := av.KeyType(operation.Typ)
 	switch colType {
 	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 {
 		for _, keyValues := range attrView.KeyValues {
 			if keyValues.Key.ID == operation.ID {
 			if keyValues.Key.ID == operation.ID {
 				keyValues.Key.Name = operation.Name
 				keyValues.Key.Name = operation.Name