diff --git a/kernel/api/block_op.go b/kernel/api/block_op.go index 051e03ae5..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) @@ -222,13 +270,13 @@ func moveBlock(c *gin.Context) { var parentID, previousID string if nil != arg["parentID"] { parentID = arg["parentID"].(string) - if util.InvalidIDPattern(parentID, ret) { + if "" != parentID && util.InvalidIDPattern(parentID, ret) { return } } if nil != arg["previousID"] { previousID = arg["previousID"].(string) - if util.InvalidIDPattern(previousID, ret) { + if "" != previousID && util.InvalidIDPattern(previousID, ret) { return } @@ -364,26 +412,20 @@ func insertBlock(c *gin.Context) { var parentID, previousID, nextID string if nil != arg["parentID"] { parentID = arg["parentID"].(string) - if "" != parentID { - if util.InvalidIDPattern(parentID, ret) { - return - } + if "" != parentID && util.InvalidIDPattern(parentID, ret) { + return } } if nil != arg["previousID"] { previousID = arg["previousID"].(string) - if "" != previousID { - if util.InvalidIDPattern(previousID, ret) { - return - } + if "" != previousID && util.InvalidIDPattern(parentID, ret) { + return } } if nil != arg["nextID"] { nextID = arg["nextID"].(string) - if "" != nextID { - if util.InvalidIDPattern(nextID, ret) { - return - } + if "" != nextID && util.InvalidIDPattern(parentID, ret) { + return } } 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/av/sort.go b/kernel/av/sort.go index 681ea7fc7..ad6b6d1a4 100644 --- a/kernel/av/sort.go +++ b/kernel/av/sort.go @@ -48,13 +48,21 @@ func (value *Value) Compare(other *Value, attrView *AttributeView) int { } case KeyTypeText: if nil != value.Text && nil != other.Text { + if "" == value.Text.Content { + if "" == other.Text.Content { + return 0 + } + return 1 + } else if "" == other.Text.Content { + return -1 + } return strings.Compare(value.Text.Content, other.Text.Content) } case KeyTypeNumber: if nil != value.Number && nil != other.Number { if value.Number.IsNotEmpty { if !other.Number.IsNotEmpty { - return 1 + return -1 } if value.Number.Content > other.Number.Content { @@ -65,17 +73,17 @@ func (value *Value) Compare(other *Value, attrView *AttributeView) int { } return 0 } else { - if other.Number.IsNotEmpty { - return -1 + if !other.Number.IsNotEmpty { + return 0 } - return 0 + return 1 } } case KeyTypeDate: if nil != value.Date && nil != other.Date { if value.Date.IsNotEmpty { if !other.Date.IsNotEmpty { - return 1 + return -1 } if value.Date.Content > other.Date.Content { return 1 @@ -85,10 +93,10 @@ func (value *Value) Compare(other *Value, attrView *AttributeView) int { } return 0 } else { - if other.Date.IsNotEmpty { - return -1 + if !other.Date.IsNotEmpty { + return 0 } - return 0 + return 1 } } case KeyTypeCreated: @@ -140,14 +148,38 @@ func (value *Value) Compare(other *Value, attrView *AttributeView) int { } case KeyTypeURL: if nil != value.URL && nil != other.URL { + if "" == value.URL.Content { + if "" == other.URL.Content { + return 0 + } + return 1 + } else if "" == other.URL.Content { + return -1 + } return strings.Compare(value.URL.Content, other.URL.Content) } case KeyTypeEmail: if nil != value.Email && nil != other.Email { + if "" == value.Email.Content { + if "" == other.Email.Content { + return 0 + } + return 1 + } else if "" == other.Email.Content { + return -1 + } return strings.Compare(value.Email.Content, other.Email.Content) } case KeyTypePhone: if nil != value.Phone && nil != other.Phone { + if "" == value.Phone.Content { + if "" == other.Phone.Content { + return 0 + } + return 1 + } else if "" == other.Phone.Content { + return -1 + } return strings.Compare(value.Phone.Content, other.Phone.Content) } case KeyTypeMAsset: diff --git a/kernel/av/table.go b/kernel/av/table.go index 0930b01eb..9d7ef8f99 100644 --- a/kernel/av/table.go +++ b/kernel/av/table.go @@ -173,25 +173,25 @@ func (table *Table) SortRows(attrView *AttributeView) { } } - includeUneditedRows := map[string]bool{} + editedValRows := map[string]bool{} for i, row := range table.Rows { for _, colIndexSort := range colIndexSorts { val := table.Rows[i].Cells[colIndexSort.Index].Value - if !val.IsEdited() { - // 如果该行的某个列的值是未编辑的,则该行不参与排序 - includeUneditedRows[row.ID] = true + if val.IsEdited() { + // 如果该行某列的值已经编辑过,则该行可参与排序 + editedValRows[row.ID] = true break } } } - // 将包含未编辑的行和全部已编辑的行分开排序 + // 将未编辑的行和已编辑的行分开排序 var uneditedRows, editedRows []*TableRow for _, row := range table.Rows { - if _, ok := includeUneditedRows[row.ID]; ok { - uneditedRows = append(uneditedRows, row) - } else { + if _, ok := editedValRows[row.ID]; ok { editedRows = append(editedRows, row) + } else { + uneditedRows = append(uneditedRows, row) } } diff --git a/kernel/model/outline.go b/kernel/model/outline.go index ea8ba9a0d..2f367c33e 100644 --- a/kernel/model/outline.go +++ b/kernel/model/outline.go @@ -26,6 +26,121 @@ 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 { + if child.ID == headingID { + break + } + targetNode = child + } + } + + if targetNode == heading.Previous { + if previousHeading.HeadingLevel >= heading.HeadingLevel { + return + } + } + + 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 + if 6 < child.HeadingLevel { + child.HeadingLevel = 6 + } + } + 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 { + if child.ID == headingID { + break + } + targetNode = child + } + } + + if targetNode == heading.Previous { + if parentHeading.HeadingLevel < heading.HeadingLevel { + return + } + } + + diffLevel := 1 + 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 + if 6 < child.HeadingLevel { + child.HeadingLevel = 6 + } + } + 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":