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})
}
}