diff --git a/kernel/api/av.go b/kernel/api/av.go index c3959bf7f..c53b247ef 100644 --- a/kernel/api/av.go +++ b/kernel/api/av.go @@ -26,6 +26,51 @@ import ( "github.com/siyuan-note/siyuan/kernel/util" ) +func getAttributeView(c *gin.Context) { + ret := gulu.Ret.NewResult() + defer c.JSON(http.StatusOK, ret) + + arg, _ := util.JsonArg(c, ret) + if nil == arg { + return + } + + id := arg["id"].(string) + av := model.GetAttributeView(id) + ret.Data = map[string]interface{}{ + "av": av, + } +} + +func searchAttributeView(c *gin.Context) { + ret := gulu.Ret.NewResult() + defer c.JSON(http.StatusOK, ret) + + arg, _ := util.JsonArg(c, ret) + if nil == arg { + return + } + + keyword := arg["keyword"].(string) + page := 1 + pageArg := arg["page"] + if nil != pageArg { + page = int(pageArg.(float64)) + } + + pageSize := 10 + pageSizeArg := arg["pageSize"] + if nil != pageSizeArg { + pageSize = int(pageSizeArg.(float64)) + } + + results, total := model.SearchAttributeView(keyword, page, pageSize) + ret.Data = map[string]interface{}{ + "results": results, + "total": total, + } +} + func renderSnapshotAttributeView(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret) diff --git a/kernel/api/router.go b/kernel/api/router.go index 49138e216..e3b5ff7df 100644 --- a/kernel/api/router.go +++ b/kernel/api/router.go @@ -386,6 +386,8 @@ func ServeAPI(ginServer *gin.Engine) { ginServer.Handle("POST", "/api/av/renderSnapshotAttributeView", model.CheckAuth, renderSnapshotAttributeView) ginServer.Handle("POST", "/api/av/getAttributeViewKeys", model.CheckAuth, getAttributeViewKeys) ginServer.Handle("POST", "/api/av/setAttributeViewBlockAttr", model.CheckAuth, model.CheckReadonly, setAttributeViewBlockAttr) + ginServer.Handle("POST", "/api/av/searchAttributeView", model.CheckAuth, model.CheckReadonly, searchAttributeView) + ginServer.Handle("POST", "/api/av/getAttributeView", model.CheckAuth, model.CheckReadonly, getAttributeView) ginServer.Handle("POST", "/api/ai/chatGPT", model.CheckAuth, chatGPT) ginServer.Handle("POST", "/api/ai/chatGPTWithAction", model.CheckAuth, chatGPTWithAction) diff --git a/kernel/av/av.go b/kernel/av/av.go index a85a3766d..261d36f6f 100644 --- a/kernel/av/av.go +++ b/kernel/av/av.go @@ -80,7 +80,7 @@ type Key struct { // 以下是某些列类型的特有属性 // 单选/多选列 - Options []*KeySelectOption `json:"options,omitempty"` // 选项列表 + Options []*SelectOption `json:"options,omitempty"` // 选项列表 // 数字列 NumberFormat NumberFormat `json:"numberFormat"` // 列数字格式化 @@ -89,13 +89,10 @@ type Key struct { Template string `json:"template"` // 模板内容 // 关联列 - RelationAvID string `json:"relationAvID"` // 关联的属性视图 ID - RelationKeyID string `json:"relationKeyID"` // 关联列 ID - IsBiRelation bool `json:"isBiRelation"` // 是否双向关联 - BackRelationKeyID string `json:"backRelationKeyID"` // 双向关联时回链关联列的 ID + Relation *Relation `json:"relation,omitempty"` // 关联信息 // 汇总列 - RollupKeyID string `json:"rollupKeyID"` // 汇总列 ID + Rollup *Rollup `json:"rollup,omitempty"` // 汇总信息 } func NewKey(id, name, icon string, keyType KeyType) *Key { @@ -107,7 +104,17 @@ func NewKey(id, name, icon string, keyType KeyType) *Key { } } -type KeySelectOption struct { +type Rollup struct { + KeyID string `json:"keyID"` // 汇总列 ID +} + +type Relation struct { + AvID string `json:"avID"` // 关联的属性视图 ID + IsTwoWay bool `json:"isTwoWay"` // 是否双向关联 + BackKeyID string `json:"backKeyID"` // 双向关联时回链关联列的 ID +} + +type SelectOption struct { Name string `json:"name"` Color string `json:"color"` } diff --git a/kernel/av/table.go b/kernel/av/table.go index 50f47e4d6..d2bb32665 100644 --- a/kernel/av/table.go +++ b/kernel/av/table.go @@ -651,9 +651,11 @@ type TableColumn struct { // 以下是某些列类型的特有属性 - Options []*KeySelectOption `json:"options,omitempty"` // 选项列表 - NumberFormat NumberFormat `json:"numberFormat"` // 列数字格式化 - Template string `json:"template"` // 模板内容 + Options []*SelectOption `json:"options,omitempty"` // 选项列表 + NumberFormat NumberFormat `json:"numberFormat"` // 列数字格式化 + Template string `json:"template"` // 模板内容 + Relation *Relation `json:"relation,omitempty"` // 关联列 + Rollup *Rollup `json:"rollup,omitempty"` // 汇总列 } type TableCell struct { diff --git a/kernel/model/attribute_view.go b/kernel/model/attribute_view.go index 93cc157fb..8df88ef34 100644 --- a/kernel/model/attribute_view.go +++ b/kernel/model/attribute_view.go @@ -37,6 +37,70 @@ import ( "github.com/siyuan-note/siyuan/kernel/util" ) +func GetAttributeView(avID string) (ret *av.AttributeView) { + waitForSyncingStorages() + + ret, _ = av.ParseAttributeView(avID) + return +} + +type SearchAttributeViewResult struct { + AvID string `json:"avID"` + AvName string `json:"avName"` + BlockID string `json:"blockID"` +} + +func SearchAttributeView(keyword string, page int, pageSize int) (ret []*SearchAttributeViewResult, pageCount int) { + waitForSyncingStorages() + + ret = []*SearchAttributeViewResult{} + blocks, _, _, pageCount := FullTextSearchBlock(keyword, nil, nil, map[string]bool{"databaseBlock": true}, 0, 7, 0, page, pageSize) + trees := map[string]*parse.Tree{} + for _, block := range blocks { + tree := trees[block.RootID] + if nil == tree { + tree, _ = loadTreeByBlockID(block.ID) + if nil != tree { + trees[block.RootID] = tree + } + } + if nil == tree { + continue + } + + node := treenode.GetNodeInTree(tree, block.ID) + if nil == node { + continue + } + + if "" == node.AttributeViewID { + continue + } + + avID := node.AttributeViewID + attrView, _ := av.ParseAttributeView(avID) + if nil == attrView { + continue + } + + exist := false + for _, result := range ret { + if result.AvID == avID { + exist = true + break + } + } + if !exist { + ret = append(ret, &SearchAttributeViewResult{ + AvID: avID, + AvName: attrView.Name, + BlockID: block.ID, + }) + } + } + return +} + type BlockAttributeViewKeys struct { AvID string `json:"avID"` AvName string `json:"avName"` @@ -489,6 +553,8 @@ func renderAttributeViewTable(attrView *av.AttributeView, view *av.View) (ret *a Options: key.Options, NumberFormat: key.NumberFormat, Template: key.Template, + Relation: key.Relation, + Rollup: key.Rollup, Wrap: col.Wrap, Hidden: col.Hidden, Width: col.Width, @@ -655,6 +721,102 @@ func getRowBlockValue(keyValues []*av.KeyValues) (ret *av.Value) { return } +func (tx *Transaction) doUpdateAttrViewColRelation(operation *Operation) (ret *TxErr) { + err := updateAttributeViewColRelation(operation) + if nil != err { + return &TxErr{code: TxErrWriteAttributeView, id: operation.AvID, msg: err.Error()} + } + return +} + +func updateAttributeViewColRelation(operation *Operation) (err error) { + // operation.AvID 源 avID + // operation.ID 目标 avID + // operation.KeyID 源 av 关联列 ID + // operation.IsTwoWay 是否双向关联 + // operation.BackRelationKeyID 双向关联的目标关联列 ID + // operation.Name 双向关联的目标关联列名称 + + srcAv, err := av.ParseAttributeView(operation.AvID) + if nil != err { + return + } + + destAv, err := av.ParseAttributeView(operation.ID) + if nil != err { + return + } + + isSameAv := srcAv.ID == destAv.ID + + for _, keyValues := range srcAv.KeyValues { + if keyValues.Key.ID == operation.KeyID { + // 已经设置过双向关联的话需要先断开双向关联 + if nil != keyValues.Key.Relation && keyValues.Key.Relation.IsTwoWay { + oldDestAv, parseErr := av.ParseAttributeView(keyValues.Key.Relation.AvID) + if nil == parseErr { + isOldSameAv := oldDestAv.ID == destAv.ID + if isOldSameAv { + oldDestAv = destAv + } + + oldDestKey, _ := oldDestAv.GetKey(keyValues.Key.Relation.BackKeyID) + if nil != oldDestKey && nil != oldDestKey.Relation && oldDestKey.Relation.AvID == srcAv.ID && oldDestKey.Relation.IsTwoWay { + oldDestKey.Relation.IsTwoWay = false + oldDestKey.Relation.BackKeyID = "" + } + + if !isOldSameAv { + err = av.SaveAttributeView(oldDestAv) + if nil != err { + return + } + } + } + } + + keyValues.Key.Relation = &av.Relation{ + AvID: operation.ID, + IsTwoWay: operation.IsTwoWay, + BackKeyID: operation.BackRelationKeyID, + } + break + } + } + + destAdded := false + for _, keyValues := range destAv.KeyValues { + if keyValues.Key.ID == operation.BackRelationKeyID { + keyValues.Key.Relation = &av.Relation{ + AvID: operation.AvID, + IsTwoWay: operation.IsTwoWay, + BackKeyID: operation.KeyID, + } + destAdded = true + break + } + } + if !destAdded { + destAv.KeyValues = append(destAv.KeyValues, &av.KeyValues{ + Key: &av.Key{ + ID: operation.BackRelationKeyID, + Name: operation.Name, + Type: av.KeyTypeRelation, + Relation: &av.Relation{AvID: operation.AvID, IsTwoWay: operation.IsTwoWay, BackKeyID: operation.KeyID}, + }, + }) + } + + err = av.SaveAttributeView(srcAv) + if nil != err { + return + } + if !isSameAv { + err = av.SaveAttributeView(destAv) + } + return +} + func (tx *Transaction) doSortAttrViewView(operation *Operation) (ret *TxErr) { avID := operation.AvID attrView, err := av.ParseAttributeView(avID) @@ -1948,7 +2110,7 @@ func updateAttributeViewColumnOptions(operation *Operation) (err error) { return } - options := []*av.KeySelectOption{} + options := []*av.SelectOption{} if err = gulu.JSON.UnmarshalJSON(jsonData, &options); nil != err { return } diff --git a/kernel/model/transaction.go b/kernel/model/transaction.go index 352d1b03e..35153b835 100644 --- a/kernel/model/transaction.go +++ b/kernel/model/transaction.go @@ -258,6 +258,8 @@ func performTx(tx *Transaction) (ret *TxErr) { ret = tx.doDuplicateAttrViewView(op) case "sortAttrViewView": ret = tx.doSortAttrViewView(op) + case "updateAttrViewColRelation": + ret = tx.doUpdateAttrViewColRelation(op) } if nil != ret { @@ -1181,14 +1183,16 @@ type Operation struct { DeckID string `json:"deckID"` // 用于添加/删除闪卡 - AvID string `json:"avID"` // 属性视图 ID - SrcIDs []string `json:"srcIDs"` // 用于将块拖拽到属性视图中 - IsDetached bool `json:"isDetached"` // 用于标识是否是脱离块,仅存在于属性视图中 - Name string `json:"name"` // 属性视图列名 - Typ string `json:"type"` // 属性视图列类型 - Format string `json:"format"` // 属性视图列格式化 - KeyID string `json:"keyID"` // 属性视列 ID - RowID string `json:"rowID"` // 属性视图行 ID + AvID string `json:"avID"` // 属性视图 ID + SrcIDs []string `json:"srcIDs"` // 用于将块拖拽到属性视图中 + IsDetached bool `json:"isDetached"` // 用于标识是否是脱离块,仅存在于属性视图中 + Name string `json:"name"` // 属性视图列名 + Typ string `json:"type"` // 属性视图列类型 + Format string `json:"format"` // 属性视图列格式化 + KeyID string `json:"keyID"` // 属性视列 ID + RowID string `json:"rowID"` // 属性视图行 ID + IsTwoWay bool `json:"isTwoWay"` // 属性视图关联列是否是双向关系 + BackRelationKeyID string `json:"backRelationKeyID"` // 属性视图关联列回链关联列的 ID } type Transaction struct { diff --git a/kernel/treenode/node.go b/kernel/treenode/node.go index 1e07f7d86..5bca7a517 100644 --- a/kernel/treenode/node.go +++ b/kernel/treenode/node.go @@ -614,6 +614,8 @@ func renderAttributeViewTable(attrView *av.AttributeView, view *av.View) (ret *a Options: key.Options, NumberFormat: key.NumberFormat, Template: key.Template, + Relation: key.Relation, + Rollup: key.Rollup, Wrap: col.Wrap, Hidden: col.Hidden, Width: col.Width,