Something went wrong. Try again.
A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613// 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 <https://www.gnu.org/licenses/>.
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 RenderAttributeView(blockID, avID, viewID, query string, page, pageSize int, groupPaging map[string]interface{}, createIfNotExist bool) (viewable av.Viewable, attrView *av.AttributeView, err error) { waitForSyncingStorages()
if avJSONPath := av.GetAttributeViewDataPath(avID); !filelock.IsExist(avJSONPath) { if !createIfNotExist { err = av.ErrAttributeViewNotFound return }
if !ast.IsNodeIDPattern(avID) { err = ErrInvalidID return }
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, nodeID, viewID, query string, page, pageSize int, groupPaging map[string]interface{}) (viewable av.Viewable, err error) { // 获取待渲染的视图 view, err := getRenderAttributeViewView(attrView, viewID, nodeID) 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) { groupKey := view.GetGroupKey(attrView) if nil == groupKey { if view.LayoutType == av.LayoutTypeKanban { preferredGroupKey := getKanbanPreferredGroupKey(attrView) group := &av.ViewGroup{Field: preferredGroupKey.ID} setAttributeViewGroup(attrView, view, group) if err = av.SaveAttributeView(attrView); err != nil { logging.LogErrorf("save attribute view [%s] failed: %s", attrView.ID, err) return } groupKey = view.GetGroupKey(attrView) if nil == groupKey { return } } else { return } }
// 当前日期可能会变,所以如果是按日期分组则需要重新生成分组 if isGroupByDate(view) { createdDate := time.UnixMilli(view.GroupCreated).Format("2006-01-02") if time.Now().Format("2006-01-02") != createdDate { genAttrViewGroups(view, attrView) // 仅重新生成一个视图的分组以提升性能 if err = av.SaveAttributeView(attrView); err != nil { logging.LogErrorf("save attribute view [%s] failed: %s", attrView.ID, err) return } } }
// 如果是按模板分组则需要重新生成分组 if isGroupByTemplate(attrView, view) { genAttrViewGroups(view, attrView) // 仅重新生成一个视图的分组以提升性能 if err = av.SaveAttributeView(attrView); err != nil { logging.LogErrorf("save attribute view [%s] failed: %s", attrView.ID, err) return } }
// 渲染分组视图 if nil == view.Groups { genAttrViewGroups(view, attrView) if err = av.SaveAttributeView(attrView); err != nil { logging.LogErrorf("save attribute view [%s] failed: %s", attrView.ID, err) return } }
for _, groupView := range view.Groups { groupView.Name = groupView.GetGroupValue() switch groupView.Name { 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) } }
sortGroupViews(attrView, 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 }
hideEmptyGroupViews(view, groupViewable) groups = append(groups, groupViewable)
// 将分组视图的分组字段清空,减少冗余(字段信息可以在总的视图 view 对象上获取到) switch groupView.LayoutType { case av.LayoutTypeTable: groupView.Table.Columns = nil case av.LayoutTypeGallery: groupView.Gallery.CardFields = nil case av.LayoutTypeKanban: groupView.Kanban.Fields = nil } } viewable.SetGroups(groups)
// 将总的视图上的项目清空,减少冗余 viewable.(av.Collection).SetItems(nil) return}
func hideEmptyGroupViews(view *av.View, viewable av.Viewable) { if !view.IsGroupView() { return }
groupHidden := viewable.GetGroupHidden() if !view.Group.HideEmpty { if 2 != groupHidden { viewable.SetGroupHidden(0) } return }
itemCount := viewable.(av.Collection).CountItems() if 1 == groupHidden && 0 < itemCount { viewable.SetGroupHidden(0) }}
func sortGroupViews(attrView *av.AttributeView, 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 todayStart := util.GetTodayStart() 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 av.GroupOrderDesc == view.Group.Order { slices.Reverse(relativeDateGroups) }
if nil != defaultGroup { relativeDateGroups = append(relativeDateGroups, defaultGroup) }
view.Groups = relativeDateGroups return }
if av.GroupOrderAsc == view.Group.Order || av.GroupOrderDesc == view.Group.Order { defaultGroup := view.GetGroupByGroupValue(groupValueDefault) if nil != defaultGroup { view.RemoveGroupByID(defaultGroup.ID) }
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) })
if nil != defaultGroup { view.Groups = append(view.Groups, defaultGroup) } return }
if av.GroupOrderSelectOption == view.Group.Order { groupKey := view.GetGroupKey(attrView) if nil == groupKey { return }
if av.KeyTypeSelect != groupKey.Type && av.KeyTypeMSelect != groupKey.Type { return }
sortGroupsBySelectOption(view, groupKey) return }}
func sortGroupsBySelectOption(view *av.View, groupKey *av.Key) { optionSort := map[string]int{} for i, op := range groupKey.Options { optionSort[op.Name] = i }
defaultGroup := view.GetGroupByGroupValue(groupValueDefault) if nil != defaultGroup { view.RemoveGroupByID(defaultGroup.ID) }
sort.Slice(view.Groups, func(i, j int) bool { vSort := optionSort[view.Groups[i].GetGroupValue()] oSort := optionSort[view.Groups[j].GetGroupValue()] return vSort < oSort })
if nil != defaultGroup { view.Groups = append(view.Groups, defaultGroup) }}
func isGroupByDate(view *av.View) bool { 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 !view.IsGroupView() { return false }
groupKey := view.GetGroupKey(attrView) if nil == groupKey { return false } return av.KeyTypeTemplate == groupKey.Type}
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 }
cachedAttrViews := map[string]*av.AttributeView{} rollupFurtherCollections := sql.GetFurtherCollections(attrView, cachedAttrViews) av.Filter(viewable, attrView, rollupFurtherCollections, cachedAttrViews) 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] case av.LayoutTypeKanban: kanban := viewable.(*av.Kanban) kanban.CardCount = len(kanban.Cards) kanban.PageSize = view.PageSize if 1 > pageSize { pageSize = kanban.PageSize } start := (page - 1) * pageSize end := start + pageSize if len(kanban.Cards) < end { end = len(kanban.Cards) } kanban.Cards = kanban.Cards[start:end] } return}
func getRenderAttributeViewView(attrView *av.AttributeView, viewID, nodeID 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 && "" != nodeID { node, _, _ := getNodeByBlockID(nil, nodeID) if nil != node { 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}
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 { if !ast.IsNodeIDPattern(avID) { err = ErrInvalidID return }
attrView = av.NewAttributeView(avID) err = av.ErrAttributeViewNotFound return } else { data, readErr := repo.OpenFile(avFile) if nil != readErr { logging.LogErrorf("read attribute view [%s] failed: %s", avID, readErr) err = readErr return }
if !ast.IsNodeIDPattern(avID) { err = ErrInvalidID return }
attrView = av.NewAttributeView(avID) 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(blockID, avID, viewID, query string, page, pageSize int, groupPaging map[string]interface{}, 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) err = fmt.Errorf("parse created [%s] failed: %w", 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) { err = av.ErrAttributeViewNotFound return }
historyDir := matches[0] avJSONPath := filepath.Join(historyDir, "storage", "av", avID+".json") if !gulu.File.IsExist(avJSONPath) { logging.LogWarnf("attribute view [%s] not found in history data [%s], use current data instead", avID, historyDir) avJSONPath = filepath.Join(util.DataDir, "storage", "av", avID+".json") } if !gulu.File.IsExist(avJSONPath) { logging.LogWarnf("attribute view [%s] not found in current data", avID) if !ast.IsNodeIDPattern(avID) { err = ErrInvalidID return }
attrView = av.NewAttributeView(avID) err = av.ErrAttributeViewNotFound return } else { data, readErr := os.ReadFile(avJSONPath) if nil != readErr { logging.LogErrorf("read attribute view [%s] failed: %s", avID, readErr) err = readErr return }
if !ast.IsNodeIDPattern(avID) { err = ErrInvalidID return }
attrView = av.NewAttributeView(avID) if err = gulu.JSON.UnmarshalJSON(data, attrView); err != nil { logging.LogErrorf("unmarshal attribute view [%s] failed: %s", avID, err) return } }
viewable, err = renderAttributeView(attrView, blockID, viewID, query, page, pageSize, groupPaging) return}