Quellcode durchsuchen

Merge remote-tracking branch 'origin/dev' into dev

Vanessa vor 1 Jahr
Ursprung
Commit
5db30cdcd9
6 geänderte Dateien mit 264 neuen und 1 gelöschten Zeilen
  1. 46 0
      API.md
  2. 46 0
      API_zh_CN.md
  3. 120 0
      kernel/api/block_op.go
  4. 2 0
      kernel/api/router.go
  5. 36 1
      kernel/model/attribute_view.go
  6. 14 0
      kernel/model/transaction.go

+ 46 - 0
API.md

@@ -28,6 +28,8 @@
     * [Update a block](#Update-a-block)
     * [Delete a block](#Delete-a-block)
     * [Move a block](#Move-a-block)
+    * [Fold a block](#Fold-a-block)
+    * [Unfold a block](#Unfold-a-block)
     * [Get a block kramdown](#Get-a-block-kramdown)
     * [Get child blocks](#get-child-blocks)
     * [Transfer block ref](#transfer-block-ref)
@@ -783,6 +785,50 @@ View API token in <kbd>Settings - About</kbd>, request header: `Authorization: T
   }
   ```
 
+### Fold a block
+
+* `/api/block/foldBlock`
+* Parameters
+
+  ```json
+  {
+    "id": "20231224160424-2f5680o"
+  }
+  ```
+
+  * `id`: Block ID to fold
+* Return value
+
+  ```json
+  {
+    "code": 0,
+    "msg": "",
+    "data": null
+  }
+  ```
+
+### Unfold a block
+
+* `/api/block/unfoldBlock`
+* Parameters
+
+  ```json
+  {
+    "id": "20231224160424-2f5680o"
+  }
+  ```
+
+  * `id`: Block ID to unfold
+* Return value
+
+  ```json
+  {
+    "code": 0,
+    "msg": "",
+    "data": null
+  }
+  ```
+
 ### Get a block kramdown
 
 * `/api/block/getBlockKramdown`

+ 46 - 0
API_zh_CN.md

@@ -29,6 +29,8 @@
     * [更新块](#更新块)
     * [删除块](#删除块)
     * [移动块](#移动块)
+    * [折叠块](#折叠块)
+    * [展开块](#展开块)
     * [获取块 kramdown 源码](#获取块-kramdown-源码)
     * [获取子块](#获取子块)
     * [转移块引用](#转移块引用)
@@ -776,6 +778,50 @@
   }
   ```
 
+### 折叠块
+
+* `/api/block/foldBlock`
+* 参数
+
+  ```json
+  {
+    "id": "20231224160424-2f5680o"
+  }
+  ```
+
+  * `id`:待折叠块的 ID
+* 返回值
+
+  ```json
+  {
+    "code": 0,
+    "msg": "",
+    "data": null
+  }
+  ```
+
+### 展开块
+
+* `/api/block/unfoldBlock`
+* 参数
+
+  ```json
+  {
+    "id": "20231224160424-2f5680o"
+  }
+  ```
+
+  * `id`:待展开块的 ID
+* 返回值
+
+  ```json
+  {
+    "code": 0,
+    "msg": "",
+    "data": null
+  }
+  ```
+
 ### 获取块 kramdown 源码
 
 * `/api/block/getBlockKramdown`

+ 120 - 0
kernel/api/block_op.go

@@ -29,6 +29,126 @@ import (
 	"github.com/siyuan-note/siyuan/kernel/util"
 )
 
+func unfoldBlock(c *gin.Context) {
+	ret := gulu.Ret.NewResult()
+	defer c.JSON(http.StatusOK, ret)
+
+	arg, ok := util.JsonArg(c, ret)
+	if !ok {
+		return
+	}
+
+	id := arg["id"].(string)
+	if util.InvalidIDPattern(id, ret) {
+		return
+	}
+
+	bt := treenode.GetBlockTree(id)
+	if nil == bt {
+		ret.Code = -1
+		ret.Msg = "block tree not found [id=" + id + "]"
+		return
+	}
+
+	if bt.Type == "d" {
+		ret.Code = -1
+		ret.Msg = "document can not be unfolded"
+		return
+	}
+
+	var transactions []*model.Transaction
+	if "h" == bt.Type {
+		transactions = []*model.Transaction{
+			{
+				DoOperations: []*model.Operation{
+					{
+						Action: "unfoldHeading",
+						ID:     id,
+					},
+				},
+			},
+		}
+	} else {
+		data, _ := gulu.JSON.MarshalJSON(map[string]interface{}{"unfold": "1"})
+		transactions = []*model.Transaction{
+			{
+				DoOperations: []*model.Operation{
+					{
+						Action: "setAttrs",
+						ID:     id,
+						Data:   string(data),
+					},
+				},
+			},
+		}
+	}
+
+	model.PerformTransactions(&transactions)
+	model.WaitForWritingFiles()
+
+	broadcastTransactions(transactions)
+}
+
+func foldBlock(c *gin.Context) {
+	ret := gulu.Ret.NewResult()
+	defer c.JSON(http.StatusOK, ret)
+
+	arg, ok := util.JsonArg(c, ret)
+	if !ok {
+		return
+	}
+
+	id := arg["id"].(string)
+	if util.InvalidIDPattern(id, ret) {
+		return
+	}
+
+	bt := treenode.GetBlockTree(id)
+	if nil == bt {
+		ret.Code = -1
+		ret.Msg = "block tree not found [id=" + id + "]"
+		return
+	}
+
+	if bt.Type == "d" {
+		ret.Code = -1
+		ret.Msg = "document can not be folded"
+		return
+	}
+
+	var transactions []*model.Transaction
+	if "h" == bt.Type {
+		transactions = []*model.Transaction{
+			{
+				DoOperations: []*model.Operation{
+					{
+						Action: "foldHeading",
+						ID:     id,
+					},
+				},
+			},
+		}
+	} else {
+		data, _ := gulu.JSON.MarshalJSON(map[string]interface{}{"fold": "1"})
+		transactions = []*model.Transaction{
+			{
+				DoOperations: []*model.Operation{
+					{
+						Action: "setAttrs",
+						ID:     id,
+						Data:   string(data),
+					},
+				},
+			},
+		}
+	}
+
+	model.PerformTransactions(&transactions)
+	model.WaitForWritingFiles()
+
+	broadcastTransactions(transactions)
+}
+
 func moveBlock(c *gin.Context) {
 	ret := gulu.Ret.NewResult()
 	defer c.JSON(http.StatusOK, ret)

+ 2 - 0
kernel/api/router.go

@@ -181,6 +181,8 @@ func ServeAPI(ginServer *gin.Engine) {
 	ginServer.Handle("POST", "/api/block/updateBlock", model.CheckAuth, model.CheckReadonly, updateBlock)
 	ginServer.Handle("POST", "/api/block/deleteBlock", model.CheckAuth, model.CheckReadonly, deleteBlock)
 	ginServer.Handle("POST", "/api/block/moveBlock", model.CheckAuth, model.CheckReadonly, moveBlock)
+	ginServer.Handle("POST", "/api/block/foldBlock", model.CheckAuth, model.CheckReadonly, foldBlock)
+	ginServer.Handle("POST", "/api/block/unfoldBlock", model.CheckAuth, model.CheckReadonly, unfoldBlock)
 	ginServer.Handle("POST", "/api/block/setBlockReminder", model.CheckAuth, model.CheckReadonly, setBlockReminder)
 	ginServer.Handle("POST", "/api/block/getHeadingLevelTransaction", model.CheckAuth, getHeadingLevelTransaction)
 	ginServer.Handle("POST", "/api/block/getHeadingDeleteTransaction", model.CheckAuth, getHeadingDeleteTransaction)

+ 36 - 1
kernel/model/attribute_view.go

@@ -862,10 +862,15 @@ func updateAttributeViewColRelation(operation *Operation) (err error) {
 
 	if !destAdded {
 		if operation.IsTwoWay {
+			name := strings.TrimSpace(operation.Name)
+			if "" == name {
+				name = srcAv.Name
+			}
+
 			destAv.KeyValues = append(destAv.KeyValues, &av.KeyValues{
 				Key: &av.Key{
 					ID:       operation.BackRelationKeyID,
-					Name:     operation.Name,
+					Name:     name,
 					Type:     av.KeyTypeRelation,
 					Relation: &av.Relation{AvID: operation.AvID, IsTwoWay: operation.IsTwoWay, BackKeyID: operation.KeyID},
 				},
@@ -1893,13 +1898,43 @@ func removeAttributeViewColumn(operation *Operation) (err error) {
 		return
 	}
 
+	var removedKey *av.Key
 	for i, keyValues := range attrView.KeyValues {
 		if keyValues.Key.ID == operation.ID {
 			attrView.KeyValues = append(attrView.KeyValues[:i], attrView.KeyValues[i+1:]...)
+			removedKey = keyValues.Key
 			break
 		}
 	}
 
+	// 删除双向关联的目标列
+	if nil != removedKey && nil != removedKey.Relation && removedKey.Relation.IsTwoWay {
+		destAv, _ := av.ParseAttributeView(removedKey.Relation.AvID)
+		if nil != destAv {
+			for i, keyValues := range destAv.KeyValues {
+				if keyValues.Key.ID == removedKey.Relation.BackKeyID {
+					destAv.KeyValues = append(destAv.KeyValues[:i], destAv.KeyValues[i+1:]...)
+					break
+				}
+			}
+
+			for _, view := range destAv.Views {
+				switch view.LayoutType {
+				case av.LayoutTypeTable:
+					for i, column := range view.Table.Columns {
+						if column.ID == removedKey.Relation.BackKeyID {
+							view.Table.Columns = append(view.Table.Columns[:i], view.Table.Columns[i+1:]...)
+							break
+						}
+					}
+				}
+			}
+
+			av.SaveAttributeView(destAv)
+			util.BroadcastByType("protyle", "refreshAttributeView", 0, "", map[string]interface{}{"id": destAv.ID})
+		}
+	}
+
 	for _, view := range attrView.Views {
 		switch view.LayoutType {
 		case av.LayoutTypeTable:

+ 14 - 0
kernel/model/transaction.go

@@ -21,6 +21,7 @@ import (
 	"errors"
 	"fmt"
 	"path/filepath"
+	"runtime/debug"
 	"strings"
 	"sync"
 	"sync/atomic"
@@ -168,6 +169,19 @@ func performTx(tx *Transaction) (ret *TxErr) {
 		return
 	}
 
+	defer func() {
+		if e := recover(); nil != e {
+			stack := debug.Stack()
+			msg := fmt.Sprintf("PANIC RECOVERED: %v\n\t%s\n", e, stack)
+			logging.LogErrorf(msg)
+
+			if 0 == tx.state.Load() {
+				tx.rollback()
+				return
+			}
+		}
+	}()
+
 	for _, op := range tx.DoOperations {
 		switch op.Action {
 		case "create":