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"
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
}
// KeyValues 描述了属性视图属性列值的结构。
type KeyValues struct {
2023-07-12 23:52:25 +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 {
if KeyTypeBlock != v . Type {
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
}
2023-07-12 19:10:05 +08:00
type KeyType string
const (
2024-04-15 00:49:48 +08:00
KeyTypeBlock KeyType = "block"
KeyTypeText KeyType = "text"
KeyTypeNumber KeyType = "number"
KeyTypeDate KeyType = "date"
KeyTypeSelect KeyType = "select"
KeyTypeMSelect KeyType = "mSelect"
KeyTypeURL KeyType = "url"
KeyTypeEmail KeyType = "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 {
2024-03-01 20:38:53 +08:00
ID string ` json:"id" ` // 视图 ID
Icon string ` json:"icon" ` // 视图图标
Name string ` json:"name" ` // 视图名称
HideAttrViewName bool ` json:"hideAttrViewName" ` // 是否隐藏属性视图名称
2024-11-09 14:09:40 +08:00
Desc string ` json:"desc" ` // 视图描述
2023-07-11 19:45:27 +08:00
2023-07-12 21:39:55 +08:00
LayoutType LayoutType ` json:"type" ` // 当前布局类型
Table * LayoutTable ` json:"table,omitempty" ` // 表格布局
2023-07-11 19:45:27 +08:00
}
2023-07-12 10:35:17 +08:00
// LayoutType 描述了视图布局的类型。
type LayoutType string
2023-03-02 09:16:30 +08:00
const (
2023-07-12 10:35:17 +08:00
LayoutTypeTable LayoutType = "table" // 属性视图类型 - 表格
2023-03-02 09:16:30 +08:00
)
2023-12-01 09:17:44 +08:00
func NewTableView ( ) ( ret * View ) {
ret = & View {
ID : ast . NewNodeID ( ) ,
2023-12-14 11:51:31 +08:00
Name : getI18nName ( "table" ) ,
2023-12-01 09:17:44 +08:00
LayoutType : LayoutTypeTable ,
Table : & LayoutTable {
2023-12-08 22:00:23 +08:00
Spec : 0 ,
ID : ast . NewNodeID ( ) ,
Filters : [ ] * ViewFilter { } ,
Sorts : [ ] * ViewSort { } ,
PageSize : 50 ,
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 ) {
2023-12-14 11:51:31 +08:00
name := getI18nName ( "table" )
2023-11-30 23:19:43 +08:00
view = & View {
2023-07-12 21:39:55 +08:00
ID : ast . NewNodeID ( ) ,
Name : name ,
LayoutType : LayoutTypeTable ,
2023-07-12 10:35:17 +08:00
Table : & LayoutTable {
2023-12-08 22:00:23 +08:00
Spec : 0 ,
ID : ast . NewNodeID ( ) ,
Filters : [ ] * ViewFilter { } ,
Sorts : [ ] * ViewSort { } ,
PageSize : 50 ,
2023-07-11 23:40:05 +08:00
} ,
}
2023-12-14 11:51:31 +08:00
blockKey = NewKey ( blockKeyID , getI18nName ( "key" ) , "" , KeyTypeBlock )
2023-12-01 08:55:32 +08:00
view . Table . Columns = [ ] * ViewTableColumn { { ID : blockKeyID } }
2024-03-24 21:26:01 +08:00
selectKey = NewKey ( ast . NewNodeID ( ) , getI18nName ( "select" ) , "" , KeyTypeSelect )
view . Table . Columns = append ( view . Table . Columns , & ViewTableColumn { ID : selectKey . ID } )
2023-11-30 23:19:43 +08:00
return
2023-07-11 23:40:05 +08:00
}
2023-07-11 19:45:27 +08:00
// Viewable 描述了视图的接口。
type Viewable interface {
Filterable
Sortable
Calculable
2023-07-12 10:35:17 +08:00
GetType ( ) LayoutType
2023-07-11 21:31:19 +08:00
GetID ( ) string
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 {
2023-07-12 21:39:55 +08:00
Spec : 0 ,
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 {
if nil != view . Table {
2023-12-08 22:00:23 +08:00
// 行去重
2023-10-16 23:57:09 +08:00
view . Table . RowIDs = gulu . Str . RemoveDuplicatedElem ( view . Table . RowIDs )
2023-12-08 22:00:23 +08:00
// 分页大小
if 1 > view . Table . PageSize {
view . Table . PageSize = 50
}
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
}
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
}
2024-04-03 09:37:42 +08:00
func ( av * AttributeView ) ExistBlock ( blockID string ) bool {
for _ , kv := range av . KeyValues {
if KeyTypeBlock != kv . Key . Type {
continue
}
for _ , v := range kv . Values {
if v . BlockID == blockID {
return true
}
}
return false
}
return false
}
2023-12-24 22:27:38 +08:00
func ( av * AttributeView ) GetValue ( keyID , blockID string ) ( ret * Value ) {
for _ , kv := range av . KeyValues {
if kv . Key . ID == keyID {
for _ , v := range kv . Values {
if v . BlockID == blockID {
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
}
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
}
2023-12-15 12:02:27 +08:00
func ( av * AttributeView ) ShallowClone ( ) ( ret * AttributeView ) {
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 ( )
view . Table . ID = ast . NewNodeID ( )
for _ , column := range view . Table . Columns {
column . ID = keyIDMap [ column . ID ]
}
view . Table . RowIDs = [ ] string { }
for _ , f := range view . Table . Filters {
f . Column = keyIDMap [ f . Column ]
}
for _ , s := range view . Table . Sorts {
s . Column = keyIDMap [ s . Column ]
}
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
}
2023-12-14 11:51:31 +08:00
func getI18nName ( name string ) string {
return util . AttrViewLangs [ util . Lang ] [ name ] . ( string )
}
2023-07-11 22:11:15 +08:00
var (
ErrViewNotFound = errors . New ( "view not found" )
2023-07-12 19:10:05 +08:00
ErrKeyNotFound = errors . New ( "key not found" )
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
)