A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 547 lines 16 kB view raw
1// SiYuan - Refactor your thinking 2// Copyright (c) 2020-present, b3log.org 3// 4// This program is free software: you can redistribute it and/or modify 5// it under the terms of the GNU Affero General Public License as published by 6// the Free Software Foundation, either version 3 of the License, or 7// (at your option) any later version. 8// 9// This program is distributed in the hope that it will be useful, 10// but WITHOUT ANY WARRANTY; without even the implied warranty of 11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12// GNU Affero General Public License for more details. 13// 14// You should have received a copy of the GNU Affero General Public License 15// along with this program. If not, see <https://www.gnu.org/licenses/>. 16 17package model 18 19import ( 20 "fmt" 21 "os" 22 "path/filepath" 23 "slices" 24 "sort" 25 "strconv" 26 "time" 27 28 "github.com/88250/gulu" 29 "github.com/88250/lute/ast" 30 "github.com/siyuan-note/dejavu/entity" 31 "github.com/siyuan-note/filelock" 32 "github.com/siyuan-note/logging" 33 "github.com/siyuan-note/siyuan/kernel/av" 34 "github.com/siyuan-note/siyuan/kernel/sql" 35 "github.com/siyuan-note/siyuan/kernel/util" 36) 37 38func RenderAttributeView(blockID, avID, viewID, query string, page, pageSize int, groupPaging map[string]interface{}) (viewable av.Viewable, attrView *av.AttributeView, err error) { 39 waitForSyncingStorages() 40 41 if avJSONPath := av.GetAttributeViewDataPath(avID); !filelock.IsExist(avJSONPath) { 42 attrView = av.NewAttributeView(avID) 43 if err = av.SaveAttributeView(attrView); err != nil { 44 logging.LogErrorf("save attribute view [%s] failed: %s", avID, err) 45 return 46 } 47 } 48 49 attrView, err = av.ParseAttributeView(avID) 50 if err != nil { 51 logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err) 52 return 53 } 54 55 viewable, err = renderAttributeView(attrView, blockID, viewID, query, page, pageSize, groupPaging) 56 return 57} 58 59const ( 60 groupValueDefault = "_@default@_" // 默认分组值(值为空的默认分组) 61 groupValueNotInRange = "_@notInRange@_" // 不再范围内的分组值(只有数字类型的分组才可能是该值) 62 groupValueLast30Days, groupValueLast7Days = "_@last30Days@_", "_@last7Days@_" 63 groupValueYesterday, groupValueToday, groupValueTomorrow = "_@yesterday@_", "_@today@_", "_@tomorrow@_" 64 groupValueNext7Days, groupValueNext30Days = "_@next7Days@_", "_@next30Days@_" 65) 66 67func renderAttributeView(attrView *av.AttributeView, nodeID, viewID, query string, page, pageSize int, groupPaging map[string]interface{}) (viewable av.Viewable, err error) { 68 // 获取待渲染的视图 69 view, err := getRenderAttributeViewView(attrView, viewID, nodeID) 70 if nil != err { 71 return 72 } 73 74 // 做一些数据兼容和订正处理 75 checkAttrView(attrView, view) 76 upgradeAttributeViewSpec(attrView) 77 78 // 渲染视图 79 viewable = sql.RenderView(attrView, view, query) 80 err = renderViewableInstance(viewable, view, attrView, page, pageSize) 81 if nil != err { 82 return 83 } 84 85 // 渲染分组视图 86 err = renderAttributeViewGroups(viewable, attrView, view, query, page, pageSize, groupPaging) 87 return 88} 89 90func renderAttributeViewGroups(viewable av.Viewable, attrView *av.AttributeView, view *av.View, query string, page, pageSize int, groupPaging map[string]interface{}) (err error) { 91 groupKey := view.GetGroupKey(attrView) 92 if nil == groupKey { 93 return 94 } 95 96 // 当前日期可能会变,所以如果是按日期分组则需要重新生成分组 97 if isGroupByDate(view) { 98 createdDate := time.UnixMilli(view.GroupCreated).Format("2006-01-02") 99 if time.Now().Format("2006-01-02") != createdDate { 100 genAttrViewGroups(view, attrView) // 仅重新生成一个视图的分组以提升性能 101 av.SaveAttributeView(attrView) 102 } 103 } 104 105 // 如果是按模板分组则需要重新生成分组 106 if isGroupByTemplate(attrView, view) { 107 genAttrViewGroups(view, attrView) // 仅重新生成一个视图的分组以提升性能 108 av.SaveAttributeView(attrView) 109 } 110 111 // 如果存在分组的话渲染分组视图 112 113 for _, groupView := range view.Groups { 114 groupView.Name = groupView.GetGroupValue() 115 switch groupView.Name { 116 case groupValueDefault: 117 groupView.Name = fmt.Sprintf(Conf.language(264), groupKey.Name) 118 case groupValueNotInRange: 119 groupView.Name = Conf.language(265) 120 case groupValueLast30Days: 121 groupView.Name = fmt.Sprintf(Conf.language(259), 30) 122 case groupValueLast7Days: 123 groupView.Name = fmt.Sprintf(Conf.language(259), 7) 124 case groupValueYesterday: 125 groupView.Name = Conf.language(260) 126 case groupValueToday: 127 groupView.Name = Conf.language(261) 128 case groupValueTomorrow: 129 groupView.Name = Conf.language(262) 130 case groupValueNext7Days: 131 groupView.Name = fmt.Sprintf(Conf.language(263), 7) 132 case groupValueNext30Days: 133 groupView.Name = fmt.Sprintf(Conf.language(263), 30) 134 } 135 } 136 137 sortGroupViews(attrView, view) 138 139 var groups []av.Viewable 140 for _, groupView := range view.Groups { 141 groupViewable := sql.RenderGroupView(attrView, view, groupView, query) 142 143 groupPage, groupPageSize := page, pageSize 144 if nil != groupPaging { 145 if paging := groupPaging[groupView.ID]; nil != paging { 146 pagingMap := paging.(map[string]interface{}) 147 if nil != pagingMap["page"] { 148 groupPage = int(pagingMap["page"].(float64)) 149 } 150 if nil != pagingMap["pageSize"] { 151 groupPageSize = int(pagingMap["pageSize"].(float64)) 152 } 153 } 154 } 155 156 err = renderViewableInstance(groupViewable, view, attrView, groupPage, groupPageSize) 157 if nil != err { 158 return 159 } 160 161 hideEmptyGroupViews(view, groupViewable) 162 groups = append(groups, groupViewable) 163 164 // 将分组视图的分组字段清空,减少冗余(字段信息可以在总的视图 view 对象上获取到) 165 switch groupView.LayoutType { 166 case av.LayoutTypeTable: 167 groupView.Table.Columns = nil 168 case av.LayoutTypeGallery: 169 groupView.Gallery.CardFields = nil 170 case av.LayoutTypeKanban: 171 groupView.Kanban.Fields = nil 172 } 173 } 174 viewable.SetGroups(groups) 175 176 // 将总的视图上的项目清空,减少冗余 177 viewable.(av.Collection).SetItems(nil) 178 return 179} 180 181func hideEmptyGroupViews(view *av.View, viewable av.Viewable) { 182 if !view.IsGroupView() { 183 return 184 } 185 186 groupHidden := viewable.GetGroupHidden() 187 if !view.Group.HideEmpty { 188 if 2 != groupHidden { 189 viewable.SetGroupHidden(0) 190 } 191 return 192 } 193 194 itemCount := viewable.(av.Collection).CountItems() 195 if 1 == groupHidden && 0 < itemCount { 196 viewable.SetGroupHidden(0) 197 } 198} 199 200func sortGroupViews(attrView *av.AttributeView, view *av.View) { 201 if av.GroupOrderMan == view.Group.Order { 202 sort.Slice(view.Groups, func(i, j int) bool { return view.Groups[i].GroupSort < view.Groups[j].GroupSort }) 203 return 204 } 205 206 if av.GroupMethodDateRelative == view.Group.Method { 207 var relativeDateGroups []*av.View 208 var last30Days, last7Days, yesterday, today, tomorrow, next7Days, next30Days, defaultGroup *av.View 209 for _, groupView := range view.Groups { 210 _, err := time.Parse("2006-01", groupView.GetGroupValue()) 211 if nil == err { // 如果能解析出来说明是 30 天之前或 30 天之后的分组形式 212 relativeDateGroups = append(relativeDateGroups, groupView) 213 } else { // 否则是相对日期分组形式 214 switch groupView.GetGroupValue() { 215 case groupValueLast30Days: 216 last30Days = groupView 217 case groupValueLast7Days: 218 last7Days = groupView 219 case groupValueYesterday: 220 yesterday = groupView 221 case groupValueToday: 222 today = groupView 223 case groupValueTomorrow: 224 tomorrow = groupView 225 case groupValueNext7Days: 226 next7Days = groupView 227 case groupValueNext30Days: 228 next30Days = groupView 229 case groupValueDefault: 230 defaultGroup = groupView 231 } 232 } 233 } 234 235 sort.SliceStable(relativeDateGroups, func(i, j int) bool { 236 return relativeDateGroups[i].GetGroupValue() < relativeDateGroups[j].GetGroupValue() 237 }) 238 239 var lastNext30Days []*av.View 240 if nil != next30Days { 241 lastNext30Days = append(lastNext30Days, next30Days) 242 } 243 if nil != next7Days { 244 lastNext30Days = append(lastNext30Days, next7Days) 245 } 246 if nil != tomorrow { 247 lastNext30Days = append(lastNext30Days, tomorrow) 248 } 249 if nil != today { 250 lastNext30Days = append(lastNext30Days, today) 251 } 252 if nil != yesterday { 253 lastNext30Days = append(lastNext30Days, yesterday) 254 } 255 256 if nil != last7Days { 257 lastNext30Days = append(lastNext30Days, last7Days) 258 } 259 if nil != last30Days { 260 lastNext30Days = append(lastNext30Days, last30Days) 261 } 262 263 startIdx := -1 264 todayStart := util.GetTodayStart() 265 thisMonth := todayStart.Format("2006-01") 266 for i, monthGroup := range relativeDateGroups { 267 if monthGroup.GetGroupValue() < thisMonth { 268 startIdx = i + 1 269 } 270 } 271 if -1 == startIdx { 272 startIdx = 0 273 } 274 for _, g := range lastNext30Days { 275 relativeDateGroups = util.InsertElem(relativeDateGroups, startIdx, g) 276 } 277 278 if av.GroupOrderDesc == view.Group.Order { 279 slices.Reverse(relativeDateGroups) 280 } 281 282 if nil != defaultGroup { 283 relativeDateGroups = append(relativeDateGroups, defaultGroup) 284 } 285 286 view.Groups = relativeDateGroups 287 return 288 } 289 290 if av.GroupOrderAsc == view.Group.Order || av.GroupOrderDesc == view.Group.Order { 291 defaultGroup := view.GetGroupByGroupValue(groupValueDefault) 292 if nil != defaultGroup { 293 view.RemoveGroupByID(defaultGroup.ID) 294 } 295 296 sort.SliceStable(view.Groups, func(i, j int) bool { 297 iVal, jVal := view.Groups[i].GetGroupValue(), view.Groups[j].GetGroupValue() 298 if av.GroupOrderAsc == view.Group.Order { 299 return util.NaturalCompare(iVal, jVal) 300 } 301 return util.NaturalCompare(jVal, iVal) 302 }) 303 304 if nil != defaultGroup { 305 view.Groups = append(view.Groups, defaultGroup) 306 } 307 return 308 } 309 310 if av.GroupOrderSelectOption == view.Group.Order { 311 groupKey := view.GetGroupKey(attrView) 312 if nil == groupKey { 313 return 314 } 315 316 if av.KeyTypeSelect != groupKey.Type && av.KeyTypeMSelect != groupKey.Type { 317 return 318 } 319 320 sortGroupsBySelectOption(view, groupKey) 321 return 322 } 323} 324 325func sortGroupsBySelectOption(view *av.View, groupKey *av.Key) { 326 optionSort := map[string]int{} 327 for i, op := range groupKey.Options { 328 optionSort[op.Name] = i 329 } 330 331 defaultGroup := view.GetGroupByGroupValue(groupValueDefault) 332 if nil != defaultGroup { 333 view.RemoveGroupByID(defaultGroup.ID) 334 } 335 336 sort.Slice(view.Groups, func(i, j int) bool { 337 vSort := optionSort[view.Groups[i].GetGroupValue()] 338 oSort := optionSort[view.Groups[j].GetGroupValue()] 339 return vSort < oSort 340 }) 341 342 if nil != defaultGroup { 343 view.Groups = append(view.Groups, defaultGroup) 344 } 345} 346 347func isGroupByDate(view *av.View) bool { 348 if !view.IsGroupView() { 349 return false 350 } 351 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 352} 353 354func isGroupByTemplate(attrView *av.AttributeView, view *av.View) bool { 355 if !view.IsGroupView() { 356 return false 357 } 358 359 groupKey := view.GetGroupKey(attrView) 360 if nil == groupKey { 361 return false 362 } 363 return av.KeyTypeTemplate == groupKey.Type 364} 365 366func renderViewableInstance(viewable av.Viewable, view *av.View, attrView *av.AttributeView, page, pageSize int) (err error) { 367 if nil == viewable { 368 err = av.ErrViewNotFound 369 logging.LogErrorf("render attribute view [%s] failed", attrView.ID) 370 return 371 } 372 373 cachedAttrViews := map[string]*av.AttributeView{} 374 rollupFurtherCollections := sql.GetFurtherCollections(attrView, cachedAttrViews) 375 av.Filter(viewable, attrView, rollupFurtherCollections, cachedAttrViews) 376 av.Sort(viewable, attrView) 377 av.Calc(viewable, attrView) 378 379 // 分页 380 switch viewable.GetType() { 381 case av.LayoutTypeTable: 382 table := viewable.(*av.Table) 383 table.RowCount = len(table.Rows) 384 table.PageSize = view.PageSize 385 if 1 > pageSize { 386 pageSize = table.PageSize 387 } 388 start := (page - 1) * pageSize 389 end := start + pageSize 390 if len(table.Rows) < end { 391 end = len(table.Rows) 392 } 393 table.Rows = table.Rows[start:end] 394 case av.LayoutTypeGallery: 395 gallery := viewable.(*av.Gallery) 396 gallery.CardCount = len(gallery.Cards) 397 gallery.PageSize = view.PageSize 398 if 1 > pageSize { 399 pageSize = gallery.PageSize 400 } 401 start := (page - 1) * pageSize 402 end := start + pageSize 403 if len(gallery.Cards) < end { 404 end = len(gallery.Cards) 405 } 406 gallery.Cards = gallery.Cards[start:end] 407 case av.LayoutTypeKanban: 408 kanban := viewable.(*av.Kanban) 409 kanban.CardCount = 0 410 kanban.PageSize = view.PageSize 411 if 1 > pageSize { 412 pageSize = kanban.PageSize 413 } 414 start := (page - 1) * pageSize 415 end := start + pageSize 416 if len(kanban.Cards) < end { 417 end = len(kanban.Cards) 418 } 419 kanban.Cards = kanban.Cards[start:end] 420 } 421 return 422} 423 424func getRenderAttributeViewView(attrView *av.AttributeView, viewID, nodeID string) (ret *av.View, err error) { 425 if 1 > len(attrView.Views) { 426 view, _, _ := av.NewTableViewWithBlockKey(ast.NewNodeID()) 427 attrView.Views = append(attrView.Views, view) 428 attrView.ViewID = view.ID 429 if err = av.SaveAttributeView(attrView); err != nil { 430 logging.LogErrorf("save attribute view [%s] failed: %s", attrView.ID, err) 431 return 432 } 433 } 434 435 if "" == viewID && "" != nodeID { 436 node, _, _ := getNodeByBlockID(nil, nodeID) 437 if nil != node { 438 viewID = node.IALAttr(av.NodeAttrView) 439 } 440 } 441 442 if "" != viewID { 443 ret, _ = attrView.GetCurrentView(viewID) 444 if nil != ret && ret.ID != attrView.ViewID { 445 attrView.ViewID = ret.ID 446 if err = av.SaveAttributeView(attrView); err != nil { 447 logging.LogErrorf("save attribute view [%s] failed: %s", attrView.ID, err) 448 return 449 } 450 } 451 } else { 452 ret = attrView.GetView(attrView.ViewID) 453 } 454 455 if nil == ret { 456 ret = attrView.Views[0] 457 } 458 return 459} 460 461func RenderRepoSnapshotAttributeView(indexID, avID string) (viewable av.Viewable, attrView *av.AttributeView, err error) { 462 repo, err := newRepository() 463 if err != nil { 464 return 465 } 466 467 index, err := repo.GetIndex(indexID) 468 if err != nil { 469 return 470 } 471 472 files, err := repo.GetFiles(index) 473 if err != nil { 474 return 475 } 476 var avFile *entity.File 477 for _, f := range files { 478 if "/storage/av/"+avID+".json" == f.Path { 479 avFile = f 480 break 481 } 482 } 483 484 if nil == avFile { 485 attrView = av.NewAttributeView(avID) 486 } else { 487 data, readErr := repo.OpenFile(avFile) 488 if nil != readErr { 489 logging.LogErrorf("read attribute view [%s] failed: %s", avID, readErr) 490 return 491 } 492 493 attrView = &av.AttributeView{RenderedViewables: map[string]av.Viewable{}} 494 if err = gulu.JSON.UnmarshalJSON(data, attrView); err != nil { 495 logging.LogErrorf("unmarshal attribute view [%s] failed: %s", avID, err) 496 return 497 } 498 } 499 500 viewable, err = renderAttributeView(attrView, "", "", "", 1, -1, nil) 501 return 502} 503 504func RenderHistoryAttributeView(blockID, avID, viewID, query string, page, pageSize int, groupPaging map[string]interface{}, created string) (viewable av.Viewable, attrView *av.AttributeView, err error) { 505 createdUnix, parseErr := strconv.ParseInt(created, 10, 64) 506 if nil != parseErr { 507 logging.LogErrorf("parse created [%s] failed: %s", created, parseErr) 508 return 509 } 510 511 dirPrefix := time.Unix(createdUnix, 0).Format("2006-01-02-150405") 512 globPath := filepath.Join(util.HistoryDir, dirPrefix+"*") 513 matches, err := filepath.Glob(globPath) 514 if err != nil { 515 logging.LogErrorf("glob [%s] failed: %s", globPath, err) 516 return 517 } 518 if 1 > len(matches) { 519 return 520 } 521 522 historyDir := matches[0] 523 avJSONPath := filepath.Join(historyDir, "storage", "av", avID+".json") 524 if !gulu.File.IsExist(avJSONPath) { 525 logging.LogWarnf("attribute view [%s] not found in history data [%s], use current data instead", avID, historyDir) 526 avJSONPath = filepath.Join(util.DataDir, "storage", "av", avID+".json") 527 } 528 if !gulu.File.IsExist(avJSONPath) { 529 logging.LogWarnf("attribute view [%s] not found in current data", avID) 530 attrView = av.NewAttributeView(avID) 531 } else { 532 data, readErr := os.ReadFile(avJSONPath) 533 if nil != readErr { 534 logging.LogErrorf("read attribute view [%s] failed: %s", avID, readErr) 535 return 536 } 537 538 attrView = &av.AttributeView{RenderedViewables: map[string]av.Viewable{}} 539 if err = gulu.JSON.UnmarshalJSON(data, attrView); err != nil { 540 logging.LogErrorf("unmarshal attribute view [%s] failed: %s", avID, err) 541 return 542 } 543 } 544 545 viewable, err = renderAttributeView(attrView, blockID, viewID, query, page, pageSize, groupPaging) 546 return 547}