2023-06-24 20:39:55 +08:00
// SiYuan - Refactor your thinking
2023-03-02 09:12:28 +08:00
// 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/>.
2023-03-02 09:27:07 +08:00
// Package av 包含了属性视图( Attribute View) 相关的实现。
2023-03-02 09:12:28 +08:00
package av
import (
2023-07-11 22:11:15 +08:00
"errors"
2025-08-24 10:49:13 +08:00
"fmt"
2023-03-02 18:49:05 +08:00
"os"
2023-03-02 09:12:28 +08:00
"path/filepath"
2024-07-04 16:59:58 +08:00
"sort"
2024-03-08 00:12:10 +08:00
"strings"
2023-03-02 09:12:28 +08:00
"github.com/88250/gulu"
2023-03-03 10:49:45 +08:00
"github.com/88250/lute/ast"
2024-03-08 20:38:32 +08:00
jsoniter "github.com/json-iterator/go"
2023-03-02 09:12:28 +08:00
"github.com/siyuan-note/filelock"
"github.com/siyuan-note/logging"
"github.com/siyuan-note/siyuan/kernel/util"
)
// AttributeView 描述了属性视图的结构。
type AttributeView struct {
2023-07-12 21:39:55 +08:00
Spec int ` json:"spec" ` // 格式版本
ID string ` json:"id" ` // 属性视图 ID
Name string ` json:"name" ` // 属性视图名称
2024-05-16 22:24:15 +08:00
KeyValues [ ] * KeyValues ` json:"keyValues" ` // 属性视图属性键值
KeyIDs [ ] string ` json:"keyIDs" ` // 属性视图属性键 ID, 用于排序
2023-07-12 21:39:55 +08:00
ViewID string ` json:"viewID" ` // 当前视图 ID
Views [ ] * View ` json:"views" ` // 视图
2023-07-12 19:10:05 +08:00
}
2025-06-08 16:12:37 +08:00
// KeyValues 描述了属性视图属性键值列表的结构。
2023-07-12 19:10:05 +08:00
type KeyValues struct {
2025-06-08 16:12:37 +08:00
Key * Key ` json:"key" ` // 属性视图属性键
Values [ ] * Value ` json:"values,omitempty" ` // 属性视图属性值列表
2023-07-12 19:10:05 +08:00
}
2023-12-24 22:27:38 +08:00
func ( kValues * KeyValues ) GetValue ( blockID string ) ( ret * Value ) {
for _ , v := range kValues . Values {
if v . BlockID == blockID {
ret = v
return
}
}
return
}
2024-05-31 23:55:17 +08:00
func ( kValues * KeyValues ) GetBlockValue ( ) ( ret * Value ) {
for _ , v := range kValues . Values {
2025-06-23 10:45:45 +08:00
if KeyTypeBlock == v . Type {
2024-05-31 23:55:17 +08:00
ret = v
return
}
}
return
}
func GetKeyBlockValue ( blockKeyValues [ ] * KeyValues ) ( ret * Value ) {
for _ , kv := range blockKeyValues {
if KeyTypeBlock == kv . Key . Type && 0 < len ( kv . Values ) {
ret = kv . Values [ 0 ]
break
}
}
return
}
2025-08-22 21:30:59 +08:00
func GetValue ( keyValues [ ] * KeyValues , keyID , itemID string ) ( ret * Value ) {
for _ , kv := range keyValues {
if kv . Key . ID == keyID {
for _ , v := range kv . Values {
if v . BlockID == itemID {
ret = v
return
}
}
}
}
return
}
2025-07-25 15:20:56 +08:00
// KeyType 描述了属性视图属性字段的类型。
2023-07-12 19:10:05 +08:00
type KeyType string
const (
2025-07-25 15:20:56 +08:00
KeyTypeBlock KeyType = "block" // 主键
KeyTypeText KeyType = "text" // 文本
KeyTypeNumber KeyType = "number" // 数字
KeyTypeDate KeyType = "date" // 日期
KeyTypeSelect KeyType = "select" // 单选
KeyTypeMSelect KeyType = "mSelect" // 多选
KeyTypeURL KeyType = "url" // URL
KeyTypeEmail KeyType = "email" // Email
KeyTypePhone KeyType = "phone" // 电话
KeyTypeMAsset KeyType = "mAsset" // 资源
KeyTypeTemplate KeyType = "template" // 模板
KeyTypeCreated KeyType = "created" // 创建时间
KeyTypeUpdated KeyType = "updated" // 更新时间
KeyTypeCheckbox KeyType = "checkbox" // 复选框
KeyTypeRelation KeyType = "relation" // 关联
KeyTypeRollup KeyType = "rollup" // 汇总
KeyTypeLineNumber KeyType = "lineNumber" // 行号
2023-07-12 19:10:05 +08:00
)
2024-11-09 14:09:40 +08:00
// Key 描述了属性视图属性字段的基础结构。
2023-07-12 19:10:05 +08:00
type Key struct {
2024-11-09 14:09:40 +08:00
ID string ` json:"id" ` // 字段 ID
Name string ` json:"name" ` // 字段名
Type KeyType ` json:"type" ` // 字段类型
Icon string ` json:"icon" ` // 字段图标
Desc string ` json:"desc" ` // 字段描述
2023-07-12 19:10:05 +08:00
// 以下是某些列类型的特有属性
2024-04-04 11:33:51 +08:00
// 单选/多选
2023-12-23 17:57:45 +08:00
Options [ ] * SelectOption ` json:"options,omitempty" ` // 选项列表
2023-12-15 20:05:14 +08:00
2024-04-04 11:33:51 +08:00
// 数字
2023-12-15 20:05:14 +08:00
NumberFormat NumberFormat ` json:"numberFormat" ` // 列数字格式化
2024-04-04 11:33:51 +08:00
// 模板
2023-12-15 20:05:14 +08:00
Template string ` json:"template" ` // 模板内容
2024-04-04 11:33:51 +08:00
// 关联
2023-12-23 17:45:46 +08:00
Relation * Relation ` json:"relation,omitempty" ` // 关联信息
2023-12-15 20:05:14 +08:00
2024-04-04 11:33:51 +08:00
// 汇总
2023-12-23 17:45:46 +08:00
Rollup * Rollup ` json:"rollup,omitempty" ` // 汇总信息
2024-04-04 11:33:51 +08:00
// 日期
Date * Date ` json:"date,omitempty" ` // 日期设置
2023-07-12 19:10:05 +08:00
}
2023-10-06 10:17:52 +08:00
func NewKey ( id , name , icon string , keyType KeyType ) * Key {
2023-07-12 19:10:05 +08:00
return & Key {
2023-07-17 11:21:46 +08:00
ID : id ,
2023-07-12 19:10:05 +08:00
Name : name ,
Type : keyType ,
2023-10-06 10:17:52 +08:00
Icon : icon ,
2023-07-12 19:10:05 +08:00
}
}
2024-05-15 22:10:26 +08:00
func ( k * Key ) GetOption ( name string ) ( ret * SelectOption ) {
for _ , option := range k . Options {
if option . Name == name {
ret = option
return
}
}
return
}
2024-04-04 11:33:51 +08:00
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
}
2023-12-23 17:45:46 +08:00
type Rollup struct {
2024-11-09 14:09:40 +08:00
RelationKeyID string ` json:"relationKeyID" ` // 关联字段 ID
KeyID string ` json:"keyID" ` // 目标字段 ID
2023-12-24 21:47:10 +08:00
Calc * RollupCalc ` json:"calc" ` // 计算方式
}
type RollupCalc struct {
Operator CalcOperator ` json:"operator" `
Result * Value ` json:"result" `
2023-12-23 17:45:46 +08:00
}
type Relation struct {
AvID string ` json:"avID" ` // 关联的属性视图 ID
IsTwoWay bool ` json:"isTwoWay" ` // 是否双向关联
BackKeyID string ` json:"backKeyID" ` // 双向关联时回链关联列的 ID
}
2023-12-23 17:57:45 +08:00
type SelectOption struct {
2024-11-09 14:09:40 +08:00
Name string ` json:"name" ` // 选项名称
Color string ` json:"color" ` // 选项颜色
Desc string ` json:"desc" ` // 选项描述
2023-07-12 19:10:05 +08:00
}
2023-03-02 09:12:28 +08:00
2023-07-11 19:45:27 +08:00
// View 描述了视图的结构。
type View struct {
2025-06-29 15:53:56 +08:00
ID string ` json:"id" ` // 视图 ID
Icon string ` json:"icon" ` // 视图图标
Name string ` json:"name" ` // 视图名称
HideAttrViewName bool ` json:"hideAttrViewName" ` // 是否隐藏属性视图名称
Desc string ` json:"desc" ` // 视图描述
Filters [ ] * ViewFilter ` json:"filters,omitempty" ` // 过滤规则
Sorts [ ] * ViewSort ` json:"sorts,omitempty" ` // 排序规则
PageSize int ` json:"pageSize" ` // 每页条目数
LayoutType LayoutType ` json:"type" ` // 当前布局类型
Table * LayoutTable ` json:"table,omitempty" ` // 表格布局
2025-07-04 12:00:47 +08:00
Gallery * LayoutGallery ` json:"gallery,omitempty" ` // 卡片布局
2025-07-02 17:02:40 +08:00
ItemIDs [ ] string ` json:"itemIds,omitempty" ` // 项目 ID 列表,用于维护所有项目
2025-06-30 20:40:22 +08:00
2025-08-05 17:13:00 +08:00
Group * ViewGroup ` json:"group,omitempty" ` // 分组规则
2025-08-05 17:42:53 +08:00
GroupCreated int64 ` json:"groupCreated" ` // 分组生成时间戳
2025-08-05 17:13:00 +08:00
Groups [ ] * View ` json:"groups,omitempty" ` // 分组视图列表
GroupItemIDs [ ] string ` json:"groupItemIds" ` // 分组项目 ID 列表,用于维护分组中的所有项目
GroupCalc * GroupCalc ` json:"groupCalc,omitempty" ` // 分组计算规则
2025-08-13 11:42:41 +08:00
GroupKey * Key ` json:"groupKey,omitempty" ` // 分组字段
2025-08-05 17:13:00 +08:00
GroupVal * Value ` json:"groupVal,omitempty" ` // 分组值
GroupFolded bool ` json:"groupFolded" ` // 分组是否折叠
GroupHidden int ` json:"groupHidden" ` // 分组是否隐藏, 0: 显示, 1: 空白隐藏, 2: 手动隐藏
2025-08-11 09:44:38 +08:00
GroupSort int ` json:"groupSort" ` // 分组排序值,用于手动排序
2025-07-27 17:08:22 +08:00
}
2025-08-09 11:24:44 +08:00
// GetGroupValue 获取分组视图的分组值。
2025-08-05 17:09:11 +08:00
func ( view * View ) GetGroupValue ( ) string {
2025-08-05 17:13:00 +08:00
if nil == view . GroupVal {
2025-08-05 17:09:11 +08:00
return ""
}
2025-08-05 17:13:00 +08:00
return view . GroupVal . String ( false )
2025-08-05 17:09:11 +08:00
}
2025-08-09 11:24:44 +08:00
// GetGroupByID 获取指定分组 ID 的分组视图。
func ( view * View ) GetGroupByID ( groupID string ) * View {
2025-07-27 17:08:22 +08:00
if nil == view . Groups {
return nil
}
for _ , group := range view . Groups {
if group . ID == groupID {
return group
}
}
2025-07-27 17:08:34 +08:00
return nil
2025-07-04 16:42:37 +08:00
}
2025-08-09 11:24:44 +08:00
// GetGroupByGroupValue 获取指定分组值的分组视图。
func ( view * View ) GetGroupByGroupValue ( groupVal string ) * View {
if nil == view . Groups {
return nil
}
for _ , group := range view . Groups {
if group . GetGroupValue ( ) == groupVal {
return group
}
}
return nil
}
// RemoveGroupByID 从分组视图列表中移除指定 ID 的分组视图。
func ( view * View ) RemoveGroupByID ( groupID string ) {
if nil == view . Groups {
return
}
for i , group := range view . Groups {
if group . ID == groupID {
view . Groups = append ( view . Groups [ : i ] , view . Groups [ i + 1 : ] ... )
return
}
}
}
2025-07-27 19:34:17 +08:00
// GetGroupKey 获取分组视图的分组字段。
func ( view * View ) GetGroupKey ( attrView * AttributeView ) ( ret * Key ) {
if nil == view . Group || "" == view . Group . Field {
return
}
for _ , kv := range attrView . KeyValues {
if kv . Key . ID == view . Group . Field {
ret = kv . Key
return
}
}
return
}
2025-07-04 16:42:37 +08:00
// GroupCalc 描述了分组计算规则和结果的结构。
type GroupCalc struct {
2025-07-05 12:11:12 +08:00
Field string ` json:"field" ` // 字段 ID
FieldCalc * FieldCalc ` json:"calc" ` // 计算规则和结果
2023-07-11 19:45:27 +08:00
}
2025-06-08 11:01:21 +08:00
// LayoutType 描述了视图布局类型。
2023-07-12 10:35:17 +08:00
type LayoutType string
2023-03-02 09:16:30 +08:00
const (
2025-06-07 22:00:04 +08:00
LayoutTypeTable LayoutType = "table" // 属性视图类型 - 表格
2025-07-04 12:00:47 +08:00
LayoutTypeGallery LayoutType = "gallery" // 属性视图类型 - 卡片
2023-03-02 09:16:30 +08:00
)
2025-06-08 16:12:37 +08:00
const (
2025-06-29 11:11:20 +08:00
ViewDefaultPageSize = 50 // 视图默认分页大小
2025-06-08 16:12:37 +08:00
)
2023-12-01 09:17:44 +08:00
func NewTableView ( ) ( ret * View ) {
ret = & View {
ID : ast . NewNodeID ( ) ,
2025-06-11 16:03:37 +08:00
Name : GetAttributeViewI18n ( "table" ) ,
2025-07-03 15:52:55 +08:00
Filters : [ ] * ViewFilter { } ,
Sorts : [ ] * ViewSort { } ,
PageSize : ViewDefaultPageSize ,
2023-12-01 09:17:44 +08:00
LayoutType : LayoutTypeTable ,
2025-06-10 12:23:07 +08:00
Table : NewLayoutTable ( ) ,
2023-12-01 09:17:44 +08:00
}
return
}
2024-03-24 21:26:01 +08:00
func NewTableViewWithBlockKey ( blockKeyID string ) ( view * View , blockKey , selectKey * Key ) {
2025-06-11 16:03:37 +08:00
name := GetAttributeViewI18n ( "table" )
2023-11-30 23:19:43 +08:00
view = & View {
2023-07-12 21:39:55 +08:00
ID : ast . NewNodeID ( ) ,
Name : name ,
2025-07-03 15:52:55 +08:00
Filters : [ ] * ViewFilter { } ,
Sorts : [ ] * ViewSort { } ,
2023-07-12 21:39:55 +08:00
LayoutType : LayoutTypeTable ,
2025-06-10 12:23:07 +08:00
Table : NewLayoutTable ( ) ,
2025-07-03 15:52:55 +08:00
PageSize : ViewDefaultPageSize ,
2023-07-11 23:40:05 +08:00
}
2025-06-11 16:03:37 +08:00
blockKey = NewKey ( blockKeyID , GetAttributeViewI18n ( "key" ) , "" , KeyTypeBlock )
2025-06-30 15:06:43 +08:00
view . Table . Columns = [ ] * ViewTableColumn { { BaseField : & BaseField { ID : blockKeyID } } }
2024-03-24 21:26:01 +08:00
2025-06-11 16:03:37 +08:00
selectKey = NewKey ( ast . NewNodeID ( ) , GetAttributeViewI18n ( "select" ) , "" , KeyTypeSelect )
2025-06-30 15:06:43 +08:00
view . Table . Columns = append ( view . Table . Columns , & ViewTableColumn { BaseField : & BaseField { ID : selectKey . ID } } )
2023-11-30 23:19:43 +08:00
return
2023-07-11 23:40:05 +08:00
}
2025-06-07 22:00:04 +08:00
func NewGalleryView ( ) ( ret * View ) {
ret = & View {
ID : ast . NewNodeID ( ) ,
2025-06-11 16:03:37 +08:00
Name : GetAttributeViewI18n ( "gallery" ) ,
2025-06-29 11:11:20 +08:00
Filters : [ ] * ViewFilter { } ,
Sorts : [ ] * ViewSort { } ,
PageSize : ViewDefaultPageSize ,
2025-06-07 22:00:04 +08:00
LayoutType : LayoutTypeGallery ,
2025-06-10 12:23:07 +08:00
Gallery : NewLayoutGallery ( ) ,
2025-06-07 22:00:04 +08:00
}
return
}
2023-07-11 19:45:27 +08:00
// Viewable 描述了视图的接口。
type Viewable interface {
2025-07-01 22:42:48 +08:00
// GetType 获取视图的布局类型。
2023-07-12 10:35:17 +08:00
GetType ( ) LayoutType
2025-07-01 22:42:48 +08:00
// GetID 获取视图的 ID。
2023-07-11 21:31:19 +08:00
GetID ( ) string
2025-07-01 22:42:48 +08:00
// SetGroups 设置视图分组列表。
SetGroups ( viewables [ ] Viewable )
2025-07-05 12:11:12 +08:00
// SetGroupCalc 设置视图分组计算规则和结果。
SetGroupCalc ( group * GroupCalc )
// GetGroupCalc 获取视图分组计算规则和结果。
GetGroupCalc ( ) * GroupCalc
// SetGroupFolded 设置分组是否折叠。
SetGroupFolded ( folded bool )
2025-08-09 17:10:22 +08:00
// GetGroupHidden 获取分组是否隐藏。
// hidden 0: 显示, 1: 空白隐藏, 2: 手动隐藏
GetGroupHidden ( ) int
2025-07-05 12:11:12 +08:00
// SetGroupHidden 设置分组是否隐藏。
2025-07-25 17:57:51 +08:00
// hidden 0: 显示, 1: 空白隐藏, 2: 手动隐藏
SetGroupHidden ( hidden int )
2023-07-11 19:45:27 +08:00
}
2023-09-26 09:38:50 +08:00
func NewAttributeView ( id string ) ( ret * AttributeView ) {
2024-03-24 21:26:01 +08:00
view , blockKey , selectKey := NewTableViewWithBlockKey ( ast . NewNodeID ( ) )
2023-07-12 19:55:18 +08:00
ret = & AttributeView {
2025-07-02 17:02:40 +08:00
Spec : 3 ,
2023-07-12 21:39:55 +08:00
ID : id ,
2024-03-24 21:26:01 +08:00
KeyValues : [ ] * KeyValues { { Key : blockKey } , { Key : selectKey } } ,
2023-07-12 21:39:55 +08:00
ViewID : view . ID ,
Views : [ ] * View { view } ,
2023-03-02 09:12:28 +08:00
}
2023-07-12 19:55:18 +08:00
return
2023-03-02 09:12:28 +08:00
}
2024-03-08 20:38:32 +08:00
func GetAttributeViewName ( avID string ) ( ret string , err error ) {
avJSONPath := GetAttributeViewDataPath ( avID )
if ! filelock . IsExist ( avJSONPath ) {
return
}
2024-04-08 21:58:57 +08:00
return GetAttributeViewNameByPath ( avJSONPath )
}
func GetAttributeViewNameByPath ( avJSONPath string ) ( ret string , err error ) {
2024-03-08 20:38:32 +08:00
data , err := filelock . ReadFile ( avJSONPath )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-04-08 21:58:57 +08:00
logging . LogErrorf ( "read attribute view [%s] failed: %s" , avJSONPath , err )
2024-03-08 20:38:32 +08:00
return
}
val := jsoniter . Get ( data , "name" )
if nil == val || val . ValueType ( ) == jsoniter . InvalidValue {
return
}
ret = val . ToString ( )
return
}
2024-02-16 16:02:22 +08:00
func IsAttributeViewExist ( avID string ) bool {
avJSONPath := GetAttributeViewDataPath ( avID )
return filelock . IsExist ( avJSONPath )
}
2023-03-02 11:32:39 +08:00
func ParseAttributeView ( avID string ) ( ret * AttributeView , err error ) {
2023-07-31 11:20:58 +08:00
avJSONPath := GetAttributeViewDataPath ( avID )
2023-11-06 22:13:04 +08:00
if ! filelock . IsExist ( avJSONPath ) {
2023-07-31 11:20:58 +08:00
err = ErrViewNotFound
return
2023-03-02 11:32:39 +08:00
}
2023-07-31 11:20:58 +08:00
data , readErr := filelock . ReadFile ( avJSONPath )
if nil != readErr {
logging . LogErrorf ( "read attribute view [%s] failed: %s" , avID , readErr )
return
}
2023-03-02 09:12:28 +08:00
2023-07-31 11:20:58 +08:00
ret = & AttributeView { }
2024-09-04 04:40:50 +03:00
if err = gulu . JSON . UnmarshalJSON ( data , ret ) ; err != nil {
2024-03-08 00:35:28 +08:00
if strings . Contains ( err . Error ( ) , ".relation.contents of type av.Value" ) {
2024-03-08 15:43:33 +08:00
mapAv := map [ string ] interface { } { }
2024-09-04 04:40:50 +03:00
if err = gulu . JSON . UnmarshalJSON ( data , & mapAv ) ; err != nil {
2024-03-08 15:43:33 +08:00
logging . LogErrorf ( "unmarshal attribute view [%s] failed: %s" , avID , err )
return
}
// v3.0.3 兼容之前旧版本,将 relation.contents[""] 转换为 null
keyValues := mapAv [ "keyValues" ]
keyValuesMap := keyValues . ( [ ] interface { } )
for _ , kv := range keyValuesMap {
kvMap := kv . ( map [ string ] interface { } )
if values := kvMap [ "values" ] ; nil != values {
valuesMap := values . ( [ ] interface { } )
for _ , v := range valuesMap {
if vMap := v . ( map [ string ] interface { } ) ; nil != vMap [ "relation" ] {
vMap [ "relation" ] . ( map [ string ] interface { } ) [ "contents" ] = nil
}
}
}
}
views := mapAv [ "views" ]
viewsMap := views . ( [ ] interface { } )
for _ , view := range viewsMap {
if table := view . ( map [ string ] interface { } ) [ "table" ] ; nil != table {
tableMap := table . ( map [ string ] interface { } )
if filters := tableMap [ "filters" ] ; nil != filters {
filtersMap := filters . ( [ ] interface { } )
for _ , f := range filtersMap {
if fMap := f . ( map [ string ] interface { } ) ; nil != fMap [ "value" ] {
if valueMap := fMap [ "value" ] . ( map [ string ] interface { } ) ; nil != valueMap [ "relation" ] {
valueMap [ "relation" ] . ( map [ string ] interface { } ) [ "contents" ] = nil
}
}
}
}
}
}
data , err = gulu . JSON . MarshalJSON ( mapAv )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-03-08 15:43:33 +08:00
logging . LogErrorf ( "marshal attribute view [%s] failed: %s" , avID , err )
return
}
2024-09-04 04:40:50 +03:00
if err = gulu . JSON . UnmarshalJSON ( data , ret ) ; err != nil {
2024-03-08 00:12:10 +08:00
logging . LogErrorf ( "unmarshal attribute view [%s] failed: %s" , avID , err )
return
}
} else {
logging . LogErrorf ( "unmarshal attribute view [%s] failed: %s" , avID , err )
return
}
2023-03-02 09:12:28 +08:00
}
return
}
2023-03-02 11:32:39 +08:00
func SaveAttributeView ( av * AttributeView ) ( err error ) {
2023-12-31 17:55:40 +08:00
if "" == av . ID {
err = errors . New ( "av id is empty" )
logging . LogErrorf ( "save attribute view failed: %s" , err )
return
}
2023-10-16 23:57:09 +08:00
// 做一些数据兼容和订正处理
2024-12-19 23:08:44 +08:00
UpgradeSpec ( av )
2024-03-06 23:12:01 +08:00
2024-02-23 23:32:23 +08:00
// 值去重
blockValues := av . GetBlockKeyValues ( )
blockIDs := map [ string ] bool { }
var duplicatedValueIDs [ ] string
for _ , blockValue := range blockValues . Values {
if ! blockIDs [ blockValue . BlockID ] {
blockIDs [ blockValue . BlockID ] = true
} else {
duplicatedValueIDs = append ( duplicatedValueIDs , blockValue . ID )
}
}
var tmp [ ] * Value
for _ , blockValue := range blockValues . Values {
if ! gulu . Str . Contains ( blockValue . ID , duplicatedValueIDs ) {
tmp = append ( tmp , blockValue )
}
}
blockValues . Values = tmp
// 视图值去重
2023-10-16 23:57:09 +08:00
for _ , view := range av . Views {
2025-07-02 17:02:40 +08:00
// 项目自定义排序去重
view . ItemIDs = gulu . Str . RemoveDuplicatedElem ( view . ItemIDs )
2025-06-29 11:11:20 +08:00
// 分页大小
if 1 > view . PageSize {
2025-06-29 11:22:02 +08:00
view . PageSize = ViewDefaultPageSize
2025-06-13 12:00:49 +08:00
}
2023-10-16 23:57:09 +08:00
}
2024-03-11 21:31:29 +08:00
var data [ ] byte
if util . UseSingleLineSave {
data , err = gulu . JSON . MarshalJSON ( av )
} else {
data , err = gulu . JSON . MarshalIndentJSON ( av , "" , "\t" )
}
2024-09-04 04:40:50 +03:00
if err != nil {
2023-03-02 11:32:39 +08:00
logging . LogErrorf ( "marshal attribute view [%s] failed: %s" , av . ID , err )
2023-03-02 09:12:28 +08:00
return
}
2023-07-31 11:20:58 +08:00
avJSONPath := GetAttributeViewDataPath ( av . ID )
2024-09-04 04:40:50 +03:00
if err = filelock . WriteFile ( avJSONPath , data ) ; err != nil {
2023-03-02 11:32:39 +08:00
logging . LogErrorf ( "save attribute view [%s] failed: %s" , av . ID , err )
2023-03-02 09:12:28 +08:00
return
}
2025-08-24 10:49:13 +08:00
if util . ExceedLargeFileWarningSize ( len ( data ) ) {
msg := fmt . Sprintf ( util . Langs [ util . Lang ] [ 268 ] , av . Name + " " + filepath . Base ( avJSONPath ) , util . LargeFileWarningSize )
util . PushErrMsg ( msg , 7000 )
}
2023-03-02 09:12:28 +08:00
return
}
2023-11-30 20:04:51 +08:00
func ( av * AttributeView ) GetView ( viewID string ) ( ret * View ) {
for _ , v := range av . Views {
if v . ID == viewID {
ret = v
return
}
}
return
}
2024-03-04 15:57:35 +08:00
func ( av * AttributeView ) GetCurrentView ( viewID string ) ( ret * View , err error ) {
if "" != viewID {
ret = av . GetView ( viewID )
if nil != ret {
return
}
}
2023-07-11 22:11:15 +08:00
for _ , v := range av . Views {
2023-07-12 23:52:25 +08:00
if v . ID == av . ViewID {
2023-07-11 22:11:15 +08:00
ret = v
return
}
}
2024-03-04 15:57:35 +08:00
if 1 > len ( av . Views ) {
err = ErrViewNotFound
return
}
ret = av . Views [ 0 ]
2023-07-11 22:11:15 +08:00
return
}
2025-08-10 15:37:37 +08:00
func ( av * AttributeView ) ExistBoundBlock ( nodeID string ) bool {
for _ , blockVal := range av . GetBlockKeyValues ( ) . Values {
if blockVal . Block . ID == nodeID {
return true
2024-04-03 09:37:42 +08:00
}
}
return false
}
2025-08-10 18:20:29 +08:00
func ( av * AttributeView ) GetBlockValueByBoundID ( nodeID string ) * Value {
for _ , kv := range av . KeyValues {
if KeyTypeBlock == kv . Key . Type {
for _ , v := range kv . Values {
if v . Block . ID == nodeID {
return v
}
}
}
}
return nil
}
2025-08-10 16:02:59 +08:00
func ( av * AttributeView ) GetValue ( keyID , itemID string ) ( ret * Value ) {
2023-12-24 22:27:38 +08:00
for _ , kv := range av . KeyValues {
if kv . Key . ID == keyID {
for _ , v := range kv . Values {
2025-08-10 16:02:59 +08:00
if v . BlockID == itemID {
2023-12-24 22:27:38 +08:00
ret = v
return
}
}
}
}
return
}
2023-07-12 19:10:05 +08:00
func ( av * AttributeView ) GetKey ( keyID string ) ( ret * Key , err error ) {
for _ , kv := range av . KeyValues {
if kv . Key . ID == keyID {
ret = kv . Key
return
}
}
err = ErrKeyNotFound
return
}
func ( av * AttributeView ) GetBlockKeyValues ( ) ( ret * KeyValues ) {
for _ , kv := range av . KeyValues {
if KeyTypeBlock == kv . Key . Type {
ret = kv
return
}
}
return
}
2025-08-13 11:00:23 +08:00
func ( av * AttributeView ) GetBlockValue ( itemID string ) ( ret * Value ) {
for _ , kv := range av . KeyValues {
if KeyTypeBlock == kv . Key . Type && 0 < len ( kv . Values ) {
for _ , v := range kv . Values {
if v . BlockID == itemID {
ret = v
return
}
}
}
}
return
}
2023-12-17 12:00:55 +08:00
func ( av * AttributeView ) GetKeyValues ( keyID string ) ( ret * KeyValues , err error ) {
for _ , kv := range av . KeyValues {
if kv . Key . ID == keyID {
ret = kv
return
}
}
err = ErrKeyNotFound
return
}
2023-12-01 08:55:32 +08:00
func ( av * AttributeView ) GetBlockKey ( ) ( ret * Key ) {
for _ , kv := range av . KeyValues {
if KeyTypeBlock == kv . Key . Type {
ret = kv . Key
return
}
}
return
}
2025-06-10 11:36:30 +08:00
func ( av * AttributeView ) Clone ( ) ( ret * AttributeView ) {
2023-12-15 12:02:27 +08:00
ret = & AttributeView { }
data , err := gulu . JSON . MarshalJSON ( av )
2024-09-04 04:40:50 +03:00
if err != nil {
2023-12-15 12:02:27 +08:00
logging . LogErrorf ( "marshal attribute view [%s] failed: %s" , av . ID , err )
return nil
}
2024-09-04 04:40:50 +03:00
if err = gulu . JSON . UnmarshalJSON ( data , ret ) ; err != nil {
2023-12-15 12:02:27 +08:00
logging . LogErrorf ( "unmarshal attribute view [%s] failed: %s" , av . ID , err )
return nil
}
ret . ID = ast . NewNodeID ( )
2024-01-08 12:04:03 +08:00
if 1 > len ( ret . Views ) {
logging . LogErrorf ( "attribute view [%s] has no views" , av . ID )
return nil
2023-12-15 12:02:27 +08:00
}
2024-07-04 16:59:58 +08:00
var oldKeyIDs [ ] string
2023-12-15 12:02:27 +08:00
keyIDMap := map [ string ] string { }
for _ , kv := range ret . KeyValues {
newID := ast . NewNodeID ( )
keyIDMap [ kv . Key . ID ] = newID
2024-07-04 16:59:58 +08:00
oldKeyIDs = append ( oldKeyIDs , kv . Key . ID )
2023-12-15 12:02:27 +08:00
kv . Key . ID = newID
kv . Values = [ ] * Value { }
}
2024-07-04 16:59:58 +08:00
oldKeyIDs = gulu . Str . RemoveDuplicatedElem ( oldKeyIDs )
sorts := map [ string ] int { }
for i , k := range ret . KeyIDs {
sorts [ k ] = i
}
sort . Slice ( oldKeyIDs , func ( i , j int ) bool {
return sorts [ oldKeyIDs [ i ] ] < sorts [ oldKeyIDs [ j ] ]
} )
2024-01-08 12:04:03 +08:00
for _ , view := range ret . Views {
view . ID = ast . NewNodeID ( )
2025-06-29 11:11:20 +08:00
for _ , f := range view . Filters {
f . Column = keyIDMap [ f . Column ]
}
for _ , s := range view . Sorts {
s . Column = keyIDMap [ s . Column ]
}
2025-06-29 15:53:56 +08:00
if nil != view . Group {
view . Group . Field = keyIDMap [ view . Group . Field ]
}
2025-06-13 12:00:49 +08:00
switch view . LayoutType {
case LayoutTypeTable :
2025-06-30 20:40:22 +08:00
view . Table . ID = ast . NewNodeID ( )
2025-06-13 12:00:49 +08:00
for _ , column := range view . Table . Columns {
column . ID = keyIDMap [ column . ID ]
}
case LayoutTypeGallery :
2025-06-30 20:40:22 +08:00
view . Gallery . ID = ast . NewNodeID ( )
2025-06-13 12:00:49 +08:00
for _ , cardField := range view . Gallery . CardFields {
cardField . ID = keyIDMap [ cardField . ID ]
}
2024-01-08 12:04:03 +08:00
}
2025-07-02 17:02:40 +08:00
view . ItemIDs = [ ] string { }
2023-12-15 12:02:27 +08:00
}
2024-01-08 12:04:03 +08:00
ret . ViewID = ret . Views [ 0 ] . ID
2024-07-04 16:59:58 +08:00
ret . KeyIDs = nil
for _ , oldKeyID := range oldKeyIDs {
newKeyID := keyIDMap [ oldKeyID ]
ret . KeyIDs = append ( ret . KeyIDs , newKeyID )
}
2023-12-15 12:02:27 +08:00
return
}
2023-07-31 11:20:58 +08:00
func GetAttributeViewDataPath ( avID string ) ( ret string ) {
2023-03-02 18:49:05 +08:00
av := filepath . Join ( util . DataDir , "storage" , "av" )
ret = filepath . Join ( av , avID + ".json" )
if ! gulu . File . IsDir ( av ) {
2024-09-04 04:40:50 +03:00
if err := os . MkdirAll ( av , 0755 ) ; err != nil {
2023-03-02 18:49:05 +08:00
logging . LogErrorf ( "create attribute view dir failed: %s" , err )
return
}
}
return
2023-03-02 09:12:28 +08:00
}
2025-06-11 16:03:37 +08:00
func GetAttributeViewI18n ( key string ) string {
return util . AttrViewLangs [ util . Lang ] [ key ] . ( string )
2023-12-14 11:51:31 +08:00
}
2023-07-11 22:11:15 +08:00
var (
2025-06-08 11:01:21 +08:00
ErrViewNotFound = errors . New ( "view not found" )
ErrKeyNotFound = errors . New ( "key not found" )
ErrWrongLayoutType = errors . New ( "wrong layout type" )
2023-07-11 22:11:15 +08:00
)
2023-10-05 12:02:17 +08:00
const (
2024-12-21 12:00:02 +08:00
NodeAttrNameAvs = "custom-avs" // 用于标记块所属的属性视图,逗号分隔 av id
NodeAttrView = "custom-sy-av-view" // 用于标记块所属的属性视图视图 view id Database block support specified view https://github.com/siyuan-note/siyuan/issues/10443
NodeAttrViewStaticText = "custom-sy-av-s-text" // 用于标记块所属的属性视图静态文本 Database-bound block primary key supports setting static anchor text https://github.com/siyuan-note/siyuan/issues/10049
2024-03-10 23:00:45 +08:00
NodeAttrViewNames = "av-names" // 用于临时标记块所属的属性视图名称,空格分隔
2023-10-05 12:02:17 +08:00
)