🎨 Add Include time switch to database creation time field and update time field https://github.com/siyuan-note/siyuan/issues/12091

Signed-off-by: Daniel <845765@qq.com>
This commit is contained in:
Daniel 2025-11-07 21:25:03 +08:00
parent 75da247b23
commit 0bea01ad3b
No known key found for this signature in database
GPG key ID: 86211BA83DF03017
8 changed files with 262 additions and 37 deletions

View file

@ -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,

View file

@ -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) {

View file

@ -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)}
}
}
}

View file

@ -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:

View file

@ -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 {

View file

@ -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 {

View file

@ -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":

View file

@ -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)
}
}
}