diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go index fcb8f4ff4..00c9247ac 100644 --- a/kernel/model/attribute_view.go +++ b/kernel/model/attribute_view.go @@ -32,7 +32,6 @@ import ( "github.com/88250/lute/ast" "github.com/88250/lute/parse" "github.com/jinzhu/copier" - "github.com/siyuan-note/dejavu/entity" "github.com/siyuan-note/filelock" "github.com/siyuan-note/logging" "github.com/siyuan-note/siyuan/kernel/av" @@ -1553,352 +1552,6 @@ func GetBlockAttributeViewKeys(blockID string) (ret []*BlockAttributeViewKeys) { return } -func RenderRepoSnapshotAttributeView(indexID, avID string) (viewable av.Viewable, attrView *av.AttributeView, err error) { - repo, err := newRepository() - if err != nil { - return - } - - index, err := repo.GetIndex(indexID) - if err != nil { - return - } - - files, err := repo.GetFiles(index) - if err != nil { - return - } - var avFile *entity.File - for _, f := range files { - if "/storage/av/"+avID+".json" == f.Path { - avFile = f - break - } - } - - if nil == avFile { - attrView = av.NewAttributeView(avID) - } else { - data, readErr := repo.OpenFile(avFile) - if nil != readErr { - logging.LogErrorf("read attribute view [%s] failed: %s", avID, readErr) - return - } - - attrView = &av.AttributeView{} - if err = gulu.JSON.UnmarshalJSON(data, attrView); err != nil { - logging.LogErrorf("unmarshal attribute view [%s] failed: %s", avID, err) - return - } - } - - viewable, err = renderAttributeView(attrView, "", "", "", 1, -1, nil) - return -} - -func RenderHistoryAttributeView(avID, created string) (viewable av.Viewable, attrView *av.AttributeView, err error) { - createdUnix, parseErr := strconv.ParseInt(created, 10, 64) - if nil != parseErr { - logging.LogErrorf("parse created [%s] failed: %s", created, parseErr) - return - } - - dirPrefix := time.Unix(createdUnix, 0).Format("2006-01-02-150405") - globPath := filepath.Join(util.HistoryDir, dirPrefix+"*") - matches, err := filepath.Glob(globPath) - if err != nil { - logging.LogErrorf("glob [%s] failed: %s", globPath, err) - return - } - if 1 > len(matches) { - return - } - - historyDir := matches[0] - avJSONPath := filepath.Join(historyDir, "storage", "av", avID+".json") - if !gulu.File.IsExist(avJSONPath) { - avJSONPath = filepath.Join(util.DataDir, "storage", "av", avID+".json") - } - if !gulu.File.IsExist(avJSONPath) { - attrView = av.NewAttributeView(avID) - } else { - data, readErr := os.ReadFile(avJSONPath) - if nil != readErr { - logging.LogErrorf("read attribute view [%s] failed: %s", avID, readErr) - return - } - - attrView = &av.AttributeView{} - if err = gulu.JSON.UnmarshalJSON(data, attrView); err != nil { - logging.LogErrorf("unmarshal attribute view [%s] failed: %s", avID, err) - return - } - } - - viewable, err = renderAttributeView(attrView, "", "", "", 1, -1, nil) - return -} - -func RenderAttributeView(blockID, avID, viewID, query string, page, pageSize int, groupPaging map[string]interface{}) (viewable av.Viewable, attrView *av.AttributeView, err error) { - waitForSyncingStorages() - - if avJSONPath := av.GetAttributeViewDataPath(avID); !filelock.IsExist(avJSONPath) { - attrView = av.NewAttributeView(avID) - if err = av.SaveAttributeView(attrView); err != nil { - logging.LogErrorf("save attribute view [%s] failed: %s", avID, err) - return - } - } - - attrView, err = av.ParseAttributeView(avID) - if err != nil { - logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err) - return - } - - viewable, err = renderAttributeView(attrView, blockID, viewID, query, page, pageSize, groupPaging) - return -} - -const ( - groupValueDefault = "_@default@_" // 默认分组值(值为空的默认分组) - groupValueNotInRange = "_@notInRange@_" // 不再范围内的分组值(只有数字类型的分组才可能是该值) - groupValueLast30Days, groupValueLast7Days = "_@last30Days@_", "_@last7Days@_" - groupValueYesterday, groupValueToday, groupValueTomorrow = "_@yesterday@_", "_@today@_", "_@tomorrow@_" - groupValueNext7Days, groupValueNext30Days = "_@next7Days@_", "_@next30Days@_" -) - -func renderAttributeView(attrView *av.AttributeView, blockID, viewID, query string, page, pageSize int, groupPaging map[string]interface{}) (viewable av.Viewable, err error) { - if 1 > len(attrView.Views) { - view, _, _ := av.NewTableViewWithBlockKey(ast.NewNodeID()) - attrView.Views = append(attrView.Views, view) - attrView.ViewID = view.ID - if err = av.SaveAttributeView(attrView); err != nil { - logging.LogErrorf("save attribute view [%s] failed: %s", attrView.ID, err) - return - } - } - - if "" == viewID && "" != blockID { - node, _, getErr := getNodeByBlockID(nil, blockID) - if nil != getErr { - logging.LogWarnf("get node by block ID [%s] failed: %s", blockID, getErr) - } else { - viewID = node.IALAttr(av.NodeAttrView) - } - } - - var view *av.View - if "" != viewID { - view, _ = attrView.GetCurrentView(viewID) - if nil != view && view.ID != attrView.ViewID { - attrView.ViewID = view.ID - if err = av.SaveAttributeView(attrView); err != nil { - logging.LogErrorf("save attribute view [%s] failed: %s", attrView.ID, err) - return - } - } - } else { - view = attrView.GetView(attrView.ViewID) - } - - if nil == view { - view = attrView.Views[0] - } - - // 做一些数据兼容和订正处理 - checkAttrView(attrView, view) - upgradeAttributeViewSpec(attrView) - - viewable = sql.RenderView(attrView, view, query) - err = renderViewableInstance(viewable, view, attrView, page, pageSize) - if nil != err { - return - } - - // 当前日期可能会变,所以如果是按日期分组则需要重新生成分组 - if isGroupByDate(view) { - createdDate := time.UnixMilli(view.GroupCreated).Format("2006-01-02") - if time.Now().Format("2006-01-02") != createdDate { - regenAttrViewViewGroups(attrView, "force") - av.SaveAttributeView(attrView) - } - } - - fixDev := false - // 如果存在分组的话渲染分组视图 - if groupKey := view.GetGroupKey(attrView); nil != groupKey { - for _, groupView := range view.Groups { - if "" == groupView.GetGroupValue() && !fixDev { - // TODO 分组上线后删除,预计 2025 年 9 月后可以删除 - regenAttrViewViewGroups(attrView, "force") - av.SaveAttributeView(attrView) - fixDev = true - } - - switch groupView.GetGroupValue() { - case groupValueDefault: - groupView.Name = fmt.Sprintf(Conf.language(264), groupKey.Name) - case groupValueNotInRange: - groupView.Name = Conf.language(265) - case groupValueLast30Days: - groupView.Name = fmt.Sprintf(Conf.language(259), 30) - case groupValueLast7Days: - groupView.Name = fmt.Sprintf(Conf.language(259), 7) - case groupValueYesterday: - groupView.Name = Conf.language(260) - case groupValueToday: - groupView.Name = Conf.language(261) - case groupValueTomorrow: - groupView.Name = Conf.language(262) - case groupValueNext7Days: - groupView.Name = fmt.Sprintf(Conf.language(263), 7) - case groupValueNext30Days: - groupView.Name = fmt.Sprintf(Conf.language(263), 30) - default: - groupView.Name = groupView.GetGroupValue() - } - } - - todayStart := time.Now() - todayStart = time.Date(todayStart.Year(), todayStart.Month(), todayStart.Day(), 0, 0, 0, 0, time.Local) - sortGroupViews(todayStart, view) - - var groups []av.Viewable - for _, groupView := range view.Groups { - groupViewable := sql.RenderGroupView(attrView, view, groupView, query) - - groupPage, groupPageSize := page, pageSize - if nil != groupPaging { - if paging := groupPaging[groupView.ID]; nil != paging { - pagingMap := paging.(map[string]interface{}) - if nil != pagingMap["page"] { - groupPage = int(pagingMap["page"].(float64)) - } - if nil != pagingMap["pageSize"] { - groupPageSize = int(pagingMap["pageSize"].(float64)) - } - } - } - - err = renderViewableInstance(groupViewable, view, attrView, groupPage, groupPageSize) - if nil != err { - return - } - groups = append(groups, groupViewable) - - // 将分组视图的分组字段清空,减少冗余(字段信息可以在总的视图 view 对象上获取到) - switch groupView.LayoutType { - case av.LayoutTypeTable: - groupView.Table.Columns = nil - case av.LayoutTypeGallery: - groupView.Gallery.CardFields = nil - } - } - viewable.SetGroups(groups) - - // 将总的视图上的项目清空,减少冗余 - viewable.(av.Collection).SetItems(nil) - } - return -} - -func sortGroupViews(todayStart time.Time, view *av.View) { - if av.GroupOrderMan == view.Group.Order { - sort.Slice(view.Groups, func(i, j int) bool { return view.Groups[i].GroupSort < view.Groups[j].GroupSort }) - return - } - - if av.GroupMethodDateRelative == view.Group.Method { - var relativeDateGroups []*av.View - var last30Days, last7Days, yesterday, today, tomorrow, next7Days, next30Days, defaultGroup *av.View - for _, groupView := range view.Groups { - _, err := time.Parse("2006-01", groupView.GetGroupValue()) - if nil == err { // 如果能解析出来说明是 30 天之前或 30 天之后的分组形式 - relativeDateGroups = append(relativeDateGroups, groupView) - } else { // 否则是相对日期分组形式 - switch groupView.GetGroupValue() { - case groupValueLast30Days: - last30Days = groupView - case groupValueLast7Days: - last7Days = groupView - case groupValueYesterday: - yesterday = groupView - case groupValueToday: - today = groupView - case groupValueTomorrow: - tomorrow = groupView - case groupValueNext7Days: - next7Days = groupView - case groupValueNext30Days: - next30Days = groupView - case groupValueDefault: - defaultGroup = groupView - } - } - } - - sort.SliceStable(relativeDateGroups, func(i, j int) bool { - return relativeDateGroups[i].GetGroupValue() < relativeDateGroups[j].GetGroupValue() - }) - - var lastNext30Days []*av.View - if nil != next30Days { - lastNext30Days = append(lastNext30Days, next30Days) - } - if nil != next7Days { - lastNext30Days = append(lastNext30Days, next7Days) - } - if nil != tomorrow { - lastNext30Days = append(lastNext30Days, tomorrow) - } - if nil != today { - lastNext30Days = append(lastNext30Days, today) - } - if nil != yesterday { - lastNext30Days = append(lastNext30Days, yesterday) - } - - if nil != last7Days { - lastNext30Days = append(lastNext30Days, last7Days) - } - if nil != last30Days { - lastNext30Days = append(lastNext30Days, last30Days) - } - - startIdx := -1 - thisMonth := todayStart.Format("2006-01") - for i, monthGroup := range relativeDateGroups { - if monthGroup.GetGroupValue() < thisMonth { - startIdx = i + 1 - } - } - if -1 == startIdx { - startIdx = 0 - } - for _, g := range lastNext30Days { - relativeDateGroups = util.InsertElem(relativeDateGroups, startIdx, g) - } - if nil != defaultGroup { - relativeDateGroups = append([]*av.View{defaultGroup}, relativeDateGroups...) - } - - if av.GroupOrderDesc == view.Group.Order { - slices.Reverse(relativeDateGroups) - } - - view.Groups = relativeDateGroups - } else { - sort.SliceStable(view.Groups, func(i, j int) bool { - iVal, jVal := view.Groups[i].GetGroupValue(), view.Groups[j].GetGroupValue() - if av.GroupOrderAsc == view.Group.Order { - return util.NaturalCompare(iVal, jVal) - } - return util.NaturalCompare(jVal, iVal) - }) - } -} - func genAttrViewViewGroups(view *av.View, attrView *av.AttributeView) { if nil == view.Group { return @@ -2117,56 +1770,6 @@ func setAttrViewGroupStates(view *av.View, groupStates map[string]*GroupState) { } } -func isGroupByDate(view *av.View) bool { - if nil == view.Group { - 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 renderViewableInstance(viewable av.Viewable, view *av.View, attrView *av.AttributeView, page, pageSize int) (err error) { - if nil == viewable { - err = av.ErrViewNotFound - logging.LogErrorf("render attribute view [%s] failed", attrView.ID) - return - } - - av.Filter(viewable, attrView) - av.Sort(viewable, attrView) - av.Calc(viewable, attrView) - - // 分页 - switch viewable.GetType() { - case av.LayoutTypeTable: - table := viewable.(*av.Table) - table.RowCount = len(table.Rows) - table.PageSize = view.PageSize - if 1 > pageSize { - pageSize = table.PageSize - } - start := (page - 1) * pageSize - end := start + pageSize - if len(table.Rows) < end { - end = len(table.Rows) - } - table.Rows = table.Rows[start:end] - case av.LayoutTypeGallery: - gallery := viewable.(*av.Gallery) - gallery.CardCount = len(gallery.Cards) - gallery.PageSize = view.PageSize - if 1 > pageSize { - pageSize = gallery.PageSize - } - start := (page - 1) * pageSize - end := start + pageSize - if len(gallery.Cards) < end { - end = len(gallery.Cards) - } - gallery.Cards = gallery.Cards[start:end] - } - return -} - func GetCurrentAttributeViewImages(avID, viewID, query string) (ret []string, err error) { var attrView *av.AttributeView attrView, err = av.ParseAttributeView(avID) diff --git a/kernel/model/attribute_view_render.go b/kernel/model/attribute_view_render.go new file mode 100644 index 000000000..b900661c9 --- /dev/null +++ b/kernel/model/attribute_view_render.go @@ -0,0 +1,450 @@ +// 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 model + +import ( + "fmt" + "os" + "path/filepath" + "slices" + "sort" + "strconv" + "time" + + "github.com/88250/gulu" + "github.com/88250/lute/ast" + "github.com/siyuan-note/dejavu/entity" + "github.com/siyuan-note/filelock" + "github.com/siyuan-note/logging" + "github.com/siyuan-note/siyuan/kernel/av" + "github.com/siyuan-note/siyuan/kernel/sql" + "github.com/siyuan-note/siyuan/kernel/util" +) + +func RenderRepoSnapshotAttributeView(indexID, avID string) (viewable av.Viewable, attrView *av.AttributeView, err error) { + repo, err := newRepository() + if err != nil { + return + } + + index, err := repo.GetIndex(indexID) + if err != nil { + return + } + + files, err := repo.GetFiles(index) + if err != nil { + return + } + var avFile *entity.File + for _, f := range files { + if "/storage/av/"+avID+".json" == f.Path { + avFile = f + break + } + } + + if nil == avFile { + attrView = av.NewAttributeView(avID) + } else { + data, readErr := repo.OpenFile(avFile) + if nil != readErr { + logging.LogErrorf("read attribute view [%s] failed: %s", avID, readErr) + return + } + + attrView = &av.AttributeView{} + if err = gulu.JSON.UnmarshalJSON(data, attrView); err != nil { + logging.LogErrorf("unmarshal attribute view [%s] failed: %s", avID, err) + return + } + } + + viewable, err = renderAttributeView(attrView, "", "", "", 1, -1, nil) + return +} + +func RenderHistoryAttributeView(avID, created string) (viewable av.Viewable, attrView *av.AttributeView, err error) { + createdUnix, parseErr := strconv.ParseInt(created, 10, 64) + if nil != parseErr { + logging.LogErrorf("parse created [%s] failed: %s", created, parseErr) + return + } + + dirPrefix := time.Unix(createdUnix, 0).Format("2006-01-02-150405") + globPath := filepath.Join(util.HistoryDir, dirPrefix+"*") + matches, err := filepath.Glob(globPath) + if err != nil { + logging.LogErrorf("glob [%s] failed: %s", globPath, err) + return + } + if 1 > len(matches) { + return + } + + historyDir := matches[0] + avJSONPath := filepath.Join(historyDir, "storage", "av", avID+".json") + if !gulu.File.IsExist(avJSONPath) { + avJSONPath = filepath.Join(util.DataDir, "storage", "av", avID+".json") + } + if !gulu.File.IsExist(avJSONPath) { + attrView = av.NewAttributeView(avID) + } else { + data, readErr := os.ReadFile(avJSONPath) + if nil != readErr { + logging.LogErrorf("read attribute view [%s] failed: %s", avID, readErr) + return + } + + attrView = &av.AttributeView{} + if err = gulu.JSON.UnmarshalJSON(data, attrView); err != nil { + logging.LogErrorf("unmarshal attribute view [%s] failed: %s", avID, err) + return + } + } + + viewable, err = renderAttributeView(attrView, "", "", "", 1, -1, nil) + return +} + +func RenderAttributeView(blockID, avID, viewID, query string, page, pageSize int, groupPaging map[string]interface{}) (viewable av.Viewable, attrView *av.AttributeView, err error) { + waitForSyncingStorages() + + if avJSONPath := av.GetAttributeViewDataPath(avID); !filelock.IsExist(avJSONPath) { + attrView = av.NewAttributeView(avID) + if err = av.SaveAttributeView(attrView); err != nil { + logging.LogErrorf("save attribute view [%s] failed: %s", avID, err) + return + } + } + + attrView, err = av.ParseAttributeView(avID) + if err != nil { + logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err) + return + } + + viewable, err = renderAttributeView(attrView, blockID, viewID, query, page, pageSize, groupPaging) + return +} + +const ( + groupValueDefault = "_@default@_" // 默认分组值(值为空的默认分组) + groupValueNotInRange = "_@notInRange@_" // 不再范围内的分组值(只有数字类型的分组才可能是该值) + groupValueLast30Days, groupValueLast7Days = "_@last30Days@_", "_@last7Days@_" + groupValueYesterday, groupValueToday, groupValueTomorrow = "_@yesterday@_", "_@today@_", "_@tomorrow@_" + groupValueNext7Days, groupValueNext30Days = "_@next7Days@_", "_@next30Days@_" +) + +func renderAttributeView(attrView *av.AttributeView, blockID, viewID, query string, page, pageSize int, groupPaging map[string]interface{}) (viewable av.Viewable, err error) { + // 获取待渲染的视图 + view, err := getRenderAttributeViewView(attrView, blockID, viewID) + if nil != err { + return + } + + // 做一些数据兼容和订正处理 + checkAttrView(attrView, view) + upgradeAttributeViewSpec(attrView) + + // 渲染视图 + viewable = sql.RenderView(attrView, view, query) + err = renderViewableInstance(viewable, view, attrView, page, pageSize) + if nil != err { + return + } + + // 渲染分组视图 + err = renderAttributeViewGroups(viewable, attrView, view, query, page, pageSize, groupPaging) + return +} + +func renderAttributeViewGroups(viewable av.Viewable, attrView *av.AttributeView, view *av.View, query string, page, pageSize int, groupPaging map[string]interface{}) (err error) { + // 当前日期可能会变,所以如果是按日期分组则需要重新生成分组 + if isGroupByDate(view) { + createdDate := time.UnixMilli(view.GroupCreated).Format("2006-01-02") + if time.Now().Format("2006-01-02") != createdDate { + regenAttrViewViewGroups(attrView, "force") + av.SaveAttributeView(attrView) + } + } + + groupKey := view.GetGroupKey(attrView) + if nil == groupKey { + return + } + + fixDev := false + // 如果存在分组的话渲染分组视图 + for _, groupView := range view.Groups { + if "" == groupView.GetGroupValue() && !fixDev { + // TODO 分组上线后删除,预计 2025 年 9 月后可以删除 + regenAttrViewViewGroups(attrView, "force") + av.SaveAttributeView(attrView) + fixDev = true + } + + switch groupView.GetGroupValue() { + case groupValueDefault: + groupView.Name = fmt.Sprintf(Conf.language(264), groupKey.Name) + case groupValueNotInRange: + groupView.Name = Conf.language(265) + case groupValueLast30Days: + groupView.Name = fmt.Sprintf(Conf.language(259), 30) + case groupValueLast7Days: + groupView.Name = fmt.Sprintf(Conf.language(259), 7) + case groupValueYesterday: + groupView.Name = Conf.language(260) + case groupValueToday: + groupView.Name = Conf.language(261) + case groupValueTomorrow: + groupView.Name = Conf.language(262) + case groupValueNext7Days: + groupView.Name = fmt.Sprintf(Conf.language(263), 7) + case groupValueNext30Days: + groupView.Name = fmt.Sprintf(Conf.language(263), 30) + default: + groupView.Name = groupView.GetGroupValue() + } + } + + todayStart := time.Now() + todayStart = time.Date(todayStart.Year(), todayStart.Month(), todayStart.Day(), 0, 0, 0, 0, time.Local) + sortGroupViews(todayStart, view) + + var groups []av.Viewable + for _, groupView := range view.Groups { + groupViewable := sql.RenderGroupView(attrView, view, groupView, query) + + groupPage, groupPageSize := page, pageSize + if nil != groupPaging { + if paging := groupPaging[groupView.ID]; nil != paging { + pagingMap := paging.(map[string]interface{}) + if nil != pagingMap["page"] { + groupPage = int(pagingMap["page"].(float64)) + } + if nil != pagingMap["pageSize"] { + groupPageSize = int(pagingMap["pageSize"].(float64)) + } + } + } + + err = renderViewableInstance(groupViewable, view, attrView, groupPage, groupPageSize) + if nil != err { + return + } + groups = append(groups, groupViewable) + + // 将分组视图的分组字段清空,减少冗余(字段信息可以在总的视图 view 对象上获取到) + switch groupView.LayoutType { + case av.LayoutTypeTable: + groupView.Table.Columns = nil + case av.LayoutTypeGallery: + groupView.Gallery.CardFields = nil + } + } + viewable.SetGroups(groups) + + // 将总的视图上的项目清空,减少冗余 + viewable.(av.Collection).SetItems(nil) + return +} + +func sortGroupViews(todayStart time.Time, view *av.View) { + if av.GroupOrderMan == view.Group.Order { + sort.Slice(view.Groups, func(i, j int) bool { return view.Groups[i].GroupSort < view.Groups[j].GroupSort }) + return + } + + if av.GroupMethodDateRelative == view.Group.Method { + var relativeDateGroups []*av.View + var last30Days, last7Days, yesterday, today, tomorrow, next7Days, next30Days, defaultGroup *av.View + for _, groupView := range view.Groups { + _, err := time.Parse("2006-01", groupView.GetGroupValue()) + if nil == err { // 如果能解析出来说明是 30 天之前或 30 天之后的分组形式 + relativeDateGroups = append(relativeDateGroups, groupView) + } else { // 否则是相对日期分组形式 + switch groupView.GetGroupValue() { + case groupValueLast30Days: + last30Days = groupView + case groupValueLast7Days: + last7Days = groupView + case groupValueYesterday: + yesterday = groupView + case groupValueToday: + today = groupView + case groupValueTomorrow: + tomorrow = groupView + case groupValueNext7Days: + next7Days = groupView + case groupValueNext30Days: + next30Days = groupView + case groupValueDefault: + defaultGroup = groupView + } + } + } + + sort.SliceStable(relativeDateGroups, func(i, j int) bool { + return relativeDateGroups[i].GetGroupValue() < relativeDateGroups[j].GetGroupValue() + }) + + var lastNext30Days []*av.View + if nil != next30Days { + lastNext30Days = append(lastNext30Days, next30Days) + } + if nil != next7Days { + lastNext30Days = append(lastNext30Days, next7Days) + } + if nil != tomorrow { + lastNext30Days = append(lastNext30Days, tomorrow) + } + if nil != today { + lastNext30Days = append(lastNext30Days, today) + } + if nil != yesterday { + lastNext30Days = append(lastNext30Days, yesterday) + } + + if nil != last7Days { + lastNext30Days = append(lastNext30Days, last7Days) + } + if nil != last30Days { + lastNext30Days = append(lastNext30Days, last30Days) + } + + startIdx := -1 + thisMonth := todayStart.Format("2006-01") + for i, monthGroup := range relativeDateGroups { + if monthGroup.GetGroupValue() < thisMonth { + startIdx = i + 1 + } + } + if -1 == startIdx { + startIdx = 0 + } + for _, g := range lastNext30Days { + relativeDateGroups = util.InsertElem(relativeDateGroups, startIdx, g) + } + if nil != defaultGroup { + relativeDateGroups = append([]*av.View{defaultGroup}, relativeDateGroups...) + } + + if av.GroupOrderDesc == view.Group.Order { + slices.Reverse(relativeDateGroups) + } + + view.Groups = relativeDateGroups + } else { + sort.SliceStable(view.Groups, func(i, j int) bool { + iVal, jVal := view.Groups[i].GetGroupValue(), view.Groups[j].GetGroupValue() + if av.GroupOrderAsc == view.Group.Order { + return util.NaturalCompare(iVal, jVal) + } + return util.NaturalCompare(jVal, iVal) + }) + } +} + +func isGroupByDate(view *av.View) bool { + if nil == view.Group { + 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 renderViewableInstance(viewable av.Viewable, view *av.View, attrView *av.AttributeView, page, pageSize int) (err error) { + if nil == viewable { + err = av.ErrViewNotFound + logging.LogErrorf("render attribute view [%s] failed", attrView.ID) + return + } + + av.Filter(viewable, attrView) + av.Sort(viewable, attrView) + av.Calc(viewable, attrView) + + // 分页 + switch viewable.GetType() { + case av.LayoutTypeTable: + table := viewable.(*av.Table) + table.RowCount = len(table.Rows) + table.PageSize = view.PageSize + if 1 > pageSize { + pageSize = table.PageSize + } + start := (page - 1) * pageSize + end := start + pageSize + if len(table.Rows) < end { + end = len(table.Rows) + } + table.Rows = table.Rows[start:end] + case av.LayoutTypeGallery: + gallery := viewable.(*av.Gallery) + gallery.CardCount = len(gallery.Cards) + gallery.PageSize = view.PageSize + if 1 > pageSize { + pageSize = gallery.PageSize + } + start := (page - 1) * pageSize + end := start + pageSize + if len(gallery.Cards) < end { + end = len(gallery.Cards) + } + gallery.Cards = gallery.Cards[start:end] + } + return +} + +func getRenderAttributeViewView(attrView *av.AttributeView, viewID, blockID string) (ret *av.View, err error) { + if 1 > len(attrView.Views) { + view, _, _ := av.NewTableViewWithBlockKey(ast.NewNodeID()) + attrView.Views = append(attrView.Views, view) + attrView.ViewID = view.ID + if err = av.SaveAttributeView(attrView); err != nil { + logging.LogErrorf("save attribute view [%s] failed: %s", attrView.ID, err) + return + } + } + + if "" == viewID && "" != blockID { + node, _, getErr := getNodeByBlockID(nil, blockID) + if nil != getErr { + logging.LogWarnf("get node by block ID [%s] failed: %s", blockID, getErr) + } else { + viewID = node.IALAttr(av.NodeAttrView) + } + } + + if "" != viewID { + ret, _ = attrView.GetCurrentView(viewID) + if nil != ret && ret.ID != attrView.ViewID { + attrView.ViewID = ret.ID + if err = av.SaveAttributeView(attrView); err != nil { + logging.LogErrorf("save attribute view [%s] failed: %s", attrView.ID, err) + return + } + } + } else { + ret = attrView.GetView(attrView.ViewID) + } + + if nil == ret { + ret = attrView.Views[0] + } + return +}