From 6cc6ef66f9ea2f22346b33912a9355a7b696869e Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Fri, 12 Sep 2025 17:32:51 +0800 Subject: [PATCH 1/2] :art: Improve database date fields to automatically fill in creation time https://github.com/siyuan-note/siyuan/issues/15828 :art: Improve database date fields to automatically fill in creation time https://github.com/siyuan-note/siyuan/issues/15828 --- kernel/av/av.go | 16 +++++++++++++++- kernel/av/value.go | 2 ++ kernel/model/attribute_view.go | 24 +++++++++++++++++------- kernel/model/attribute_view_render.go | 6 +++--- kernel/sql/av.go | 1 + 5 files changed, 38 insertions(+), 11 deletions(-) diff --git a/kernel/av/av.go b/kernel/av/av.go index 21874e1ac..c8ccbf416 100644 --- a/kernel/av/av.go +++ b/kernel/av/av.go @@ -221,6 +221,10 @@ type View struct { GroupSort int `json:"groupSort"` // 分组排序值,用于手动排序 } +func (view *View) IsGroupView() bool { + return nil != view.Group && "" != view.Group.Field +} + // GetGroupValue 获取分组视图的分组值。 func (view *View) GetGroupValue() string { if nil == view.GroupVal { @@ -270,7 +274,7 @@ func (view *View) RemoveGroupByID(groupID string) { // GetGroupKey 获取分组视图的分组字段。 func (view *View) GetGroupKey(attrView *AttributeView) (ret *Key) { - if nil == view.Group || "" == view.Group.Field { + if !view.IsGroupView() { return } @@ -295,6 +299,7 @@ type LayoutType string const ( LayoutTypeTable LayoutType = "table" // 属性视图类型 - 表格 LayoutTypeGallery LayoutType = "gallery" // 属性视图类型 - 卡片 + LayoutTypeKanban LayoutType = "kanban" // 属性视图类型 - 看板 ) const ( @@ -530,6 +535,15 @@ func SaveAttributeView(av *AttributeView) (err error) { } } + // 清理渲染回填值 + for _, kv := range av.KeyValues { + for i := len(kv.Values) - 1; i >= 0; i-- { + if kv.Values[i].IsRenderAutoFill { + kv.Values = append(kv.Values[:i], kv.Values[i+1:]...) + } + } + } + var data []byte if util.UseSingleLineSave { data, err = gulu.JSON.MarshalJSON(av) diff --git a/kernel/av/value.go b/kernel/av/value.go index 033990d28..fc3441476 100644 --- a/kernel/av/value.go +++ b/kernel/av/value.go @@ -57,6 +57,8 @@ type Value struct { Checkbox *ValueCheckbox `json:"checkbox,omitempty"` Relation *ValueRelation `json:"relation,omitempty"` Rollup *ValueRollup `json:"rollup,omitempty"` + + IsRenderAutoFill bool `json:"-"` // 标识是否是渲染阶段自动填充的值,保存数据的时候要删掉 } func (value *Value) SetUpdatedAt(mills int64) { diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go index c6d6ec5fd..e5a02ba72 100644 --- a/kernel/model/attribute_view.go +++ b/kernel/model/attribute_view.go @@ -101,7 +101,7 @@ func GetAttrViewAddingBlockDefaultValues(avID, viewID, groupID, previousBlockID, return } - if 1 > len(view.Filters) && nil == view.Group { + if 1 > len(view.Filters) && !view.IsGroupView() { // 没有过滤条件也没有分组条件时忽略 return } @@ -128,7 +128,7 @@ func GetAttrViewAddingBlockDefaultValues(avID, viewID, groupID, previousBlockID, func getAttrViewAddingBlockDefaultValues(attrView *av.AttributeView, view, groupView *av.View, previousItemID, addingItemID string) (ret map[string]*av.Value) { ret = map[string]*av.Value{} - if 1 > len(view.Filters) && nil == view.Group { + if 1 > len(view.Filters) && !view.IsGroupView() { // 没有过滤条件也没有分组条件时忽略 return } @@ -533,7 +533,7 @@ func foldAttrViewGroup(avID, blockID, groupID string, folded bool) (err error) { return err } - if nil == view.Group { + if !view.IsGroupView() { return } @@ -1032,7 +1032,7 @@ func AppendAttributeViewDetachedBlocksWithValues(avID string, blocksValues [][]* v.IsDetached = true v.CreatedAt = now v.UpdatedAt = now - + v.IsRenderAutoFill = false keyValues.Values = append(keyValues.Values, v) if av.KeyTypeSelect == v.Type || av.KeyTypeMSelect == v.Type { @@ -1725,7 +1725,7 @@ func GetBlockAttributeViewKeys(nodeID string) (ret []*BlockAttributeViewKeys) { } func genAttrViewGroups(view *av.View, attrView *av.AttributeView) { - if nil == view.Group { + if !view.IsGroupView() { return } @@ -1969,7 +1969,7 @@ type GroupState struct { func getAttrViewGroupStates(view *av.View) (groupStates map[string]*GroupState) { groupStates = map[string]*GroupState{} - if nil == view.Group { + if !view.IsGroupView() { return } @@ -2329,6 +2329,7 @@ func updateAttributeViewColRelation(operation *Operation) (err error) { destVal.Relation = &av.ValueRelation{} } destVal.UpdatedAt = now + destVal.IsRenderAutoFill = false } destVal.Relation.BlockIDs = append(destVal.Relation.BlockIDs, srcVal.BlockID) destVal.Relation.BlockIDs = gulu.Str.RemoveDuplicatedElem(destVal.Relation.BlockIDs) @@ -3145,12 +3146,19 @@ func addAttributeViewBlock(now int64, avID, dbBlockID, viewID, groupID, previous // The database date field supports filling the current time by default https://github.com/siyuan-note/siyuan/issues/10823 for _, keyValues := range attrView.KeyValues { if av.KeyTypeDate == keyValues.Key.Type && nil != keyValues.Key.Date && keyValues.Key.Date.AutoFillNow { - if nil == keyValues.GetValue(addingItemID) { // 避免覆盖已有值(可能前面已经通过过滤或者分组条件填充了值) + val := keyValues.GetValue(addingItemID) + if nil == val { // 避免覆盖已有值(可能前面已经通过过滤或者分组条件填充了值) dateVal := &av.Value{ ID: ast.NewNodeID(), KeyID: keyValues.Key.ID, BlockID: addingItemID, Type: av.KeyTypeDate, IsDetached: isDetached, CreatedAt: now, UpdatedAt: now + 1000, Date: &av.ValueDate{Content: now, IsNotEmpty: true}, } keyValues.Values = append(keyValues.Values, dateVal) + } else { + if val.IsRenderAutoFill { + val.CreatedAt, val.UpdatedAt = now, now+1000 + val.Date.Content, val.Date.IsNotEmpty = now, true + val.IsRenderAutoFill = false + } } } } @@ -3230,11 +3238,13 @@ func fillDefaultValue(attrView *av.AttributeView, view, groupView *av.View, prev existingVal := keyValues.GetValue(addingItemID) if nil == existingVal { + newValue.IsRenderAutoFill = false keyValues.Values = append(keyValues.Values, newValue) } else { newValueRaw := newValue.GetValByType(keyValues.Key.Type) if av.KeyTypeBlock != existingVal.Type || (av.KeyTypeBlock == existingVal.Type && existingVal.IsDetached) { // 非主键的值直接覆盖,主键的值只覆盖非绑定块 + existingVal.IsRenderAutoFill = false existingVal.SetValByType(keyValues.Key.Type, newValueRaw) } } diff --git a/kernel/model/attribute_view_render.go b/kernel/model/attribute_view_render.go index 992a39c4b..534159c86 100644 --- a/kernel/model/attribute_view_render.go +++ b/kernel/model/attribute_view_render.go @@ -177,7 +177,7 @@ func renderAttributeViewGroups(viewable av.Viewable, attrView *av.AttributeView, } func hideEmptyGroupViews(view *av.View, viewable av.Viewable) { - if nil == view.Group { + if !view.IsGroupView() { return } @@ -343,14 +343,14 @@ func sortGroupsBySelectOption(view *av.View, groupKey *av.Key) { } func isGroupByDate(view *av.View) bool { - if nil == view.Group { + if !view.IsGroupView() { return false } return av.GroupMethodDateDay == view.Group.Method || av.GroupMethodDateWeek == view.Group.Method || av.GroupMethodDateMonth == view.Group.Method || av.GroupMethodDateYear == view.Group.Method || av.GroupMethodDateRelative == view.Group.Method } func isGroupByTemplate(attrView *av.AttributeView, view *av.View) bool { - if nil == view.Group { + if !view.IsGroupView() { return false } diff --git a/kernel/sql/av.go b/kernel/sql/av.go index 7d8e7b873..7dff8e64a 100644 --- a/kernel/sql/av.go +++ b/kernel/sql/av.go @@ -624,6 +624,7 @@ func fillAttributeViewKeyValues(attrView *av.AttributeView, collection av.Collec } } if !exist { + val.IsRenderAutoFill = true keyValues.Values = append(keyValues.Values, val) } } From d6e7d0163ad13dad100d4409767f127f59c37ade Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Fri, 12 Sep 2025 19:10:29 +0800 Subject: [PATCH 2/2] :sparkles: Database kanban view https://github.com/siyuan-note/siyuan/issues/8873 Signed-off-by: Daniel <845765@qq.com> --- kernel/av/layout_kanban.go | 149 +++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 kernel/av/layout_kanban.go diff --git a/kernel/av/layout_kanban.go b/kernel/av/layout_kanban.go new file mode 100644 index 000000000..1d5a322b2 --- /dev/null +++ b/kernel/av/layout_kanban.go @@ -0,0 +1,149 @@ +// SiYuan - Refactor your thinking +// Copyright (c) 2020-present, b3log.org +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package av + +import ( + "github.com/88250/lute/ast" +) + +// LayoutKanban 描述了看板视图的结构。 +type LayoutKanban struct { + *BaseLayout + + GroupFields []*ViewKanbanField `json:"field"` // 字段 +} + +func NewLayoutKanban() *LayoutKanban { + return &LayoutKanban{ + BaseLayout: &BaseLayout{ + Spec: 0, + ID: ast.NewNodeID(), + ShowIcon: true, + }, + } +} + +// ViewKanbanField 描述了看板字段的结构。 +type ViewKanbanField struct { + *BaseField +} + +// Kanban 描述了看板视图实例的结构。 +type Kanban struct { + *BaseInstance + + Fields []*KanbanField `json:"fields"` // 卡片字段 + Cards []*KanbanCard `json:"cards"` // 卡片 + CardCount int `json:"rowCount"` // 总卡片数 +} + +// KanbanCard 描述了看板实例卡片的结构。 +type KanbanCard struct { + ID string `json:"id"` // 卡片 ID + Values []*KanbanFieldValue `json:"values"` // 卡片字段值 +} + +// KanbanField 描述了看板实例字段的结构。 +type KanbanField struct { + *BaseInstanceField +} + +// KanbanFieldValue 描述了卡片字段实例值的结构。 +type KanbanFieldValue struct { + *BaseValue +} + +func (card *KanbanCard) GetID() string { + return card.ID +} + +func (card *KanbanCard) GetBlockValue() (ret *Value) { + for _, v := range card.Values { + if KeyTypeBlock == v.ValueType { + ret = v.Value + break + } + } + return +} + +func (card *KanbanCard) GetValues() (ret []*Value) { + ret = []*Value{} + for _, v := range card.Values { + ret = append(ret, v.Value) + } + return +} + +func (card *KanbanCard) GetValue(keyID string) (ret *Value) { + for _, value := range card.Values { + if nil != value.Value && keyID == value.Value.KeyID { + ret = value.Value + break + } + } + return +} + +func (kanban *Kanban) GetItems() (ret []Item) { + ret = []Item{} + for _, card := range kanban.Cards { + ret = append(ret, card) + } + return +} + +func (kanban *Kanban) SetItems(items []Item) { + kanban.Cards = []*KanbanCard{} + for _, item := range items { + kanban.Cards = append(kanban.Cards, item.(*KanbanCard)) + } +} + +func (kanban *Kanban) CountItems() int { + return len(kanban.Cards) +} + +func (kanban *Kanban) GetFields() (ret []Field) { + ret = []Field{} + for _, field := range kanban.Fields { + ret = append(ret, field) + } + return ret +} + +func (kanban *Kanban) GetField(id string) (ret Field, fieldIndex int) { + for i, field := range kanban.Fields { + if field.ID == id { + return field, i + } + } + return nil, -1 +} + +func (kanban *Kanban) GetValue(itemID, keyID string) (ret *Value) { + for _, card := range kanban.Cards { + if card.ID == itemID { + return card.GetValue(keyID) + } + } + return nil +} + +func (kanban *Kanban) GetType() LayoutType { + return LayoutTypeKanban +}