diff --git a/kernel/api/av.go b/kernel/api/av.go index 63ea22afd..a1a6f94a2 100644 --- a/kernel/api/av.go +++ b/kernel/api/av.go @@ -47,7 +47,7 @@ func renderAttributeView(c *gin.Context) { view := map[string]interface{}{ "id": v.ID, "name": v.Name, - "type": v.Type, + "type": v.CurrentLayoutType, } views = append(views, view) diff --git a/kernel/av/attribute_view.go b/kernel/av/av.go similarity index 58% rename from kernel/av/attribute_view.go rename to kernel/av/av.go index ef9157014..d7abf877b 100644 --- a/kernel/av/attribute_view.go +++ b/kernel/av/av.go @@ -31,14 +31,98 @@ import ( // AttributeView 描述了属性视图的结构。 type AttributeView struct { - Spec int `json:"spec"` // 格式版本 - ID string `json:"id"` // 属性视图 ID - Name string `json:"name"` // 属性视图名称 - Columns []*Column `json:"columns"` // 列 - Rows []*Row `json:"rows"` // 行 + Spec int `json:"spec"` // 格式版本 + ID string `json:"id"` // 属性视图 ID + Name string `json:"name"` // 属性视图名称 + KeyValues []*KeyValues `json:"keyValues"` // 属性视图属性列值 + CurrentViewID string `json:"currentViewID"` // 当前视图 ID + Views []*View `json:"views"` // 视图 +} - CurrentViewID string `json:"currentViewID"` // 当前视图 ID - Views []*View `json:"views"` // 视图 +// KeyValues 描述了属性视图属性列值的结构。 +type KeyValues struct { + Key *Key `json:"key"` // 属性视图属性列 + Values []*Value `json:"values"` // 属性视图属性列值 +} + +type KeyType string + +const ( + KeyTypeBlock KeyType = "block" + KeyTypeText KeyType = "text" + KeyTypeNumber KeyType = "number" + KeyTypeDate KeyType = "date" + KeyTypeSelect KeyType = "select" + KeyTypeMSelect KeyType = "mSelect" +) + +// Key 描述了属性视图属性列的基础结构。 +type Key struct { + ID string `json:"id"` // 列 ID + Name string `json:"name"` // 列名 + Type KeyType `json:"type"` // 列类型 + Icon string `json:"icon"` // 列图标 + + // 以下是某些列类型的特有属性 + + Options []*KeySelectOption `json:"options"` // 选项列表 +} + +func NewKey(name string, keyType KeyType) *Key { + return &Key{ + ID: ast.NewNodeID(), + Name: name, + Type: keyType, + } +} + +type KeySelectOption struct { + Name string `json:"name"` + Color string `json:"color"` +} + +type Value struct { + ID string `json:"id"` + KeyID string `json:"keyID"` + BlockID string `json:"blockID"` + + Block *ValueBlock `json:"block,omitempty"` + Text *ValueText `json:"text,omitempty"` + Number *ValueNumber `json:"number,omitempty"` + Date *ValueDate `json:"date,omitempty"` + MSelect []*ValueSelect `json:"mSelect,omitempty"` +} + +func (value *Value) ToJSONString() string { + data, err := gulu.JSON.MarshalJSON(value) + if nil != err { + return "" + } + return string(data) +} + +type ValueBlock struct { + ID string `json:"id"` + Content string `json:"content"` +} + +type ValueText struct { + Content string `json:"content"` +} + +type ValueNumber struct { + Content float64 `json:"content"` + IsNotEmpty bool `json:"isNotEmpty"` +} + +type ValueDate struct { + Content int64 `json:"content"` + Content2 int64 `json:"content2"` +} + +type ValueSelect struct { + Content string `json:"content"` + Color string `json:"color"` } // View 描述了视图的结构。 @@ -91,8 +175,7 @@ func NewAttributeView(id string) *AttributeView { return &AttributeView{ Spec: 0, ID: id, - Columns: []*Column{{ID: ast.NewNodeID(), Name: "Block", Type: ColumnTypeBlock}}, - Rows: []*Row{}, + KeyValues: []*KeyValues{{Key: NewKey("Name", KeyTypeBlock)}}, CurrentViewID: view.ID, Views: []*View{view}, } @@ -125,34 +208,6 @@ func ParseAttributeView(avID string) (ret *AttributeView, err error) { return } -func ParseAttributeViewMap(avID string) (ret map[string]interface{}, err error) { - ret = map[string]interface{}{} - avJSONPath := getAttributeViewDataPath(avID) - if !gulu.File.IsExist(avJSONPath) { - av := NewAttributeView(avID) - var data []byte - data, err = gulu.JSON.MarshalJSON(av) - if nil == err { - return - } - - err = gulu.JSON.UnmarshalJSON(data, &ret) - return - } - - data, err := filelock.ReadFile(avJSONPath) - if nil != err { - logging.LogErrorf("read attribute view [%s] failed: %s", avID, err) - return - } - - if err = gulu.JSON.UnmarshalJSON(data, &ret); nil != err { - logging.LogErrorf("unmarshal attribute view [%s] failed: %s", avID, err) - return - } - return -} - func SaveAttributeView(av *AttributeView) (err error) { data, err := gulu.JSON.MarshalIndentJSON(av, "", "\t") if nil != err { @@ -179,6 +234,27 @@ func (av *AttributeView) GetView(viewID string) (ret *View, err error) { return } +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 +} + func getAttributeViewDataPath(avID string) (ret string) { av := filepath.Join(util.DataDir, "storage", "av") ret = filepath.Join(av, avID+".json") @@ -193,4 +269,5 @@ func getAttributeViewDataPath(avID string) (ret string) { var ( ErrViewNotFound = errors.New("view not found") + ErrKeyNotFound = errors.New("key not found") ) diff --git a/kernel/av/cell.go b/kernel/av/cell.go deleted file mode 100644 index 597f74b03..000000000 --- a/kernel/av/cell.go +++ /dev/null @@ -1,237 +0,0 @@ -// 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 av - -import ( - "github.com/88250/gulu" - "github.com/88250/lute/ast" - "strings" -) - -type Cell struct { - ID string `json:"id"` - Value *Value `json:"value"` - ValueType ColumnType `json:"valueType"` - Color string `json:"color"` - BgColor string `json:"bgColor"` -} - -type Value struct { - Block *ValueBlock `json:"block,omitempty"` - Text *ValueText `json:"text,omitempty"` - Number *ValueNumber `json:"number,omitempty"` - Date *ValueDate `json:"date,omitempty"` - MSelect []*ValueSelect `json:"mSelect,omitempty"` -} - -func (value *Value) ToJSONString() string { - data, err := gulu.JSON.MarshalJSON(value) - if nil != err { - return "" - } - return string(data) -} - -func (value *Value) Compare(other *Value) int { - if nil == value { - return -1 - } - if nil == other { - return 1 - } - if nil != value.Block && nil != other.Block { - return strings.Compare(value.Block.Content, other.Block.Content) - } - if nil != value.Text && nil != other.Text { - return strings.Compare(value.Text.Content, other.Text.Content) - } - if nil != value.Number && nil != other.Number { - if value.Number.Content > other.Number.Content { - return 1 - } else if value.Number.Content < other.Number.Content { - return -1 - } else { - return 0 - } - } - if nil != value.Date && nil != other.Date { - if value.Date.Content > other.Date.Content { - return 1 - } else if value.Date.Content < other.Date.Content { - return -1 - } else { - return 0 - } - } - - if nil != value.MSelect && nil != other.MSelect { - var v1 string - for _, v := range value.MSelect { - v1 += v.Content - } - var v2 string - for _, v := range other.MSelect { - v2 += v.Content - } - return strings.Compare(v1, v2) - } - return 0 -} - -func (value *Value) CompareOperator(other *Value, operator FilterOperator) bool { - if nil == value || nil == other { - return false - } - - if nil != value.Block && nil != other.Block { - return strings.Contains(value.Block.Content, other.Block.Content) - } - - if nil != value.Text && nil != other.Text { - switch operator { - case FilterOperatorIsEqual: - return value.Text.Content == other.Text.Content - case FilterOperatorIsNotEqual: - return value.Text.Content != other.Text.Content - case FilterOperatorContains: - return strings.Contains(value.Text.Content, other.Text.Content) - case FilterOperatorDoesNotContain: - return !strings.Contains(value.Text.Content, other.Text.Content) - case FilterOperatorStartsWith: - return strings.HasPrefix(value.Text.Content, other.Text.Content) - case FilterOperatorEndsWith: - return strings.HasSuffix(value.Text.Content, other.Text.Content) - case FilterOperatorIsEmpty: - return "" == strings.TrimSpace(value.Text.Content) - case FilterOperatorIsNotEmpty: - return "" != strings.TrimSpace(value.Text.Content) - } - } - - if nil != value.Number && nil != other.Number { - switch operator { - case FilterOperatorIsEqual: - return value.Number.Content == other.Number.Content - case FilterOperatorIsNotEqual: - return value.Number.Content != other.Number.Content - case FilterOperatorIsGreater: - return value.Number.Content > other.Number.Content - case FilterOperatorIsGreaterOrEqual: - return value.Number.Content >= other.Number.Content - case FilterOperatorIsLess: - return value.Number.Content < other.Number.Content - case FilterOperatorIsLessOrEqual: - return value.Number.Content <= other.Number.Content - case FilterOperatorIsEmpty: - return !value.Number.IsNotEmpty - case FilterOperatorIsNotEmpty: - return value.Number.IsNotEmpty - } - } - - if nil != value.Date && nil != other.Date { - switch operator { - case FilterOperatorIsEqual: - return value.Date.Content == other.Date.Content - case FilterOperatorIsNotEqual: - return value.Date.Content != other.Date.Content - case FilterOperatorIsGreater: - return value.Date.Content > other.Date.Content - case FilterOperatorIsGreaterOrEqual: - return value.Date.Content >= other.Date.Content - case FilterOperatorIsLess: - return value.Date.Content < other.Date.Content - case FilterOperatorIsLessOrEqual: - return value.Date.Content <= other.Date.Content - case FilterOperatorIsBetween: - return value.Date.Content >= other.Date.Content && value.Date.Content <= other.Date.Content2 - case FilterOperatorIsEmpty: - return 0 == value.Date.Content - case FilterOperatorIsNotEmpty: - return 0 != value.Date.Content - case FilterOperatorIsRelativeToToday: - // TODO: date filter (relative to today) - return value.Date.Content >= other.Date.Content && value.Date.Content <= other.Date.Content2 - } - } - - if nil != value.MSelect && nil != other.MSelect && 0 < len(value.MSelect) && 0 < len(other.MSelect) { - switch operator { - case FilterOperatorIsEqual: - return value.MSelect[0].Content == other.MSelect[0].Content - case FilterOperatorIsNotEqual: - return value.MSelect[0].Content != other.MSelect[0].Content - case FilterOperatorContains: - for _, v := range value.MSelect { - if v.Content == other.MSelect[0].Content { - return true - } - } - case FilterOperatorDoesNotContain: - for _, v := range value.MSelect { - if v.Content == other.MSelect[0].Content { - return false - } - } - return true - case FilterOperatorIsEmpty: - return 0 == len(value.MSelect) - case FilterOperatorIsNotEmpty: - return 0 != len(value.MSelect) - } - } - return false -} - -func NewCellBlock(blockID, blockContent string) *Cell { - return &Cell{ - ID: ast.NewNodeID(), - Value: &Value{Block: &ValueBlock{ID: blockID, Content: blockContent}}, - ValueType: ColumnTypeBlock, - } -} - -func NewCell(valueType ColumnType) *Cell { - return &Cell{ - ID: ast.NewNodeID(), - ValueType: valueType, - } -} - -type ValueBlock struct { - ID string `json:"id"` - Content string `json:"content"` -} - -type ValueText struct { - Content string `json:"content"` -} - -type ValueNumber struct { - Content float64 `json:"content"` - IsNotEmpty bool `json:"isNotEmpty"` -} - -type ValueDate struct { - Content int64 `json:"content"` - Content2 int64 `json:"content2"` -} - -type ValueSelect struct { - Content string `json:"content"` - Color string `json:"color"` -} diff --git a/kernel/av/column.go b/kernel/av/column.go deleted file mode 100644 index fbe40a0b4..000000000 --- a/kernel/av/column.go +++ /dev/null @@ -1,57 +0,0 @@ -// 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 av - -import ( - "github.com/88250/lute/ast" -) - -type ColumnType string - -const ( - ColumnTypeBlock ColumnType = "block" - ColumnTypeText ColumnType = "text" - ColumnTypeNumber ColumnType = "number" - ColumnTypeDate ColumnType = "date" - ColumnTypeSelect ColumnType = "select" - ColumnTypeMSelect ColumnType = "mSelect" -) - -// Column 描述了属性视图的基础结构。 -type Column struct { - ID string `json:"id"` // 列 ID - Name string `json:"name"` // 列名 - Type ColumnType `json:"type"` // 列类型 - Icon string `json:"icon"` // 列图标 - - // 以下是某些列类型的特有属性 - - Options []*ColumnSelectOption `json:"options"` // 选项列表 -} - -type ColumnSelectOption struct { - Name string `json:"name"` - Color string `json:"color"` -} - -func NewColumn(name string, columnType ColumnType) *Column { - return &Column{ - ID: ast.NewNodeID(), - Name: name, - Type: columnType, - } -} diff --git a/kernel/av/column_calc.go b/kernel/av/column_calc.go deleted file mode 100644 index e2df83e57..000000000 --- a/kernel/av/column_calc.go +++ /dev/null @@ -1,48 +0,0 @@ -// 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 av - -type Calculable interface { - CalcCols() -} - -type ColumnCalc struct { - Column string `json:"column"` - Operator CalcOperator `json:"operator"` - Result *Value `json:"result"` -} - -type CalcOperator string - -const ( - CalcOperatorNone CalcOperator = "" - CalcOperatorCountAll CalcOperator = "Count all" - CalcOperatorCountValues CalcOperator = "Count values" - CalcOperatorCountUniqueValues CalcOperator = "Count unique values" - CalcOperatorCountEmpty CalcOperator = "Count empty" - CalcOperatorCountNotEmpty CalcOperator = "Count not empty" - CalcOperatorPercentEmpty CalcOperator = "Percent empty" - CalcOperatorPercentNotEmpty CalcOperator = "Percent not empty" - CalcOperatorSum CalcOperator = "Sum" - CalcOperatorAverage CalcOperator = "Average" - CalcOperatorMedian CalcOperator = "Median" - CalcOperatorMin CalcOperator = "Min" - CalcOperatorMax CalcOperator = "Max" - CalcOperatorRange CalcOperator = "Range" - CalcOperatorEarliest CalcOperator = "Earliest" - CalcOperatorLatest CalcOperator = "Latest" -) diff --git a/kernel/av/row.go b/kernel/av/row.go deleted file mode 100644 index 383a9b34d..000000000 --- a/kernel/av/row.go +++ /dev/null @@ -1,37 +0,0 @@ -// 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 av - -import "github.com/88250/lute/ast" - -type Row struct { - ID string `json:"id"` - Cells []*Cell `json:"cells"` -} - -func NewRow() *Row { - return &Row{ID: ast.NewNodeID()} -} - -func (row *Row) GetBlockCell() *Cell { - for _, cell := range row.Cells { - if ColumnTypeBlock == cell.ValueType { - return cell - } - } - return nil -} diff --git a/kernel/av/table.go b/kernel/av/table.go index 48101421d..e97e927f2 100644 --- a/kernel/av/table.go +++ b/kernel/av/table.go @@ -17,8 +17,10 @@ package av import ( + "github.com/88250/lute/ast" "math" "sort" + "strings" ) // LayoutTable 描述了表格布局的结构。 @@ -26,18 +28,14 @@ type LayoutTable struct { Spec int `json:"spec"` // 布局格式版本 ID string `json:"id"` // 布局 ID - Columns []*TableColumn `json:"columns"` // 表格列 - ColIDs []string `json:"colIds"` // 列 ID,用于自定义排序 - RowIDs []string `json:"rowIds"` // 行 ID,用于自定义排序 - Filters []*ViewFilter `json:"filters"` // 过滤规则 - Sorts []*ViewSort `json:"sorts"` // 排序规则 + Columns []*ViewTableColumn `json:"columns"` // 表格列 + RowIDs []string `json:"rowIds"` // 行 ID,用于自定义排序 + Filters []*ViewFilter `json:"filters"` // 过滤规则 + Sorts []*ViewSort `json:"sorts"` // 排序规则 } -type TableColumn struct { - ID string `json:"id"` // 列 ID - Name string `json:"name"` // 列名 - Type ColumnType `json:"type"` // 列类型 - Icon string `json:"icon"` // 列图标 +type ViewTableColumn struct { + ID string `json:"id"` // 列 ID Wrap bool `json:"wrap"` // 是否换行 Hidden bool `json:"hidden"` // 是否隐藏 @@ -45,9 +43,194 @@ type TableColumn struct { Calc *ColumnCalc `json:"calc"` // 计算 } -type TableRow struct { - ID string `json:"id"` - Cells []*Cell `json:"cells"` +type Calculable interface { + CalcCols() +} + +type ColumnCalc struct { + Column string `json:"column"` + Operator CalcOperator `json:"operator"` + Result *Value `json:"result"` +} + +type CalcOperator string + +const ( + CalcOperatorNone CalcOperator = "" + CalcOperatorCountAll CalcOperator = "Count all" + CalcOperatorCountValues CalcOperator = "Count values" + CalcOperatorCountUniqueValues CalcOperator = "Count unique values" + CalcOperatorCountEmpty CalcOperator = "Count empty" + CalcOperatorCountNotEmpty CalcOperator = "Count not empty" + CalcOperatorPercentEmpty CalcOperator = "Percent empty" + CalcOperatorPercentNotEmpty CalcOperator = "Percent not empty" + CalcOperatorSum CalcOperator = "Sum" + CalcOperatorAverage CalcOperator = "Average" + CalcOperatorMedian CalcOperator = "Median" + CalcOperatorMin CalcOperator = "Min" + CalcOperatorMax CalcOperator = "Max" + CalcOperatorRange CalcOperator = "Range" + CalcOperatorEarliest CalcOperator = "Earliest" + CalcOperatorLatest CalcOperator = "Latest" +) + +type TableCell struct { + ID string `json:"id"` + Value *Value `json:"value"` + ValueType KeyType `json:"valueType"` + Color string `json:"color"` + BgColor string `json:"bgColor"` +} + +func (value *Value) Compare(other *Value) int { + if nil == value { + return -1 + } + if nil == other { + return 1 + } + if nil != value.Block && nil != other.Block { + return strings.Compare(value.Block.Content, other.Block.Content) + } + if nil != value.Text && nil != other.Text { + return strings.Compare(value.Text.Content, other.Text.Content) + } + if nil != value.Number && nil != other.Number { + if value.Number.Content > other.Number.Content { + return 1 + } else if value.Number.Content < other.Number.Content { + return -1 + } else { + return 0 + } + } + if nil != value.Date && nil != other.Date { + if value.Date.Content > other.Date.Content { + return 1 + } else if value.Date.Content < other.Date.Content { + return -1 + } else { + return 0 + } + } + + if nil != value.MSelect && nil != other.MSelect { + var v1 string + for _, v := range value.MSelect { + v1 += v.Content + } + var v2 string + for _, v := range other.MSelect { + v2 += v.Content + } + return strings.Compare(v1, v2) + } + return 0 +} + +func (value *Value) CompareOperator(other *Value, operator FilterOperator) bool { + if nil == value || nil == other { + return false + } + + if nil != value.Block && nil != other.Block { + return strings.Contains(value.Block.Content, other.Block.Content) + } + + if nil != value.Text && nil != other.Text { + switch operator { + case FilterOperatorIsEqual: + return value.Text.Content == other.Text.Content + case FilterOperatorIsNotEqual: + return value.Text.Content != other.Text.Content + case FilterOperatorContains: + return strings.Contains(value.Text.Content, other.Text.Content) + case FilterOperatorDoesNotContain: + return !strings.Contains(value.Text.Content, other.Text.Content) + case FilterOperatorStartsWith: + return strings.HasPrefix(value.Text.Content, other.Text.Content) + case FilterOperatorEndsWith: + return strings.HasSuffix(value.Text.Content, other.Text.Content) + case FilterOperatorIsEmpty: + return "" == strings.TrimSpace(value.Text.Content) + case FilterOperatorIsNotEmpty: + return "" != strings.TrimSpace(value.Text.Content) + } + } + + if nil != value.Number && nil != other.Number { + switch operator { + case FilterOperatorIsEqual: + return value.Number.Content == other.Number.Content + case FilterOperatorIsNotEqual: + return value.Number.Content != other.Number.Content + case FilterOperatorIsGreater: + return value.Number.Content > other.Number.Content + case FilterOperatorIsGreaterOrEqual: + return value.Number.Content >= other.Number.Content + case FilterOperatorIsLess: + return value.Number.Content < other.Number.Content + case FilterOperatorIsLessOrEqual: + return value.Number.Content <= other.Number.Content + case FilterOperatorIsEmpty: + return !value.Number.IsNotEmpty + case FilterOperatorIsNotEmpty: + return value.Number.IsNotEmpty + } + } + + if nil != value.Date && nil != other.Date { + switch operator { + case FilterOperatorIsEqual: + return value.Date.Content == other.Date.Content + case FilterOperatorIsNotEqual: + return value.Date.Content != other.Date.Content + case FilterOperatorIsGreater: + return value.Date.Content > other.Date.Content + case FilterOperatorIsGreaterOrEqual: + return value.Date.Content >= other.Date.Content + case FilterOperatorIsLess: + return value.Date.Content < other.Date.Content + case FilterOperatorIsLessOrEqual: + return value.Date.Content <= other.Date.Content + case FilterOperatorIsBetween: + return value.Date.Content >= other.Date.Content && value.Date.Content <= other.Date.Content2 + case FilterOperatorIsEmpty: + return 0 == value.Date.Content + case FilterOperatorIsNotEmpty: + return 0 != value.Date.Content + case FilterOperatorIsRelativeToToday: + // TODO: date filter (relative to today) + return value.Date.Content >= other.Date.Content && value.Date.Content <= other.Date.Content2 + } + } + + if nil != value.MSelect && nil != other.MSelect && 0 < len(value.MSelect) && 0 < len(other.MSelect) { + switch operator { + case FilterOperatorIsEqual: + return value.MSelect[0].Content == other.MSelect[0].Content + case FilterOperatorIsNotEqual: + return value.MSelect[0].Content != other.MSelect[0].Content + case FilterOperatorContains: + for _, v := range value.MSelect { + if v.Content == other.MSelect[0].Content { + return true + } + } + case FilterOperatorDoesNotContain: + for _, v := range value.MSelect { + if v.Content == other.MSelect[0].Content { + return false + } + } + return true + case FilterOperatorIsEmpty: + return 0 == len(value.MSelect) + case FilterOperatorIsNotEmpty: + return 0 != len(value.MSelect) + } + } + return false } // Table 描述了表格实例的结构。 @@ -60,6 +243,29 @@ type Table struct { Rows []*TableRow `json:"rows"` // 表格行 } +type TableColumn struct { + ID string `json:"id"` // 列 ID + Name string `json:"name"` // 列名 + Type KeyType `json:"type"` // 列类型 + Icon string `json:"icon"` // 列图标 + Wrap bool `json:"wrap"` // 是否换行 + Hidden bool `json:"hidden"` // 是否隐藏 + Width string `json:"width"` // 列宽度 + Calc *ColumnCalc `json:"calc"` // 计算 +} + +type TableRow struct { + ID string `json:"id"` + Cells []*TableCell `json:"cells"` +} + +func NewTableRow() *TableRow { + return &TableRow{ + ID: ast.NewNodeID(), + Cells: []*TableCell{}, + } +} + func (table *Table) GetType() LayoutType { return LayoutTypeTable } @@ -91,7 +297,7 @@ func (table *Table) SortRows() { sort.Slice(table.Rows, func(i, j int) bool { for _, colIndexSort := range colIndexSorts { c := table.Columns[colIndexSort.Index] - if c.Type == ColumnTypeBlock { + if c.Type == KeyTypeBlock { continue } @@ -129,7 +335,7 @@ func (table *Table) FilterRows() { pass := true for j, index := range colIndexes { c := table.Columns[index] - if c.Type == ColumnTypeBlock { + if c.Type == KeyTypeBlock { continue } @@ -156,15 +362,15 @@ func (table *Table) CalcCols() { } switch col.Type { - case ColumnTypeText: + case KeyTypeText: table.calcColText(col, i) - case ColumnTypeNumber: + case KeyTypeNumber: table.calcColNumber(col, i) - case ColumnTypeDate: + case KeyTypeDate: table.calcColDate(col, i) - case ColumnTypeSelect: + case KeyTypeSelect: table.calcColSelect(col, i) - case ColumnTypeMSelect: + case KeyTypeMSelect: table.calcColMSelect(col, i) } } diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go index 9a937a94a..141d3de2a 100644 --- a/kernel/model/attribute_view.go +++ b/kernel/model/attribute_view.go @@ -17,14 +17,11 @@ package model import ( - "errors" - "fmt" "strings" "github.com/88250/gulu" "github.com/88250/lute/ast" "github.com/88250/lute/parse" - "github.com/jinzhu/copier" "github.com/siyuan-note/logging" "github.com/siyuan-note/siyuan/kernel/av" "github.com/siyuan-note/siyuan/kernel/sql" @@ -77,9 +74,54 @@ func renderAttributeViewTable(attrView *av.AttributeView, view *av.View) (ret *a Sorts: view.Table.Sorts, } - for _, avRow := range attrView.Rows { - row := &av.TableRow{ID: avRow.ID, Cells: avRow.Cells} - ret.Rows = append(ret.Rows, row) + for _, col := range view.Table.Columns { + key, getErr := attrView.GetKey(col.ID) + if nil != getErr { + err = getErr + return + } + + ret.Columns = append(ret.Columns, &av.TableColumn{ + ID: key.ID, + Name: key.Name, + Type: key.Type, + Icon: key.Icon, + Wrap: col.Wrap, + Hidden: col.Hidden, + Width: col.Width, + }) + } + + rows := map[string][]*av.Value{} + for _, keyValues := range attrView.KeyValues { + for _, val := range keyValues.Values { + rows[val.BlockID] = append(rows[val.BlockID], val) + } + } + + for _, row := range rows { + var tableRow av.TableRow + for _, col := range ret.Columns { + var tableCell *av.TableCell + for _, val := range row { + if val.KeyID == col.ID { + tableCell = &av.TableCell{ + ID: val.ID, + Value: val, + ValueType: col.Type, + } + break + } + } + if nil == tableCell { + tableCell = &av.TableCell{ + ID: ast.NewNodeID(), + ValueType: col.Type, + } + } + tableRow.Cells = append(tableRow.Cells, tableCell) + } + ret.Rows = append(ret.Rows, &tableRow) } return } @@ -140,8 +182,11 @@ func setAttributeViewFilters(operation *Operation) (err error) { return } - if err = gulu.JSON.UnmarshalJSON(data, &view.Table.Filters); nil != err { - return + switch view.CurrentLayoutType { + case av.LayoutTypeTable: + if err = gulu.JSON.UnmarshalJSON(data, &view.Table.Filters); nil != err { + return + } } err = av.SaveAttributeView(attrView) @@ -174,8 +219,11 @@ func setAttributeViewSorts(operation *Operation) (err error) { return } - if err = gulu.JSON.UnmarshalJSON(data, &view.Table.Sorts); nil != err { - return + switch view.CurrentLayoutType { + case av.LayoutTypeTable: + if err = gulu.JSON.UnmarshalJSON(data, &view.Table.Sorts); nil != err { + return + } } err = av.SaveAttributeView(attrView) @@ -222,29 +270,32 @@ func addAttributeViewBlock(blockID string, operation *Operation, tree *parse.Tre return } - // 不允许重复添加相同的块到属性视图中 - for _, row := range attrView.Rows { - blockCell := row.GetBlockCell() - if nil == blockCell { - continue - } + view, err := attrView.GetView(operation.ViewID) + if nil != err { + return + } - if blockCell.Value.Block.ID == blockID { + // 不允许重复添加相同的块到属性视图中 + blockValues := attrView.GetBlockKeyValues() + for _, blockValue := range blockValues.Values { + if blockValue.Block.ID == blockID { return } } - row := av.NewRow() - attrs := parse.IAL2Map(node.KramdownIAL) - for _, col := range attrView.Columns { - if av.ColumnTypeBlock != col.Type { - attrs[NodeAttrNamePrefixAvCol+operation.AvID+"-"+col.ID] = "" // 将列作为属性添加到块中 - row.Cells = append(row.Cells, av.NewCell(col.Type)) - } else { - row.Cells = append(row.Cells, av.NewCellBlock(blockID, getNodeRefText(node))) + for _, keyValues := range attrView.KeyValues { + value := &av.Value{KeyID: keyValues.Key.ID, BlockID: blockID} + blockValues.Values = append(blockValues.Values, value) + + if av.KeyTypeBlock == keyValues.Key.Type { + value.Block = &av.ValueBlock{ID: blockID, Content: getNodeRefText(node)} + break } } + attrs := parse.IAL2Map(node.KramdownIAL) + attrs[NodeAttrNamePrefixAvKey+operation.AvID+"-"+blockValues.Key.ID] = "" // 将列作为属性添加到块中 + if "" == attrs[NodeAttrNameAVs] { attrs[NodeAttrNameAVs] = operation.AvID } else { @@ -258,13 +309,14 @@ func addAttributeViewBlock(blockID string, operation *Operation, tree *parse.Tre return } - if "" == operation.PreviousID { - attrView.Rows = append([]*av.Row{row}, attrView.Rows...) - } else { - for i, r := range attrView.Rows { - if r.ID == operation.PreviousID { - attrView.Rows = append(attrView.Rows[:i+1], append([]*av.Row{row}, attrView.Rows[i+1:]...)...) - break + switch view.CurrentLayoutType { + case av.LayoutTypeTable: + if "" != operation.PreviousID { + for i, id := range view.Table.RowIDs { + if id == operation.PreviousID { + view.Table.RowIDs = append(view.Table.RowIDs[:i+1], append([]string{blockID}, view.Table.RowIDs[i+1:]...)...) + break + } } } } @@ -289,16 +341,12 @@ func removeAttributeViewBlock(blockID string, operation *Operation) (err error) return } - for i, row := range attrView.Rows { - blockCell := row.GetBlockCell() - if nil == blockCell { - continue - } - - if blockCell.Value.Block.ID == blockID { - // 从行中移除,但是不移除属性 - attrView.Rows = append(attrView.Rows[:i], attrView.Rows[i+1:]...) - break + for _, keyValues := range attrView.KeyValues { + for i, values := range keyValues.Values { + if values.BlockID == blockID { + keyValues.Values = append(keyValues.Values[:i], keyValues.Values[i+1:]...) + break + } } } @@ -325,10 +373,13 @@ func setAttributeViewColWidth(operation *Operation) (err error) { return } - for _, column := range view.Table.Columns { - if column.ID == operation.ID { - column.Width = operation.Data.(string) - break + switch view.CurrentLayoutType { + case av.LayoutTypeTable: + for _, column := range view.Table.Columns { + if column.ID == operation.ID { + column.Width = operation.Data.(string) + break + } } } @@ -355,10 +406,13 @@ func setAttributeViewColWrap(operation *Operation) (err error) { return } - for _, column := range view.Table.Columns { - if column.ID == operation.ID { - column.Wrap = operation.Data.(bool) - break + switch view.CurrentLayoutType { + case av.LayoutTypeTable: + for _, column := range view.Table.Columns { + if column.ID == operation.ID { + column.Wrap = operation.Data.(bool) + break + } } } @@ -385,10 +439,13 @@ func setAttributeViewColHidden(operation *Operation) (err error) { return } - for _, column := range view.Table.Columns { - if column.ID == operation.ID { - column.Hidden = operation.Data.(bool) - break + switch view.CurrentLayoutType { + case av.LayoutTypeTable: + for _, column := range view.Table.Columns { + if column.ID == operation.ID { + column.Hidden = operation.Data.(bool) + break + } } } @@ -428,14 +485,17 @@ func sortAttributeViewRow(operation *Operation) (err error) { return } - view.Table.RowIDs = append(view.Table.RowIDs[:index], view.Table.RowIDs[index+1:]...) - for i, r := range view.Table.RowIDs { - if r == operation.PreviousID { - previousIndex = i + 1 - break + switch view.CurrentLayoutType { + case av.LayoutTypeTable: + view.Table.RowIDs = append(view.Table.RowIDs[:index], view.Table.RowIDs[index+1:]...) + for i, r := range view.Table.RowIDs { + if r == operation.PreviousID { + previousIndex = i + 1 + break + } } + view.Table.RowIDs = util.InsertElem(view.Table.RowIDs, previousIndex, rowID) } - view.Table.RowIDs = util.InsertElem(view.Table.RowIDs, previousIndex, rowID) err = av.SaveAttributeView(attrView) return @@ -460,32 +520,29 @@ func sortAttributeViewColumn(operation *Operation) (err error) { return } - var col *av.Column - var index, previousIndex int - for i, column := range attrView.Columns { - if column.ID == operation.ID { - col = column - index = i - break + switch view.CurrentLayoutType { + case av.LayoutTypeTable: + var col *av.ViewTableColumn + var index, previousIndex int + for i, column := range view.Table.Columns { + if column.ID == operation.ID { + col = column + index = i + break + } } - } - if nil == col { - return - } - - attrView.Columns = append(attrView.Columns[:index], attrView.Columns[index+1:]...) - for i, column := range attrView.Columns { - if column.ID == operation.PreviousID { - previousIndex = i + 1 - break + if nil == col { + return } - } - attrView.Columns = util.InsertElem(attrView.Columns, previousIndex, col) - for _, row := range attrView.Rows { - cel := row.Cells[index] - row.Cells = append(row.Cells[:index], row.Cells[index+1:]...) - row.Cells = util.InsertElem(row.Cells, previousIndex, cel) + view.Table.Columns = append(view.Table.Columns[:index], view.Table.Columns[index+1:]...) + for i, column := range view.Table.Columns { + if column.ID == operation.PreviousID { + previousIndex = i + 1 + break + } + } + view.Table.Columns = util.InsertElem(view.Table.Columns, previousIndex, col) } err = av.SaveAttributeView(attrView) @@ -511,21 +568,16 @@ func addAttributeViewColumn(operation *Operation) (err error) { return } - colType := av.ColumnType(operation.Typ) - switch colType { - case av.ColumnTypeText, av.ColumnTypeNumber, av.ColumnTypeDate, av.ColumnTypeSelect, av.ColumnTypeMSelect: - col := &av.Column{ID: ast.NewNodeID(), Name: operation.Name, Type: colType} - attrView.Columns = append(attrView.Columns, col) - view.Table.Columns = append(view.Table.Columns, &av.TableColumn{ID: col.ID, Name: col.Name, Type: col.Type}) + keyType := av.KeyType(operation.Typ) + switch keyType { + case av.KeyTypeText, av.KeyTypeNumber, av.KeyTypeDate, av.KeyTypeSelect, av.KeyTypeMSelect: + key := av.NewKey(operation.Name, keyType) + attrView.KeyValues = append(attrView.KeyValues, &av.KeyValues{Key: key}) - for _, row := range attrView.Rows { - row.Cells = append(row.Cells, av.NewCell(colType)) + switch view.CurrentLayoutType { + case av.LayoutTypeTable: + view.Table.Columns = append(view.Table.Columns, &av.ViewTableColumn{ID: key.ID}) } - default: - msg := fmt.Sprintf("invalid column type [%s]", operation.Typ) - logging.LogErrorf(msg) - err = errors.New(msg) - return } err = av.SaveAttributeView(attrView) @@ -546,31 +598,16 @@ func updateAttributeViewColumn(operation *Operation) (err error) { return } - colType := av.ColumnType(operation.Typ) + colType := av.KeyType(operation.Typ) switch colType { - case av.ColumnTypeText, av.ColumnTypeNumber, av.ColumnTypeDate, av.ColumnTypeSelect, av.ColumnTypeMSelect: - for _, col := range attrView.Columns { - if col.ID == operation.ID { - col.Name = operation.Name - col.Type = colType + case av.KeyTypeText, av.KeyTypeNumber, av.KeyTypeDate, av.KeyTypeSelect, av.KeyTypeMSelect: + for _, keyValues := range attrView.KeyValues { + if keyValues.Key.ID == operation.ID { + keyValues.Key.Name = operation.Name + keyValues.Key.Type = colType break } } - - for _, view := range attrView.Views { - for _, col := range view.Table.Columns { - if col.ID == operation.ID { - col.Name = operation.Name - col.Type = colType - break - } - } - } - default: - msg := fmt.Sprintf("invalid column type [%s]", operation.Typ) - logging.LogErrorf(msg) - err = errors.New(msg) - return } err = av.SaveAttributeView(attrView) @@ -591,25 +628,21 @@ func removeAttributeViewColumn(operation *Operation) (err error) { return } - for i, column := range attrView.Columns { - if column.ID == operation.ID { - attrView.Columns = append(attrView.Columns[:i], attrView.Columns[i+1:]...) - for _, row := range attrView.Rows { - if len(row.Cells) <= i { - continue - } - - row.Cells = append(row.Cells[:i], row.Cells[i+1:]...) - } + for i, keyValues := range attrView.KeyValues { + if keyValues.Key.ID == operation.ID { + attrView.KeyValues = append(attrView.KeyValues[:i], attrView.KeyValues[i+1:]...) break } } for _, view := range attrView.Views { - for i, column := range view.Table.Columns { - if column.ID == operation.ID { - view.Table.Columns = append(view.Table.Columns[:i], view.Table.Columns[i+1:]...) - break + switch view.CurrentLayoutType { + case av.LayoutTypeTable: + for i, column := range view.Table.Columns { + if column.ID == operation.ID { + view.Table.Columns = append(view.Table.Columns[:i], view.Table.Columns[i+1:]...) + break + } } } } @@ -618,48 +651,44 @@ func removeAttributeViewColumn(operation *Operation) (err error) { return } -// TODO 下面的方法要重写 - func (tx *Transaction) doUpdateAttrViewCell(operation *Operation) (ret *TxErr) { + err := updateAttributeViewCell(operation, tx) + if nil != err { + return &TxErr{code: TxErrWriteAttributeView, id: operation.ParentID, msg: err.Error()} + } + return +} + +func updateAttributeViewCell(operation *Operation, tx *Transaction) (err error) { avID := operation.ParentID - view, err := av.ParseAttributeView(avID) + attrView, err := av.ParseAttributeView(avID) if nil != err { - logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err) - return &TxErr{code: TxErrCodeBlockNotFound, id: avID, msg: err.Error()} + return } - var c *av.Cell - var blockID string - for _, row := range view.Rows { - if row.ID != operation.RowID { - continue - } - - blockCell := row.GetBlockCell() - if nil == blockCell { - continue - } - - blockID = blockCell.Value.Block.ID - for _, cell := range row.Cells { - if cell.ID == operation.ID { - c = cell - break + var val *av.Value + for _, keyValues := range attrView.KeyValues { + if operation.KeyID == keyValues.Key.ID { + for _, value := range keyValues.Values { + if operation.ID == value.ID { + val = value + break + } } + break } - break } - if nil == c { + if nil == val { return } - tree, err := tx.loadTree(blockID) + tree, err := tx.loadTree(val.BlockID) if nil != err { return } - node := treenode.GetNodeInTree(tree, blockID) + node := treenode.GetNodeInTree(tree, val.BlockID) if nil == node { return } @@ -668,248 +697,215 @@ func (tx *Transaction) doUpdateAttrViewCell(operation *Operation) (ret *TxErr) { if nil != err { return } - if err = gulu.JSON.UnmarshalJSON(data, &c.Value); nil != err { + if err = gulu.JSON.UnmarshalJSON(data, &val); nil != err { return } attrs := parse.IAL2Map(node.KramdownIAL) - attrs[NodeAttrNamePrefixAvCol+avID+"-"+c.ID] = c.Value.ToJSONString() + attrs[NodeAttrNamePrefixAvKey+avID+"-"+val.KeyID] = val.ToJSONString() if err = setNodeAttrsWithTx(tx, node, tree, attrs); nil != err { return } - if err = av.SaveAttributeView(view); nil != err { + if err = av.SaveAttributeView(attrView); nil != err { return } - - return -} - -func (tx *Transaction) doUpdateAttrViewColOption(operation *Operation) (ret *TxErr) { - err := updateAttributeViewColumnOption(operation) - if nil != err { - return &TxErr{code: TxErrWriteAttributeView, id: operation.ParentID, msg: err.Error()} - } return } -func (tx *Transaction) doRemoveAttrViewColOption(operation *Operation) (ret *TxErr) { - err := removeAttributeViewColumnOption(operation) - if nil != err { - return &TxErr{code: TxErrWriteAttributeView, id: operation.ParentID, msg: err.Error()} - } - return -} +// TODO 下面的方法要重写 -func (tx *Transaction) doUpdateAttrViewColOptions(operation *Operation) (ret *TxErr) { - err := updateAttributeViewColumnOptions(operation.Data, operation.ID, operation.ParentID) - if nil != err { - return &TxErr{code: TxErrWriteAttributeView, id: operation.ParentID, msg: err.Error()} - } - return -} - -func (tx *Transaction) doSetAttrView(operation *Operation) (ret *TxErr) { - err := setAttributeView(operation) - if nil != err { - return &TxErr{code: TxErrWriteAttributeView, id: operation.ParentID, msg: err.Error()} - } - return -} - -func updateAttributeViewColumnOption(operation *Operation) (err error) { - avID := operation.ParentID - attrView, err := av.ParseAttributeView(avID) - if nil != err { - return - } - - colID := operation.ID - data := operation.Data.(map[string]interface{}) - - oldName := data["oldName"].(string) - newName := data["newName"].(string) - newColor := data["newColor"].(string) - - var colIndex int - for i, col := range attrView.Columns { - if col.ID != colID { - continue - } - - colIndex = i - existOpt := false - for j, opt := range col.Options { - if opt.Name == newName { - existOpt = true - col.Options = append(col.Options[:j], col.Options[j+1:]...) - break - } - } - if !existOpt { - for _, opt := range col.Options { - if opt.Name != oldName { - continue - } - - opt.Name = newName - opt.Color = newColor - break - } - } - break - } - - for _, row := range attrView.Rows { - for i, cell := range row.Cells { - if colIndex != i || nil == cell.Value { - continue - } - - if nil != cell.Value.MSelect && 0 < len(cell.Value.MSelect) && nil != cell.Value.MSelect[0] { - if oldName == cell.Value.MSelect[0].Content { - cell.Value.MSelect[0].Content = newName - cell.Value.MSelect[0].Color = newColor - break - } - } else if nil != cell.Value.MSelect { - existInMSelect := false - for j, opt := range cell.Value.MSelect { - if opt.Content == newName { - existInMSelect = true - cell.Value.MSelect = append(cell.Value.MSelect[:j], cell.Value.MSelect[j+1:]...) - break - } - } - if !existInMSelect { - for j, opt := range cell.Value.MSelect { - if oldName == opt.Content { - cell.Value.MSelect[j].Content = newName - cell.Value.MSelect[j].Color = newColor - break - } - } - } - } - break - } - } - - err = av.SaveAttributeView(attrView) - return -} - -func removeAttributeViewColumnOption(operation *Operation) (err error) { - avID := operation.ParentID - attrView, err := av.ParseAttributeView(avID) - if nil != err { - return - } - - colID := operation.ID - optName := operation.Data.(string) - - var colIndex int - for i, col := range attrView.Columns { - if col.ID != colID { - continue - } - - colIndex = i - - for j, opt := range col.Options { - if opt.Name != optName { - continue - } - - col.Options = append(col.Options[:j], col.Options[j+1:]...) - break - } - break - } - - for _, row := range attrView.Rows { - for i, cell := range row.Cells { - if colIndex != i { - continue - } - - if nil != cell.Value { - if nil != cell.Value.MSelect && 0 < len(cell.Value.MSelect) && nil != cell.Value.MSelect[0] { - if optName == cell.Value.MSelect[0].Content { - cell.Value = nil - break - } - } else if nil != cell.Value.MSelect { - for j, opt := range cell.Value.MSelect { - if optName == opt.Content { - cell.Value.MSelect = append(cell.Value.MSelect[:j], cell.Value.MSelect[j+1:]...) - break - } - } - } - } - break - } - } - - err = av.SaveAttributeView(attrView) - return -} - -func updateAttributeViewColumnOptions(data interface{}, id, avID string) (err error) { - attrView, err := av.ParseAttributeView(avID) - if nil != err { - return - } - - jsonData, err := gulu.JSON.MarshalJSON(data) - if nil != err { - return - } - - options := []*av.ColumnSelectOption{} - if err = gulu.JSON.UnmarshalJSON(jsonData, &options); nil != err { - return - } - - for _, col := range attrView.Columns { - if col.ID == id { - col.Options = options - err = av.SaveAttributeView(attrView) - return - } - } - return -} - -func setAttributeView(operation *Operation) (err error) { - avID := operation.ID - attrViewMap, err := av.ParseAttributeViewMap(avID) - if nil != err { - return - } - - operationData := operation.Data.(map[string]interface{}) - if err = copier.Copy(&attrViewMap, operationData); nil != err { - return - } - - data, err := gulu.JSON.MarshalJSON(attrViewMap) - if nil != err { - return - } - - attrView := &av.AttributeView{} - if err = gulu.JSON.UnmarshalJSON(data, attrView); nil != err { - return - } - - err = av.SaveAttributeView(attrView) - return -} +//func (tx *Transaction) doUpdateAttrViewColOption(operation *Operation) (ret *TxErr) { +// err := updateAttributeViewColumnOption(operation) +// if nil != err { +// return &TxErr{code: TxErrWriteAttributeView, id: operation.ParentID, msg: err.Error()} +// } +// return +//} +// +//func (tx *Transaction) doRemoveAttrViewColOption(operation *Operation) (ret *TxErr) { +// err := removeAttributeViewColumnOption(operation) +// if nil != err { +// return &TxErr{code: TxErrWriteAttributeView, id: operation.ParentID, msg: err.Error()} +// } +// return +//} +// +//func (tx *Transaction) doUpdateAttrViewColOptions(operation *Operation) (ret *TxErr) { +// err := updateAttributeViewColumnOptions(operation.Data, operation.ID, operation.ParentID) +// if nil != err { +// return &TxErr{code: TxErrWriteAttributeView, id: operation.ParentID, msg: err.Error()} +// } +// return +//} +// +//func updateAttributeViewColumnOption(operation *Operation) (err error) { +// avID := operation.ParentID +// attrView, err := av.ParseAttributeView(avID) +// if nil != err { +// return +// } +// +// colID := operation.ID +// data := operation.Data.(map[string]interface{}) +// +// oldName := data["oldName"].(string) +// newName := data["newName"].(string) +// newColor := data["newColor"].(string) +// +// var colIndex int +// for i, col := range attrView.Columns { +// if col.ID != colID { +// continue +// } +// +// colIndex = i +// existOpt := false +// for j, opt := range col.Options { +// if opt.Name == newName { +// existOpt = true +// col.Options = append(col.Options[:j], col.Options[j+1:]...) +// break +// } +// } +// if !existOpt { +// for _, opt := range col.Options { +// if opt.Name != oldName { +// continue +// } +// +// opt.Name = newName +// opt.Color = newColor +// break +// } +// } +// break +// } +// +// for _, row := range attrView.Rows { +// for i, cell := range row.Cells { +// if colIndex != i || nil == cell.Value { +// continue +// } +// +// if nil != cell.Value.MSelect && 0 < len(cell.Value.MSelect) && nil != cell.Value.MSelect[0] { +// if oldName == cell.Value.MSelect[0].Content { +// cell.Value.MSelect[0].Content = newName +// cell.Value.MSelect[0].Color = newColor +// break +// } +// } else if nil != cell.Value.MSelect { +// existInMSelect := false +// for j, opt := range cell.Value.MSelect { +// if opt.Content == newName { +// existInMSelect = true +// cell.Value.MSelect = append(cell.Value.MSelect[:j], cell.Value.MSelect[j+1:]...) +// break +// } +// } +// if !existInMSelect { +// for j, opt := range cell.Value.MSelect { +// if oldName == opt.Content { +// cell.Value.MSelect[j].Content = newName +// cell.Value.MSelect[j].Color = newColor +// break +// } +// } +// } +// } +// break +// } +// } +// +// err = av.SaveAttributeView(attrView) +// return +//} +// +//func removeAttributeViewColumnOption(operation *Operation) (err error) { +// avID := operation.ParentID +// attrView, err := av.ParseAttributeView(avID) +// if nil != err { +// return +// } +// +// colID := operation.ID +// optName := operation.Data.(string) +// +// var colIndex int +// for i, col := range attrView.Columns { +// if col.ID != colID { +// continue +// } +// +// colIndex = i +// +// for j, opt := range col.Options { +// if opt.Name != optName { +// continue +// } +// +// col.Options = append(col.Options[:j], col.Options[j+1:]...) +// break +// } +// break +// } +// +// for _, row := range attrView.Rows { +// for i, cell := range row.Cells { +// if colIndex != i { +// continue +// } +// +// if nil != cell.Value { +// if nil != cell.Value.MSelect && 0 < len(cell.Value.MSelect) && nil != cell.Value.MSelect[0] { +// if optName == cell.Value.MSelect[0].Content { +// cell.Value = nil +// break +// } +// } else if nil != cell.Value.MSelect { +// for j, opt := range cell.Value.MSelect { +// if optName == opt.Content { +// cell.Value.MSelect = append(cell.Value.MSelect[:j], cell.Value.MSelect[j+1:]...) +// break +// } +// } +// } +// } +// break +// } +// } +// +// err = av.SaveAttributeView(attrView) +// return +//} +// +//func updateAttributeViewColumnOptions(data interface{}, id, avID string) (err error) { +// attrView, err := av.ParseAttributeView(avID) +// if nil != err { +// return +// } +// +// jsonData, err := gulu.JSON.MarshalJSON(data) +// if nil != err { +// return +// } +// +// options := []*av.ColumnSelectOption{} +// if err = gulu.JSON.UnmarshalJSON(jsonData, &options); nil != err { +// return +// } +// +// for _, col := range attrView.Columns { +// if col.ID == id { +// col.Options = options +// err = av.SaveAttributeView(attrView) +// return +// } +// } +// return +//} const ( NodeAttrNameAVs = "custom-avs" - NodeAttrNamePrefixAvCol = "custom-av-col-" + NodeAttrNamePrefixAvKey = "custom-av-key-" ) diff --git a/kernel/model/transaction.go b/kernel/model/transaction.go index f84b61c69..87cc11bf8 100644 --- a/kernel/model/transaction.go +++ b/kernel/model/transaction.go @@ -243,17 +243,15 @@ func performTx(tx *Transaction) (ret *TxErr) { ret = tx.doSortAttrViewRow(op) case "sortAttrViewCol": ret = tx.doSortAttrViewColumn(op) - // TODO 下面的方法要重写 case "updateAttrViewCell": ret = tx.doUpdateAttrViewCell(op) - case "setAttrView": - ret = tx.doSetAttrView(op) - case "updateAttrViewColOptions": - ret = tx.doUpdateAttrViewColOptions(op) - case "removeAttrViewColOption": - ret = tx.doRemoveAttrViewColOption(op) - case "updateAttrViewColOption": - ret = tx.doUpdateAttrViewColOption(op) + // TODO 下面的方法要重写 + //case "updateAttrViewColOptions": + // ret = tx.doUpdateAttrViewColOptions(op) + //case "removeAttrViewColOption": + // ret = tx.doRemoveAttrViewColOption(op) + //case "updateAttrViewColOption": + // ret = tx.doUpdateAttrViewColOption(op) } if nil != ret { @@ -1058,7 +1056,8 @@ type Operation struct { SrcIDs []string `json:"srcIDs"` // 用于将块拖拽到属性视图中 Name string `json:"name"` // 属性视图列名 Typ string `json:"type"` // 属性视图列类型 - RowID string `json:"rowID"` // 属性视图行 ID + KeyID string `json:"keyID"` // 属性视列 ID + RowID string `json:"rowID"` // 属性视图行 ID(块 ID) discard bool // 用于标识是否在事务合并中丢弃 } @@ -1219,16 +1218,12 @@ func refreshDynamicRefTexts(updatedDefNodes map[string]*ast.Node, updatedTrees m } changedAv := false - for _, row := range attrView.Rows { - blockCell := row.GetBlockCell() - if nil == blockCell || nil == blockCell.Value || nil == blockCell.Value.Block { - continue - } - - if blockCell.Value.Block.ID == updatedDefNode.ID { + blockValues := attrView.GetBlockKeyValues() + for _, blockValue := range blockValues.Values { + if blockValue.Block.ID == updatedDefNode.ID { newContent := getNodeRefText(updatedDefNode) - if newContent != blockCell.Value.Block.Content { - blockCell.Value.Block.Content = newContent + if newContent != blockValue.Block.Content { + blockValue.Block.Content = newContent changedAv = true } break @@ -1236,7 +1231,6 @@ func refreshDynamicRefTexts(updatedDefNodes map[string]*ast.Node, updatedTrees m } if changedAv { av.SaveAttributeView(attrView) - util.BroadcastByType("protyle", "refreshAttributeView", 0, "", map[string]interface{}{"id": avID}) } }