diff --git a/kernel/av/av.go b/kernel/av/av.go index 195aa1886..4fb5901c1 100644 --- a/kernel/av/av.go +++ b/kernel/av/av.go @@ -146,6 +146,12 @@ type Key struct { // 日期 Date *Date `json:"date,omitempty"` // 日期设置 + + // 创建时间 + Created *Created `json:"created,omitempty"` // 创建时间设置 + + // 更新时间 + Updated *Updated `json:"updated,omitempty"` // 更新时间设置 } func NewKey(id, name, icon string, keyType KeyType) *Key { @@ -167,6 +173,14 @@ func (k *Key) GetOption(name string) (ret *SelectOption) { return } +type Created struct { + IncludeTime bool `json:"includeTime"` // 是否填充具体时间 Add `Include time` switch to database creation time field and update time field https://github.com/siyuan-note/siyuan/issues/12091 +} + +type Updated struct { + IncludeTime bool `json:"includeTime"` // 是否填充具体时间 Add `Include time` switch to database creation time field and update time field https://github.com/siyuan-note/siyuan/issues/12091 +} + type Date struct { AutoFillNow bool `json:"autoFillNow"` // 是否自动填充当前时间 The database date field supports filling the current time by default https://github.com/siyuan-note/siyuan/issues/10823 FillSpecificTime bool `json:"fillSpecificTime"` // 是否填充具体时间 Add `Default fill specific time` switch to database date field https://github.com/siyuan-note/siyuan/issues/12089 @@ -396,7 +410,7 @@ type Viewable interface { func NewAttributeView(id string) (ret *AttributeView) { view, blockKey, selectKey := NewTableViewWithBlockKey(ast.NewNodeID()) ret = &AttributeView{ - Spec: 3, + Spec: CurrentSpec, ID: id, KeyValues: []*KeyValues{{Key: blockKey}, {Key: selectKey}}, ViewID: view.ID, diff --git a/kernel/av/av_fix.go b/kernel/av/av_fix.go index 7a98cf9e7..b9312a81c 100644 --- a/kernel/av/av_fix.go +++ b/kernel/av/av_fix.go @@ -24,10 +24,38 @@ import ( "github.com/siyuan-note/siyuan/kernel/util" ) +const CurrentSpec = 4 + func UpgradeSpec(av *AttributeView) { + if CurrentSpec <= av.Spec { + return + } + upgradeSpec1(av) upgradeSpec2(av) upgradeSpec3(av) + upgradeSpec4(av) +} + +func upgradeSpec4(av *AttributeView) { + if 4 <= av.Spec { + return + } + + for _, keyValues := range av.KeyValues { + switch keyValues.Key.Type { + case KeyTypeCreated: + if nil == keyValues.Key.Created { + keyValues.Key.Created = &Created{IncludeTime: true} + } + case KeyTypeUpdated: + if nil == keyValues.Key.Updated { + keyValues.Key.Updated = &Updated{IncludeTime: true} + } + } + } + + av.Spec = 4 } func upgradeSpec3(av *AttributeView) { diff --git a/kernel/av/calc.go b/kernel/av/calc.go index dab4134ef..5ac8b0925 100644 --- a/kernel/av/calc.go +++ b/kernel/av/calc.go @@ -66,7 +66,7 @@ func Calc(viewable Viewable, attrView *AttributeView) { continue } - calcField(collection, field, i) + calcField(collection, field, i, attrView) } // 分组计算 @@ -83,7 +83,7 @@ func Calc(viewable Viewable, attrView *AttributeView) { if nil == calcResult { // 在字段上设置计算规则,使用字段结算结果作为分组计算结果,最后再清除字段上的计算规则 field.SetCalc(groupCalc.FieldCalc) - calcField(collection, field, fieldIndex) + calcField(collection, field, fieldIndex, attrView) calcResult = &GroupCalc{Field: groupCalcKey.ID, FieldCalc: field.GetCalc()} field.SetCalc(nil) } @@ -94,7 +94,7 @@ func Calc(viewable Viewable, attrView *AttributeView) { } } -func calcField(collection Collection, field Field, fieldIndex int) { +func calcField(collection Collection, field Field, fieldIndex int, attrView *AttributeView) { switch field.GetType() { case KeyTypeBlock: calcFieldBlock(collection, field, fieldIndex) @@ -119,9 +119,9 @@ func calcField(collection Collection, field Field, fieldIndex int) { case KeyTypeTemplate: calcFieldTemplate(collection, field, fieldIndex) case KeyTypeCreated: - calcFieldCreated(collection, field, fieldIndex) + calcFieldCreated(collection, field, fieldIndex, attrView) case KeyTypeUpdated: - calcFieldUpdated(collection, field, fieldIndex) + calcFieldUpdated(collection, field, fieldIndex, attrView) case KeyTypeCheckbox: calcFieldCheckbox(collection, field, fieldIndex) case KeyTypeRelation: @@ -1296,7 +1296,7 @@ func calcFieldBlock(collection Collection, field Field, fieldIndex int) { } } -func calcFieldCreated(collection Collection, field Field, fieldIndex int) { +func calcFieldCreated(collection Collection, field Field, fieldIndex int, attrView *AttributeView) { calc := field.GetCalc() switch calc.Operator { case CalcOperatorCountAll: @@ -1389,7 +1389,13 @@ func calcFieldCreated(collection Collection, field Field, fieldIndex int) { } } if 0 != earliest { - calc.Result = &Value{Created: NewFormattedValueCreated(earliest, 0, CreatedFormatNone)} + key, _ := attrView.GetKey(field.GetID()) + isNotTime := false + if nil != key && nil != key.Created { + isNotTime = !key.Created.IncludeTime + } + + calc.Result = &Value{Created: NewFormattedValueCreated(earliest, 0, CreatedFormatNone, isNotTime)} } case CalcOperatorLatest: latest := int64(0) @@ -1402,7 +1408,13 @@ func calcFieldCreated(collection Collection, field Field, fieldIndex int) { } } if 0 != latest { - calc.Result = &Value{Created: NewFormattedValueCreated(latest, 0, CreatedFormatNone)} + key, _ := attrView.GetKey(field.GetID()) + isNotTime := false + if nil != key && nil != key.Created { + isNotTime = !key.Created.IncludeTime + } + + calc.Result = &Value{Created: NewFormattedValueCreated(latest, 0, CreatedFormatNone, isNotTime)} } case CalcOperatorRange: earliest := int64(0) @@ -1419,12 +1431,18 @@ func calcFieldCreated(collection Collection, field Field, fieldIndex int) { } } if 0 != earliest && 0 != latest { - calc.Result = &Value{Created: NewFormattedValueCreated(earliest, latest, CreatedFormatDuration)} + key, _ := attrView.GetKey(field.GetID()) + isNotTime := false + if nil != key && nil != key.Created { + isNotTime = !key.Created.IncludeTime + } + + calc.Result = &Value{Created: NewFormattedValueCreated(earliest, latest, CreatedFormatDuration, isNotTime)} } } } -func calcFieldUpdated(collection Collection, field Field, fieldIndex int) { +func calcFieldUpdated(collection Collection, field Field, fieldIndex int, attrView *AttributeView) { calc := field.GetCalc() switch calc.Operator { case CalcOperatorCountAll: @@ -1517,7 +1535,13 @@ func calcFieldUpdated(collection Collection, field Field, fieldIndex int) { } } if 0 != earliest { - calc.Result = &Value{Updated: NewFormattedValueUpdated(earliest, 0, UpdatedFormatNone)} + key, _ := attrView.GetKey(field.GetID()) + isNotTime := false + if nil != key && nil != key.Updated { + isNotTime = !key.Updated.IncludeTime + } + + calc.Result = &Value{Updated: NewFormattedValueUpdated(earliest, 0, UpdatedFormatNone, isNotTime)} } case CalcOperatorLatest: latest := int64(0) @@ -1530,7 +1554,13 @@ func calcFieldUpdated(collection Collection, field Field, fieldIndex int) { } } if 0 != latest { - calc.Result = &Value{Updated: NewFormattedValueUpdated(latest, 0, UpdatedFormatNone)} + key, _ := attrView.GetKey(field.GetID()) + isNotTime := false + if nil != key && nil != key.Updated { + isNotTime = !key.Updated.IncludeTime + } + + calc.Result = &Value{Updated: NewFormattedValueUpdated(latest, 0, UpdatedFormatNone, isNotTime)} } case CalcOperatorRange: earliest := int64(0) @@ -1547,7 +1577,13 @@ func calcFieldUpdated(collection Collection, field Field, fieldIndex int) { } } if 0 != earliest && 0 != latest { - calc.Result = &Value{Updated: NewFormattedValueUpdated(earliest, latest, UpdatedFormatDuration)} + key, _ := attrView.GetKey(field.GetID()) + isNotTime := false + if nil != key && nil != key.Updated { + isNotTime = !key.Updated.IncludeTime + } + + calc.Result = &Value{Updated: NewFormattedValueUpdated(earliest, latest, UpdatedFormatDuration, isNotTime)} } } } diff --git a/kernel/av/value.go b/kernel/av/value.go index ee9286c79..a206b8030 100644 --- a/kernel/av/value.go +++ b/kernel/av/value.go @@ -735,8 +735,14 @@ const ( CreatedFormatDuration CreatedFormat = "duration" ) -func NewFormattedValueCreated(content, content2 int64, format CreatedFormat) (ret *ValueCreated) { - formatted := time.UnixMilli(content).Format("2006-01-02 15:04") +func NewFormattedValueCreated(content, content2 int64, format CreatedFormat, isNotTime bool) (ret *ValueCreated) { + var formatted string + if isNotTime { + formatted = time.UnixMilli(content).Format("2006-01-02") + } else { + formatted = time.UnixMilli(content).Format("2006-01-02 15:04") + } + if 0 < content2 { formatted += " → " + time.UnixMilli(content2).Format("2006-01-02 15:04") } @@ -770,8 +776,14 @@ const ( UpdatedFormatDuration UpdatedFormat = "duration" ) -func NewFormattedValueUpdated(content, content2 int64, format UpdatedFormat) (ret *ValueUpdated) { - formatted := time.UnixMilli(content).Format("2006-01-02 15:04") +func NewFormattedValueUpdated(content, content2 int64, format UpdatedFormat, isNotTime bool) (ret *ValueUpdated) { + var formatted string + if isNotTime { + formatted = time.UnixMilli(content).Format("2006-01-02") + } else { + formatted = time.UnixMilli(content).Format("2006-01-02 15:04") + } + if 0 < content2 { formatted += " → " + time.UnixMilli(content2).Format("2006-01-02 15:04") } @@ -1098,11 +1110,21 @@ func (r *ValueRollup) calcContents(calc *RollupCalc, destKey *Key) { } case KeyTypeUpdated: if 0 != earliest && 0 != latest { - r.Contents = []*Value{{Type: KeyTypeUpdated, Updated: NewFormattedValueUpdated(earliest, latest, UpdatedFormatDuration)}} + isNotTime = false + if nil != destKey.Updated { + isNotTime = !destKey.Updated.IncludeTime + } + + r.Contents = []*Value{{Type: KeyTypeUpdated, Updated: NewFormattedValueUpdated(earliest, latest, UpdatedFormatDuration, isNotTime)}} } case KeyTypeCreated: if 0 != earliest && 0 != latest { - r.Contents = []*Value{{Type: KeyTypeCreated, Created: NewFormattedValueCreated(earliest, latest, CreatedFormatDuration)}} + isNotTime = false + if nil != destKey.Created { + isNotTime = !destKey.Created.IncludeTime + } + + r.Contents = []*Value{{Type: KeyTypeCreated, Created: NewFormattedValueCreated(earliest, latest, CreatedFormatDuration, isNotTime)}} } default: if math.MaxFloat64 != minVal && -math.MaxFloat64 != maxVal { @@ -1146,11 +1168,21 @@ func (r *ValueRollup) calcContents(calc *RollupCalc, destKey *Key) { } case KeyTypeUpdated: if 0 != earliest { - r.Contents = []*Value{{Type: KeyTypeUpdated, Updated: NewFormattedValueUpdated(earliest, 0, UpdatedFormatNone)}} + isNotTime = false + if nil != destKey.Updated { + isNotTime = !destKey.Updated.IncludeTime + } + + r.Contents = []*Value{{Type: KeyTypeUpdated, Updated: NewFormattedValueUpdated(earliest, 0, UpdatedFormatNone, isNotTime)}} } case KeyTypeCreated: if 0 != earliest { - r.Contents = []*Value{{Type: KeyTypeCreated, Created: NewFormattedValueCreated(earliest, 0, CreatedFormatNone)}} + isNotTime = false + if nil != destKey.Created { + isNotTime = !destKey.Created.IncludeTime + } + + r.Contents = []*Value{{Type: KeyTypeCreated, Created: NewFormattedValueCreated(earliest, 0, CreatedFormatNone, isNotTime)}} } } case CalcOperatorLatest: @@ -1190,11 +1222,20 @@ func (r *ValueRollup) calcContents(calc *RollupCalc, destKey *Key) { } case KeyTypeUpdated: if 0 != latest { - r.Contents = []*Value{{Type: KeyTypeUpdated, Updated: NewFormattedValueUpdated(latest, 0, UpdatedFormatNone)}} + isNotTime = false + if nil != destKey.Updated { + isNotTime = !destKey.Updated.IncludeTime + } + r.Contents = []*Value{{Type: KeyTypeUpdated, Updated: NewFormattedValueUpdated(latest, 0, UpdatedFormatNone, isNotTime)}} } case KeyTypeCreated: if 0 != latest { - r.Contents = []*Value{{Type: KeyTypeCreated, Created: NewFormattedValueCreated(latest, 0, CreatedFormatNone)}} + isNotTime = false + if nil != destKey.Created { + isNotTime = !destKey.Created.IncludeTime + } + + r.Contents = []*Value{{Type: KeyTypeCreated, Created: NewFormattedValueCreated(latest, 0, CreatedFormatNone, isNotTime)}} } } case CalcOperatorChecked: diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go index 1dd1716ca..99719809f 100644 --- a/kernel/model/attribute_view.go +++ b/kernel/model/attribute_view.go @@ -1688,25 +1688,35 @@ func GetBlockAttributeViewKeys(nodeID string) (ret []*BlockAttributeViewKeys) { } } case av.KeyTypeCreated: + isNotTime := false + if nil != kv.Key && nil != kv.Key.Created { + isNotTime = !kv.Key.Created.IncludeTime + } + createdStr := nodeID[:len("20060102150405")] created, parseErr := time.ParseInLocation("20060102150405", createdStr, time.Local) if nil == parseErr { - kv.Values[0].Created = av.NewFormattedValueCreated(created.UnixMilli(), 0, av.CreatedFormatNone) + kv.Values[0].Created = av.NewFormattedValueCreated(created.UnixMilli(), 0, av.CreatedFormatNone, isNotTime) kv.Values[0].Created.IsNotEmpty = true } else { logging.LogWarnf("parse created [%s] failed: %s", createdStr, parseErr) - kv.Values[0].Created = av.NewFormattedValueCreated(time.Now().UnixMilli(), 0, av.CreatedFormatNone) + kv.Values[0].Created = av.NewFormattedValueCreated(time.Now().UnixMilli(), 0, av.CreatedFormatNone, isNotTime) } case av.KeyTypeUpdated: + isNotTime := false + if nil != kv.Key && nil != kv.Key.Updated { + isNotTime = !kv.Key.Updated.IncludeTime + } + ial := sql.GetBlockAttrs(nodeID) updatedStr := ial["updated"] updated, parseErr := time.ParseInLocation("20060102150405", updatedStr, time.Local) if nil == parseErr { - kv.Values[0].Updated = av.NewFormattedValueUpdated(updated.UnixMilli(), 0, av.UpdatedFormatNone) + kv.Values[0].Updated = av.NewFormattedValueUpdated(updated.UnixMilli(), 0, av.UpdatedFormatNone, isNotTime) kv.Values[0].Updated.IsNotEmpty = true } else { logging.LogWarnf("parse updated [%s] failed: %s", updatedStr, parseErr) - kv.Values[0].Updated = av.NewFormattedValueUpdated(time.Now().UnixMilli(), 0, av.UpdatedFormatNone) + kv.Values[0].Updated = av.NewFormattedValueUpdated(time.Now().UnixMilli(), 0, av.UpdatedFormatNone, isNotTime) } } } @@ -2271,6 +2281,62 @@ func setAttrViewColDateFillSpecificTime(operation *Operation) (err error) { return } +func (tx *Transaction) doSetAttrViewCreatedIncludeTime(operation *Operation) (ret *TxErr) { + err := setAttrViewCreatedIncludeTime(operation) + if err != nil { + return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()} + } + return +} + +func setAttrViewCreatedIncludeTime(operation *Operation) (err error) { + attrView, err := av.ParseAttributeView(operation.AvID) + if err != nil { + return + } + + key, _ := attrView.GetKey(operation.ID) + if nil == key { + return + } + + if nil == key.Created { + key.Created = &av.Created{} + } + + key.Created.IncludeTime = operation.Data.(bool) + err = av.SaveAttributeView(attrView) + return +} + +func (tx *Transaction) doSetAttrViewUpdatedIncludeTime(operation *Operation) (ret *TxErr) { + err := setAttrViewUpdatedIncludeTime(operation) + if err != nil { + return &TxErr{code: TxErrHandleAttributeView, id: operation.AvID, msg: err.Error()} + } + return +} + +func setAttrViewUpdatedIncludeTime(operation *Operation) (err error) { + attrView, err := av.ParseAttributeView(operation.AvID) + if err != nil { + return + } + + key, _ := attrView.GetKey(operation.ID) + if nil == key { + return + } + + if nil == key.Updated { + key.Updated = &av.Updated{} + } + + key.Updated.IncludeTime = operation.Data.(bool) + err = av.SaveAttributeView(attrView) + return +} + func (tx *Transaction) doHideAttrViewName(operation *Operation) (ret *TxErr) { err := hideAttrViewName(operation) if err != nil { diff --git a/kernel/model/export.go b/kernel/model/export.go index 96fa59a8b..aad640c0e 100644 --- a/kernel/model/export.go +++ b/kernel/model/export.go @@ -130,11 +130,23 @@ func ExportAv2CSV(avID, blockID string) (zipPath string, err error) { } } else if av.KeyTypeCreated == cell.Value.Type { if nil != cell.Value.Created { - cell.Value.Created = av.NewFormattedValueCreated(cell.Value.Created.Content, 0, av.CreatedFormatNone) + key, _ := attrView.GetKey(cell.Value.KeyID) + isNotTime := false + if nil != key && nil != key.Created { + isNotTime = !key.Created.IncludeTime + } + + cell.Value.Created = av.NewFormattedValueCreated(cell.Value.Created.Content, 0, av.CreatedFormatNone, isNotTime) } } else if av.KeyTypeUpdated == cell.Value.Type { if nil != cell.Value.Updated { - cell.Value.Updated = av.NewFormattedValueUpdated(cell.Value.Updated.Content, 0, av.UpdatedFormatNone) + key, _ := attrView.GetKey(cell.Value.KeyID) + isNotTime := false + if nil != key && nil != key.Updated { + isNotTime = !key.Updated.IncludeTime + } + + cell.Value.Updated = av.NewFormattedValueUpdated(cell.Value.Updated.Content, 0, av.UpdatedFormatNone, isNotTime) } } else if av.KeyTypeMAsset == cell.Value.Type { if nil != cell.Value.MAsset { @@ -2711,11 +2723,23 @@ func exportTree(tree *parse.Tree, wysiwyg, keepFold, avHiddenCol bool, } } else if av.KeyTypeCreated == cell.Value.Type { if nil != cell.Value.Created { - cell.Value.Created = av.NewFormattedValueCreated(cell.Value.Created.Content, 0, av.CreatedFormatNone) + key, _ := attrView.GetKey(cell.Value.KeyID) + isNotTime := false + if nil != key && nil != key.Created { + isNotTime = !key.Created.IncludeTime + } + + cell.Value.Created = av.NewFormattedValueCreated(cell.Value.Created.Content, 0, av.CreatedFormatNone, isNotTime) } } else if av.KeyTypeUpdated == cell.Value.Type { if nil != cell.Value.Updated { - cell.Value.Updated = av.NewFormattedValueUpdated(cell.Value.Updated.Content, 0, av.UpdatedFormatNone) + key, _ := attrView.GetKey(cell.Value.KeyID) + isNotTime := false + if nil != key && nil != key.Updated { + isNotTime = !key.Updated.IncludeTime + } + + cell.Value.Updated = av.NewFormattedValueUpdated(cell.Value.Updated.Content, 0, av.UpdatedFormatNone, isNotTime) } } else if av.KeyTypeURL == cell.Value.Type { if nil != cell.Value.URL { diff --git a/kernel/model/transaction.go b/kernel/model/transaction.go index 9ffe57e01..f2e9421af 100644 --- a/kernel/model/transaction.go +++ b/kernel/model/transaction.go @@ -281,6 +281,10 @@ func performTx(tx *Transaction) (ret *TxErr) { ret = tx.doSetAttrViewColDateFillCreated(op) case "setAttrViewColDateFillSpecificTime": ret = tx.doSetAttrViewColDateFillSpecificTime(op) + case "setAttrViewCreatedIncludeTime": + ret = tx.doSetAttrViewCreatedIncludeTime(op) + case "setAttrViewUpdatedIncludeTime": + ret = tx.doSetAttrViewUpdatedIncludeTime(op) case "duplicateAttrViewKey": ret = tx.doDuplicateAttrViewKey(op) case "setAttrViewCoverFrom": diff --git a/kernel/sql/av.go b/kernel/sql/av.go index 8ca65ce78..76850beb2 100644 --- a/kernel/sql/av.go +++ b/kernel/sql/av.go @@ -370,6 +370,12 @@ func fillAttributeViewAutoGeneratedValues(attrView *av.AttributeView, collection } } case av.KeyTypeCreated: // 渲染创建时间 + key, _ := attrView.GetKey(value.KeyID) + isNotTime := false + if nil != key && nil != key.Created { + isNotTime = !key.Created.IncludeTime + } + ial := map[string]string{} block := item.GetBlockValue() if nil != block { @@ -385,12 +391,18 @@ func fillAttributeViewAutoGeneratedValues(attrView *av.AttributeView, collection createdStr := id[:len("20060102150405")] created, parseErr := time.ParseInLocation("20060102150405", createdStr, time.Local) if nil == parseErr { - value.Created = av.NewFormattedValueCreated(created.UnixMilli(), 0, av.CreatedFormatNone) + value.Created = av.NewFormattedValueCreated(created.UnixMilli(), 0, av.CreatedFormatNone, isNotTime) value.Created.IsNotEmpty = true } else { - value.Created = av.NewFormattedValueCreated(time.Now().UnixMilli(), 0, av.CreatedFormatNone) + value.Created = av.NewFormattedValueCreated(time.Now().UnixMilli(), 0, av.CreatedFormatNone, isNotTime) } case av.KeyTypeUpdated: // 渲染更新时间 + key, _ := attrView.GetKey(value.KeyID) + isNotTime := false + if nil != key && nil != key.Updated { + isNotTime = !key.Updated.IncludeTime + } + ial := map[string]string{} block := item.GetBlockValue() if nil != block { @@ -401,15 +413,15 @@ func fillAttributeViewAutoGeneratedValues(attrView *av.AttributeView, collection } updatedStr := ial["updated"] if "" == updatedStr && nil != block { - value.Updated = av.NewFormattedValueUpdated(block.Block.Updated, 0, av.UpdatedFormatNone) + value.Updated = av.NewFormattedValueUpdated(block.Block.Updated, 0, av.UpdatedFormatNone, isNotTime) value.Updated.IsNotEmpty = true } else { updated, parseErr := time.ParseInLocation("20060102150405", updatedStr, time.Local) if nil == parseErr { - value.Updated = av.NewFormattedValueUpdated(updated.UnixMilli(), 0, av.UpdatedFormatNone) + value.Updated = av.NewFormattedValueUpdated(updated.UnixMilli(), 0, av.UpdatedFormatNone, isNotTime) value.Updated.IsNotEmpty = true } else { - value.Updated = av.NewFormattedValueUpdated(time.Now().UnixMilli(), 0, av.UpdatedFormatNone) + value.Updated = av.NewFormattedValueUpdated(time.Now().UnixMilli(), 0, av.UpdatedFormatNone, isNotTime) } } }