From 917e21f25e6799f4903d808f1c46f87c6883b26c Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Sun, 8 Oct 2023 12:16:58 +0800 Subject: [PATCH] :art: Add created and updated type column to database https://github.com/siyuan-note/siyuan/issues/9371 --- kernel/av/av.go | 78 ++++++++++++ kernel/av/table.go | 210 +++++++++++++++++++++++++++++++++ kernel/model/attribute_view.go | 4 +- kernel/model/export.go | 14 ++- 4 files changed, 302 insertions(+), 4 deletions(-) diff --git a/kernel/av/av.go b/kernel/av/av.go index 934080159..e929072b0 100644 --- a/kernel/av/av.go +++ b/kernel/av/av.go @@ -66,6 +66,8 @@ const ( KeyTypePhone KeyType = "phone" KeyTypeMAsset KeyType = "mAsset" KeyTypeTemplate KeyType = "template" + KeyTypeCreated KeyType = "created" + KeyTypeUpdated KeyType = "updated" ) // Key 描述了属性视图属性列的基础结构。 @@ -113,6 +115,8 @@ type Value struct { Phone *ValuePhone `json:"phone,omitempty"` MAsset []*ValueAsset `json:"mAsset,omitempty"` Template *ValueTemplate `json:"template,omitempty"` + Created *ValueCreated `json:"created,omitempty"` + Updated *ValueUpdated `json:"updated,omitempty"` } func (value *Value) String() string { @@ -145,6 +149,10 @@ func (value *Value) String() string { return strings.Join(ret, " ") case KeyTypeTemplate: return value.Template.Content + case KeyTypeCreated: + return value.Created.FormattedContent + case KeyTypeUpdated: + return value.Created.FormattedContent default: return "" } @@ -355,6 +363,76 @@ type ValueTemplate struct { Content string `json:"content"` } +type ValueCreated struct { + Content int64 `json:"content"` + IsNotEmpty bool `json:"isNotEmpty"` + Content2 int64 `json:"content2"` + IsNotEmpty2 bool `json:"isNotEmpty2"` + FormattedContent string `json:"formattedContent"` +} + +type CreatedFormat string + +const ( + CreatedFormatNone CreatedFormat = "" // 2006-01-02 15:04 + CreatedFormatDuration CreatedFormat = "duration" +) + +func NewFormattedValueCreated(content, content2 int64, format CreatedFormat) (ret *ValueCreated) { + formatted := time.UnixMilli(content).Format("2006-01-02 15:04") + if 0 < content2 { + formatted += " → " + time.UnixMilli(content2).Format("2006-01-02 15:04") + } + switch format { + case CreatedFormatNone: + case CreatedFormatDuration: + t1 := time.UnixMilli(content) + t2 := time.UnixMilli(content2) + formatted = util.HumanizeRelTime(t1, t2, util.Lang) + } + ret = &ValueCreated{ + Content: content, + Content2: content2, + FormattedContent: formatted, + } + return +} + +type ValueUpdated struct { + Content int64 `json:"content"` + IsNotEmpty bool `json:"isNotEmpty"` + Content2 int64 `json:"content2"` + IsNotEmpty2 bool `json:"isNotEmpty2"` + FormattedContent string `json:"formattedContent"` +} + +type UpdatedFormat string + +const ( + UpdatedFormatNone UpdatedFormat = "" // 2006-01-02 15:04 + UpdatedFormatDuration UpdatedFormat = "duration" +) + +func NewFormattedValueUpdated(content, content2 int64, format UpdatedFormat) (ret *ValueUpdated) { + formatted := time.UnixMilli(content).Format("2006-01-02 15:04") + if 0 < content2 { + formatted += " → " + time.UnixMilli(content2).Format("2006-01-02 15:04") + } + switch format { + case UpdatedFormatNone: + case UpdatedFormatDuration: + t1 := time.UnixMilli(content) + t2 := time.UnixMilli(content2) + formatted = util.HumanizeRelTime(t1, t2, util.Lang) + } + ret = &ValueUpdated{ + Content: content, + Content2: content2, + FormattedContent: formatted, + } + return +} + // View 描述了视图的结构。 type View struct { ID string `json:"id"` // 视图 ID diff --git a/kernel/av/table.go b/kernel/av/table.go index b09f0a3df..afa419a78 100644 --- a/kernel/av/table.go +++ b/kernel/av/table.go @@ -512,6 +512,10 @@ func (table *Table) CalcCols() { table.calcColMAsset(col, i) case KeyTypeTemplate: table.calcColTemplate(col, i) + case KeyTypeCreated: + table.calcColCreated(col, i) + case KeyTypeUpdated: + table.calcColUpdated(col, i) } } } @@ -1325,3 +1329,209 @@ func (table *Table) calcColBlock(col *TableColumn, colIndex int) { } } } + +func (table *Table) calcColCreated(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.Created { + countValues++ + } + } + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countValues), NumberFormatNone)} + case CalcOperatorCountUniqueValues: + countUniqueValues := 0 + uniqueValues := map[int64]bool{} + for _, row := range table.Rows { + if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Created { + if _, ok := uniqueValues[row.Cells[colIndex].Value.Created.Content]; !ok { + countUniqueValues++ + uniqueValues[row.Cells[colIndex].Value.Created.Content] = true + } + } + } + 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.Created { + 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.Created { + 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.Created { + 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.Created { + countNotEmpty++ + } + } + if 0 < len(table.Rows) { + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)} + } + case CalcOperatorEarliest: + earliest := int64(0) + for _, row := range table.Rows { + if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Created { + if 0 == earliest || earliest > row.Cells[colIndex].Value.Created.Content { + earliest = row.Cells[colIndex].Value.Created.Content + } + } + } + if 0 != earliest { + col.Calc.Result = &Value{Created: NewFormattedValueCreated(earliest, 0, CreatedFormatNone)} + } + case CalcOperatorLatest: + latest := int64(0) + for _, row := range table.Rows { + if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Created { + if 0 == latest || latest < row.Cells[colIndex].Value.Created.Content { + latest = row.Cells[colIndex].Value.Created.Content + } + } + } + if 0 != latest { + col.Calc.Result = &Value{Created: NewFormattedValueCreated(latest, 0, CreatedFormatNone)} + } + case CalcOperatorRange: + earliest := int64(0) + latest := int64(0) + for _, row := range table.Rows { + if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Created { + if 0 == earliest || earliest > row.Cells[colIndex].Value.Created.Content { + earliest = row.Cells[colIndex].Value.Created.Content + } + if 0 == latest || latest < row.Cells[colIndex].Value.Created.Content { + latest = row.Cells[colIndex].Value.Created.Content + } + } + } + if 0 != earliest && 0 != latest { + col.Calc.Result = &Value{Created: NewFormattedValueCreated(earliest, latest, CreatedFormatDuration)} + } + } +} + +func (table *Table) calcColUpdated(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.Updated && row.Cells[colIndex].Value.Updated.IsNotEmpty { + countValues++ + } + } + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countValues), NumberFormatNone)} + case CalcOperatorCountUniqueValues: + countUniqueValues := 0 + uniqueValues := map[int64]bool{} + for _, row := range table.Rows { + if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Updated && row.Cells[colIndex].Value.Updated.IsNotEmpty { + if _, ok := uniqueValues[row.Cells[colIndex].Value.Updated.Content]; !ok { + countUniqueValues++ + uniqueValues[row.Cells[colIndex].Value.Updated.Content] = true + } + } + } + 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.Updated || !row.Cells[colIndex].Value.Updated.IsNotEmpty { + 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.Updated && row.Cells[colIndex].Value.Updated.IsNotEmpty { + 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.Updated || !row.Cells[colIndex].Value.Updated.IsNotEmpty { + 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.Updated && row.Cells[colIndex].Value.Updated.IsNotEmpty { + countNotEmpty++ + } + } + if 0 < len(table.Rows) { + col.Calc.Result = &Value{Number: NewFormattedValueNumber(float64(countNotEmpty)/float64(len(table.Rows)), NumberFormatPercent)} + } + case CalcOperatorEarliest: + earliest := int64(0) + for _, row := range table.Rows { + if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Updated && row.Cells[colIndex].Value.Updated.IsNotEmpty { + if 0 == earliest || earliest > row.Cells[colIndex].Value.Updated.Content { + earliest = row.Cells[colIndex].Value.Updated.Content + } + } + } + if 0 != earliest { + col.Calc.Result = &Value{Updated: NewFormattedValueUpdated(earliest, 0, UpdatedFormatNone)} + } + case CalcOperatorLatest: + latest := int64(0) + for _, row := range table.Rows { + if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Updated && row.Cells[colIndex].Value.Updated.IsNotEmpty { + if 0 == latest || latest < row.Cells[colIndex].Value.Updated.Content { + latest = row.Cells[colIndex].Value.Updated.Content + } + } + } + if 0 != latest { + col.Calc.Result = &Value{Updated: NewFormattedValueUpdated(latest, 0, UpdatedFormatNone)} + } + case CalcOperatorRange: + earliest := int64(0) + latest := int64(0) + for _, row := range table.Rows { + if nil != row.Cells[colIndex] && nil != row.Cells[colIndex].Value && nil != row.Cells[colIndex].Value.Updated && row.Cells[colIndex].Value.Updated.IsNotEmpty { + if 0 == earliest || earliest > row.Cells[colIndex].Value.Updated.Content { + earliest = row.Cells[colIndex].Value.Updated.Content + } + if 0 == latest || latest < row.Cells[colIndex].Value.Updated.Content { + latest = row.Cells[colIndex].Value.Updated.Content + } + } + } + if 0 != earliest && 0 != latest { + col.Calc.Result = &Value{Updated: NewFormattedValueUpdated(earliest, latest, UpdatedFormatDuration)} + } + } +} diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go index 884d01a4f..c4ed522be 100644 --- a/kernel/model/attribute_view.go +++ b/kernel/model/attribute_view.go @@ -924,7 +924,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, av.KeyTypeTemplate: + case av.KeyTypeText, av.KeyTypeNumber, av.KeyTypeDate, av.KeyTypeSelect, av.KeyTypeMSelect, av.KeyTypeURL, av.KeyTypeEmail, av.KeyTypePhone, av.KeyTypeMAsset, av.KeyTypeTemplate, av.KeyTypeCreated, av.KeyTypeUpdated: var icon string if nil != operation.Data { icon = operation.Data.(string) @@ -1016,7 +1016,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, av.KeyTypeTemplate: + case av.KeyTypeBlock, av.KeyTypeText, av.KeyTypeNumber, av.KeyTypeDate, av.KeyTypeSelect, av.KeyTypeMSelect, av.KeyTypeURL, av.KeyTypeEmail, av.KeyTypePhone, av.KeyTypeMAsset, av.KeyTypeTemplate, av.KeyTypeCreated, av.KeyTypeUpdated: for _, keyValues := range attrView.KeyValues { if keyValues.Key.ID == operation.ID { keyValues.Key.Name = operation.Name diff --git a/kernel/model/export.go b/kernel/model/export.go index 88d5e255c..be5c17e70 100644 --- a/kernel/model/export.go +++ b/kernel/model/export.go @@ -1909,8 +1909,18 @@ func exportTree(tree *parse.Tree, wysiwyg, expandKaTexMacros, keepFold bool, mdTableRow.AppendChild(mdTableCell) var val string if nil != cell.Value { - if av.KeyTypeDate == cell.Value.Type && nil != cell.Value.Date { - cell.Value.Date = av.NewFormattedValueDate(cell.Value.Date.Content, cell.Value.Date.Content2, av.DateFormatNone) + if av.KeyTypeDate == cell.Value.Type { + if nil != cell.Value.Date { + cell.Value.Date = av.NewFormattedValueDate(cell.Value.Date.Content, cell.Value.Date.Content2, av.DateFormatNone) + } + } else if av.KeyTypeCreated == cell.Value.Type { + if nil != cell.Value.Created { + cell.Value.Created = av.NewFormattedValueCreated(cell.Value.Date.Content, av.CreatedFormatNone) + } + } else if av.KeyTypeUpdated == cell.Value.Type { + if nil != cell.Value.Updated { + cell.Value.Updated = av.NewFormattedValueUpdated(cell.Value.Date.Content, av.UpdatedFormatNone) + } } val = cell.Value.String()