diff --git a/kernel/api/block_op.go b/kernel/api/block_op.go index 829c266a0..0e0b32cde 100644 --- a/kernel/api/block_op.go +++ b/kernel/api/block_op.go @@ -32,6 +32,54 @@ import ( "github.com/siyuan-note/siyuan/kernel/util" ) +func moveOutlineHeading(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 + } + + var parentID, previousID string + if nil != arg["parentID"] { + parentID = arg["parentID"].(string) + if "" != parentID && util.InvalidIDPattern(parentID, ret) { + return + } + } + if nil != arg["previousID"] { + previousID = arg["previousID"].(string) + if "" != previousID && util.InvalidIDPattern(previousID, ret) { + return + } + } + + transactions := []*model.Transaction{ + { + DoOperations: []*model.Operation{ + { + Action: "moveOutlineHeading", + ID: id, + PreviousID: previousID, + ParentID: parentID, + }, + }, + }, + } + + model.PerformTransactions(&transactions) + model.WaitForWritingFiles() + + ret.Data = transactions + broadcastTransactions(transactions) +} + func appendDailyNoteBlock(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 e42551a04..0b6e89593 100644 --- a/kernel/api/router.go +++ b/kernel/api/router.go @@ -188,6 +188,7 @@ 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/moveOutlineHeading", model.CheckAuth, model.CheckReadonly, moveOutlineHeading) 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) diff --git a/kernel/model/outline.go b/kernel/model/outline.go index ea8ba9a0d..9d519821a 100644 --- a/kernel/model/outline.go +++ b/kernel/model/outline.go @@ -26,6 +26,103 @@ import ( "github.com/siyuan-note/siyuan/kernel/util" ) +func (tx *Transaction) doMoveOutlineHeading(operation *Operation) (ret *TxErr) { + headingID := operation.ID + previousID := operation.PreviousID + parentID := operation.ParentID + + if headingID == parentID || headingID == previousID { + return + } + + tree, err := tx.loadTree(headingID) + if nil != err { + return &TxErr{code: TxErrCodeBlockNotFound, id: headingID} + } + + heading := treenode.GetNodeInTree(tree, headingID) + if nil == heading { + return &TxErr{code: TxErrCodeBlockNotFound, id: headingID} + } + + headings := []*ast.Node{} + ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { + if entering && ast.NodeHeading == n.Type && !n.ParentIs(ast.NodeBlockquote) { + headings = append(headings, n) + } + return ast.WalkContinue + }) + + headingChildren := treenode.HeadingChildren(heading) + if "" != previousID { + previousHeading := treenode.GetNodeInTree(tree, previousID) + if nil == previousHeading { + return &TxErr{code: TxErrCodeBlockNotFound, id: previousID} + } + + targetNode := previousHeading + previousHeadingChildren := treenode.HeadingChildren(previousHeading) + if 0 < len(previousHeadingChildren) { + for _, child := range previousHeadingChildren { + targetNode = child + if child.ID == headingID { + break + } + } + } + + diffLevel := heading.HeadingLevel - previousHeading.HeadingLevel + heading.HeadingLevel = previousHeading.HeadingLevel + + for i := len(headingChildren) - 1; i >= 0; i-- { + child := headingChildren[i] + if ast.NodeHeading == child.Type { + child.HeadingLevel -= diffLevel + } + targetNode.InsertAfter(child) + } + targetNode.InsertAfter(heading) + } else if "" != parentID { + parentHeading := treenode.GetNodeInTree(tree, parentID) + if nil == parentHeading { + return &TxErr{code: TxErrCodeBlockNotFound, id: parentID} + } + + targetNode := parentHeading + parentHeadingChildren := treenode.HeadingChildren(parentHeading) + if 0 < len(parentHeadingChildren) { + for _, child := range parentHeadingChildren { + targetNode = child + if child.ID == headingID { + break + } + } + } + + diffLevel := heading.HeadingLevel - parentHeading.HeadingLevel + heading.HeadingLevel = parentHeading.HeadingLevel + 1 + if 6 < heading.HeadingLevel { + heading.HeadingLevel = 6 + } + + for i := len(headingChildren) - 1; i >= 0; i-- { + child := headingChildren[i] + if ast.NodeHeading == child.Type { + child.HeadingLevel -= diffLevel + } + targetNode.InsertAfter(child) + } + targetNode.InsertAfter(heading) + } else { + return + } + + if err = tx.writeTree(tree); nil != err { + return + } + return +} + func Outline(rootID string) (ret []*Path, err error) { time.Sleep(util.FrontendQueueInterval) WaitForWritingFiles() diff --git a/kernel/model/transaction.go b/kernel/model/transaction.go index 88c356d2e..5d2e07f16 100644 --- a/kernel/model/transaction.go +++ b/kernel/model/transaction.go @@ -195,6 +195,8 @@ func performTx(tx *Transaction) (ret *TxErr) { ret = tx.doDelete(op) case "move": ret = tx.doMove(op) + case "moveOutlineHeading": + ret = tx.doMoveOutlineHeading(op) case "append": ret = tx.doAppend(op) case "appendInsert":