2023-06-24 20:39:55 +08:00
|
|
|
// SiYuan - Refactor your thinking
|
2022-05-26 15:18:53 +08:00
|
|
|
// 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 <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
2024-01-11 11:11:52 +08:00
|
|
|
"errors"
|
2022-05-26 15:18:53 +08:00
|
|
|
"net/http"
|
|
|
|
|
|
|
|
"github.com/88250/gulu"
|
|
|
|
"github.com/88250/lute"
|
|
|
|
"github.com/88250/lute/ast"
|
2025-03-11 11:45:24 +08:00
|
|
|
"github.com/88250/lute/parse"
|
2022-05-26 15:18:53 +08:00
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/siyuan-note/siyuan/kernel/model"
|
2023-02-10 14:28:10 +08:00
|
|
|
"github.com/siyuan-note/siyuan/kernel/treenode"
|
2022-05-26 15:18:53 +08:00
|
|
|
"github.com/siyuan-note/siyuan/kernel/util"
|
|
|
|
)
|
|
|
|
|
2024-03-27 22:15:52 +08:00
|
|
|
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)
|
2024-10-22 19:20:44 +08:00
|
|
|
model.FlushTxQueue()
|
2024-03-27 22:15:52 +08:00
|
|
|
|
|
|
|
ret.Data = transactions
|
|
|
|
broadcastTransactions(transactions)
|
|
|
|
}
|
|
|
|
|
2024-02-18 15:03:20 +08:00
|
|
|
func appendDailyNoteBlock(c *gin.Context) {
|
|
|
|
ret := gulu.Ret.NewResult()
|
|
|
|
defer c.JSON(http.StatusOK, ret)
|
|
|
|
|
|
|
|
arg, ok := util.JsonArg(c, ret)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
data := arg["data"].(string)
|
|
|
|
dataType := arg["dataType"].(string)
|
|
|
|
boxID := arg["notebook"].(string)
|
|
|
|
if util.InvalidIDPattern(boxID, ret) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if "markdown" == dataType {
|
|
|
|
luteEngine := util.NewLute()
|
|
|
|
var err error
|
|
|
|
data, err = dataBlockDOM(data, luteEngine)
|
2024-09-04 04:40:50 +03:00
|
|
|
if err != nil {
|
2024-02-18 15:03:20 +08:00
|
|
|
ret.Code = -1
|
|
|
|
ret.Msg = "data block DOM failed: " + err.Error()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
p, _, err := model.CreateDailyNote(boxID)
|
2024-09-04 04:40:50 +03:00
|
|
|
if err != nil {
|
2024-02-18 15:03:20 +08:00
|
|
|
ret.Code = -1
|
|
|
|
ret.Msg = "create daily note failed: " + err.Error()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-11-29 08:41:43 +08:00
|
|
|
parentID := util.GetTreeID(p)
|
2024-02-18 15:03:20 +08:00
|
|
|
transactions := []*model.Transaction{
|
|
|
|
{
|
|
|
|
DoOperations: []*model.Operation{
|
|
|
|
{
|
|
|
|
Action: "appendInsert",
|
|
|
|
Data: data,
|
|
|
|
ParentID: parentID,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
model.PerformTransactions(&transactions)
|
2024-10-22 19:20:44 +08:00
|
|
|
model.FlushTxQueue()
|
2024-02-18 15:03:20 +08:00
|
|
|
|
|
|
|
ret.Data = transactions
|
|
|
|
broadcastTransactions(transactions)
|
|
|
|
}
|
|
|
|
|
2024-05-17 17:32:57 +08:00
|
|
|
func prependDailyNoteBlock(c *gin.Context) {
|
|
|
|
ret := gulu.Ret.NewResult()
|
|
|
|
defer c.JSON(http.StatusOK, ret)
|
|
|
|
|
|
|
|
arg, ok := util.JsonArg(c, ret)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
data := arg["data"].(string)
|
|
|
|
dataType := arg["dataType"].(string)
|
|
|
|
boxID := arg["notebook"].(string)
|
|
|
|
if util.InvalidIDPattern(boxID, ret) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if "markdown" == dataType {
|
|
|
|
luteEngine := util.NewLute()
|
|
|
|
var err error
|
|
|
|
data, err = dataBlockDOM(data, luteEngine)
|
2024-09-04 04:40:50 +03:00
|
|
|
if err != nil {
|
2024-05-17 17:32:57 +08:00
|
|
|
ret.Code = -1
|
|
|
|
ret.Msg = "data block DOM failed: " + err.Error()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
p, _, err := model.CreateDailyNote(boxID)
|
2024-09-04 04:40:50 +03:00
|
|
|
if err != nil {
|
2024-05-17 17:32:57 +08:00
|
|
|
ret.Code = -1
|
|
|
|
ret.Msg = "create daily note failed: " + err.Error()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-11-29 08:41:43 +08:00
|
|
|
parentID := util.GetTreeID(p)
|
2024-05-17 17:32:57 +08:00
|
|
|
transactions := []*model.Transaction{
|
|
|
|
{
|
|
|
|
DoOperations: []*model.Operation{
|
|
|
|
{
|
|
|
|
Action: "prependInsert",
|
|
|
|
Data: data,
|
|
|
|
ParentID: parentID,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
model.PerformTransactions(&transactions)
|
2024-10-22 19:20:44 +08:00
|
|
|
model.FlushTxQueue()
|
2024-05-17 17:32:57 +08:00
|
|
|
|
|
|
|
ret.Data = transactions
|
|
|
|
broadcastTransactions(transactions)
|
|
|
|
}
|
|
|
|
|
2023-12-24 16:19:52 +08:00
|
|
|
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 {
|
2024-03-24 22:16:49 +08:00
|
|
|
data, _ := gulu.JSON.MarshalJSON(map[string]interface{}{"fold": ""})
|
2023-12-24 16:19:52 +08:00
|
|
|
transactions = []*model.Transaction{
|
|
|
|
{
|
|
|
|
DoOperations: []*model.Operation{
|
|
|
|
{
|
|
|
|
Action: "setAttrs",
|
|
|
|
ID: id,
|
|
|
|
Data: string(data),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
model.PerformTransactions(&transactions)
|
2024-10-22 19:20:44 +08:00
|
|
|
model.FlushTxQueue()
|
2023-12-24 16:19:52 +08:00
|
|
|
|
|
|
|
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)
|
2024-10-22 19:20:44 +08:00
|
|
|
model.FlushTxQueue()
|
2023-12-24 16:19:52 +08:00
|
|
|
|
|
|
|
broadcastTransactions(transactions)
|
|
|
|
}
|
|
|
|
|
2023-04-06 18:22:41 +08:00
|
|
|
func moveBlock(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
|
|
|
|
}
|
|
|
|
|
2025-04-13 18:36:25 +08:00
|
|
|
currentBt := treenode.GetBlockTree(id)
|
|
|
|
if nil == currentBt {
|
|
|
|
ret.Code = -1
|
|
|
|
ret.Msg = "block not found [id=" + id + "]"
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-04-06 18:22:41 +08:00
|
|
|
var parentID, previousID string
|
|
|
|
if nil != arg["parentID"] {
|
|
|
|
parentID = arg["parentID"].(string)
|
2024-03-27 20:42:09 +08:00
|
|
|
if "" != parentID && util.InvalidIDPattern(parentID, ret) {
|
2023-04-06 18:22:41 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if nil != arg["previousID"] {
|
|
|
|
previousID = arg["previousID"].(string)
|
2024-03-27 20:42:09 +08:00
|
|
|
if "" != previousID && util.InvalidIDPattern(previousID, ret) {
|
2023-04-06 18:22:41 +08:00
|
|
|
return
|
|
|
|
}
|
2023-04-17 12:18:28 +08:00
|
|
|
|
|
|
|
// Check the validity of the API `moveBlock` parameter `previousID` https://github.com/siyuan-note/siyuan/issues/8007
|
|
|
|
if bt := treenode.GetBlockTree(previousID); nil == bt || "d" == bt.Type {
|
|
|
|
ret.Code = -1
|
|
|
|
ret.Msg = "`previousID` can not be the ID of a document"
|
|
|
|
return
|
|
|
|
}
|
2023-04-06 18:22:41 +08:00
|
|
|
}
|
|
|
|
|
2025-04-13 18:36:25 +08:00
|
|
|
var targetBt *treenode.BlockTree
|
|
|
|
if "" != previousID {
|
|
|
|
targetBt = treenode.GetBlockTree(previousID)
|
|
|
|
} else if "" != parentID {
|
|
|
|
targetBt = treenode.GetBlockTree(parentID)
|
|
|
|
}
|
|
|
|
|
|
|
|
if nil == targetBt {
|
|
|
|
ret.Code = -1
|
|
|
|
ret.Msg = "target block not found [id=" + parentID + "]"
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-04-06 18:22:41 +08:00
|
|
|
transactions := []*model.Transaction{
|
|
|
|
{
|
|
|
|
DoOperations: []*model.Operation{
|
|
|
|
{
|
|
|
|
Action: "move",
|
|
|
|
ID: id,
|
|
|
|
PreviousID: previousID,
|
|
|
|
ParentID: parentID,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
model.PerformTransactions(&transactions)
|
2024-10-22 19:20:44 +08:00
|
|
|
model.FlushTxQueue()
|
2023-04-06 18:22:41 +08:00
|
|
|
|
2025-04-13 18:36:25 +08:00
|
|
|
model.ReloadProtyle(currentBt.RootID)
|
|
|
|
if currentBt.RootID != targetBt.RootID {
|
|
|
|
model.ReloadProtyle(targetBt.RootID)
|
|
|
|
}
|
2023-04-06 18:22:41 +08:00
|
|
|
}
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
func appendBlock(c *gin.Context) {
|
|
|
|
ret := gulu.Ret.NewResult()
|
|
|
|
defer c.JSON(http.StatusOK, ret)
|
|
|
|
|
|
|
|
arg, ok := util.JsonArg(c, ret)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
data := arg["data"].(string)
|
|
|
|
dataType := arg["dataType"].(string)
|
|
|
|
parentID := arg["parentID"].(string)
|
2023-02-02 11:06:29 +08:00
|
|
|
if util.InvalidIDPattern(parentID, ret) {
|
|
|
|
return
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
if "markdown" == dataType {
|
2023-02-10 14:28:10 +08:00
|
|
|
luteEngine := util.NewLute()
|
2024-01-11 11:11:52 +08:00
|
|
|
var err error
|
|
|
|
data, err = dataBlockDOM(data, luteEngine)
|
2024-09-04 04:40:50 +03:00
|
|
|
if err != nil {
|
2024-01-11 11:11:52 +08:00
|
|
|
ret.Code = -1
|
|
|
|
ret.Msg = "data block DOM failed: " + err.Error()
|
|
|
|
return
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
transactions := []*model.Transaction{
|
|
|
|
{
|
|
|
|
DoOperations: []*model.Operation{
|
|
|
|
{
|
|
|
|
Action: "appendInsert",
|
|
|
|
Data: data,
|
|
|
|
ParentID: parentID,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2023-03-19 00:12:28 +08:00
|
|
|
model.PerformTransactions(&transactions)
|
2024-10-22 19:20:44 +08:00
|
|
|
model.FlushTxQueue()
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
|
|
ret.Data = transactions
|
|
|
|
broadcastTransactions(transactions)
|
|
|
|
}
|
|
|
|
|
|
|
|
func prependBlock(c *gin.Context) {
|
|
|
|
ret := gulu.Ret.NewResult()
|
|
|
|
defer c.JSON(http.StatusOK, ret)
|
|
|
|
|
|
|
|
arg, ok := util.JsonArg(c, ret)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
data := arg["data"].(string)
|
|
|
|
dataType := arg["dataType"].(string)
|
|
|
|
parentID := arg["parentID"].(string)
|
2023-02-02 11:06:29 +08:00
|
|
|
if util.InvalidIDPattern(parentID, ret) {
|
|
|
|
return
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
if "markdown" == dataType {
|
2023-02-10 14:28:10 +08:00
|
|
|
luteEngine := util.NewLute()
|
2024-01-11 11:11:52 +08:00
|
|
|
var err error
|
|
|
|
data, err = dataBlockDOM(data, luteEngine)
|
2024-09-04 04:40:50 +03:00
|
|
|
if err != nil {
|
2024-01-11 11:11:52 +08:00
|
|
|
ret.Code = -1
|
|
|
|
ret.Msg = "data block DOM failed: " + err.Error()
|
|
|
|
return
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
transactions := []*model.Transaction{
|
|
|
|
{
|
|
|
|
DoOperations: []*model.Operation{
|
|
|
|
{
|
|
|
|
Action: "prependInsert",
|
|
|
|
Data: data,
|
|
|
|
ParentID: parentID,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2023-03-19 00:12:28 +08:00
|
|
|
model.PerformTransactions(&transactions)
|
2024-10-22 19:20:44 +08:00
|
|
|
model.FlushTxQueue()
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
|
|
ret.Data = transactions
|
|
|
|
broadcastTransactions(transactions)
|
|
|
|
}
|
|
|
|
|
|
|
|
func insertBlock(c *gin.Context) {
|
|
|
|
ret := gulu.Ret.NewResult()
|
|
|
|
defer c.JSON(http.StatusOK, ret)
|
|
|
|
|
|
|
|
arg, ok := util.JsonArg(c, ret)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
data := arg["data"].(string)
|
|
|
|
dataType := arg["dataType"].(string)
|
2022-10-14 16:46:15 +08:00
|
|
|
var parentID, previousID, nextID string
|
2022-05-26 15:18:53 +08:00
|
|
|
if nil != arg["parentID"] {
|
|
|
|
parentID = arg["parentID"].(string)
|
2024-03-27 20:42:09 +08:00
|
|
|
if "" != parentID && util.InvalidIDPattern(parentID, ret) {
|
|
|
|
return
|
2023-02-02 11:06:29 +08:00
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
}
|
|
|
|
if nil != arg["previousID"] {
|
|
|
|
previousID = arg["previousID"].(string)
|
2024-03-29 11:55:21 +08:00
|
|
|
if "" != previousID && util.InvalidIDPattern(previousID, ret) {
|
2024-03-27 20:42:09 +08:00
|
|
|
return
|
2023-02-02 11:06:29 +08:00
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
}
|
2022-10-14 16:46:15 +08:00
|
|
|
if nil != arg["nextID"] {
|
|
|
|
nextID = arg["nextID"].(string)
|
2024-03-29 11:55:21 +08:00
|
|
|
if "" != nextID && util.InvalidIDPattern(nextID, ret) {
|
2024-03-27 20:42:09 +08:00
|
|
|
return
|
2023-02-02 11:06:29 +08:00
|
|
|
}
|
2022-10-14 16:46:15 +08:00
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
|
|
if "markdown" == dataType {
|
2023-02-10 14:28:10 +08:00
|
|
|
luteEngine := util.NewLute()
|
2024-01-11 11:11:52 +08:00
|
|
|
var err error
|
|
|
|
data, err = dataBlockDOM(data, luteEngine)
|
2024-09-04 04:40:50 +03:00
|
|
|
if err != nil {
|
2024-01-11 11:11:52 +08:00
|
|
|
ret.Code = -1
|
|
|
|
ret.Msg = "data block DOM failed: " + err.Error()
|
|
|
|
return
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
transactions := []*model.Transaction{
|
|
|
|
{
|
|
|
|
DoOperations: []*model.Operation{
|
|
|
|
{
|
|
|
|
Action: "insert",
|
|
|
|
Data: data,
|
|
|
|
ParentID: parentID,
|
|
|
|
PreviousID: previousID,
|
2022-10-14 16:46:15 +08:00
|
|
|
NextID: nextID,
|
2022-05-26 15:18:53 +08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2023-03-19 00:12:28 +08:00
|
|
|
model.PerformTransactions(&transactions)
|
2024-10-22 19:20:44 +08:00
|
|
|
model.FlushTxQueue()
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
|
|
ret.Data = transactions
|
|
|
|
broadcastTransactions(transactions)
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateBlock(c *gin.Context) {
|
|
|
|
ret := gulu.Ret.NewResult()
|
|
|
|
defer c.JSON(http.StatusOK, ret)
|
|
|
|
|
|
|
|
arg, ok := util.JsonArg(c, ret)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
data := arg["data"].(string)
|
|
|
|
dataType := arg["dataType"].(string)
|
|
|
|
id := arg["id"].(string)
|
2023-02-02 11:06:29 +08:00
|
|
|
if util.InvalidIDPattern(id, ret) {
|
|
|
|
return
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
2023-02-10 14:28:10 +08:00
|
|
|
luteEngine := util.NewLute()
|
2022-05-26 15:18:53 +08:00
|
|
|
if "markdown" == dataType {
|
2024-01-11 11:11:52 +08:00
|
|
|
var err error
|
|
|
|
data, err = dataBlockDOM(data, luteEngine)
|
2024-09-04 04:40:50 +03:00
|
|
|
if err != nil {
|
2024-01-11 11:11:52 +08:00
|
|
|
ret.Code = -1
|
|
|
|
ret.Msg = "data block DOM failed: " + err.Error()
|
|
|
|
return
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
}
|
|
|
|
tree := luteEngine.BlockDOM2Tree(data)
|
|
|
|
if nil == tree || nil == tree.Root || nil == tree.Root.FirstChild {
|
|
|
|
ret.Code = -1
|
|
|
|
ret.Msg = "parse tree failed"
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-07-16 00:05:51 +08:00
|
|
|
oldTree, err := model.LoadTreeByBlockID(id)
|
2024-09-04 04:40:50 +03:00
|
|
|
if err != nil {
|
2022-05-26 15:18:53 +08:00
|
|
|
ret.Code = -1
|
2025-07-16 00:05:51 +08:00
|
|
|
ret.Msg = "load tree failed: " + err.Error()
|
2022-05-26 15:18:53 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-07-16 00:05:51 +08:00
|
|
|
node := treenode.GetNodeInTree(oldTree, id)
|
|
|
|
if nil == node {
|
|
|
|
ret.Code = -1
|
|
|
|
ret.Msg = "block not found [id=" + id + "]"
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if ast.NodeDocument == node.Type {
|
2022-05-26 15:18:53 +08:00
|
|
|
var toRemoves []*ast.Node
|
|
|
|
for n := oldTree.Root.FirstChild; nil != n; n = n.Next {
|
|
|
|
toRemoves = append(toRemoves, n)
|
|
|
|
}
|
|
|
|
for _, n := range toRemoves {
|
|
|
|
n.Unlink()
|
|
|
|
}
|
2025-07-15 23:39:59 +08:00
|
|
|
|
|
|
|
var toAppends []*ast.Node
|
|
|
|
for n := tree.Root.FirstChild; nil != n; n = n.Next {
|
|
|
|
toAppends = append(toAppends, n)
|
|
|
|
}
|
|
|
|
for _, n := range toAppends {
|
|
|
|
oldTree.Root.AppendChild(n)
|
2022-05-26 15:18:53 +08:00
|
|
|
}
|
2025-07-16 00:05:51 +08:00
|
|
|
} else {
|
|
|
|
if ast.NodeListItem == node.Type && ast.NodeList == tree.Root.FirstChild.Type {
|
|
|
|
// 使用 API `api/block/updateBlock` 更新列表项时渲染错误 https://github.com/siyuan-note/siyuan/issues/4658
|
|
|
|
tree.Root.AppendChild(tree.Root.FirstChild.FirstChild) // 将列表下的第一个列表项移到文档结尾,移动以后根下面直接挂列表项,渲染器可以正常工作
|
|
|
|
tree.Root.FirstChild.Unlink() // 删除列表
|
|
|
|
tree.Root.FirstChild.Unlink() // 继续删除列表 IAL
|
|
|
|
}
|
|
|
|
tree.Root.FirstChild.SetIALAttr("id", id)
|
|
|
|
tree.Root.FirstChild.ID = id
|
|
|
|
node.InsertBefore(tree.Root.FirstChild)
|
|
|
|
node.Unlink()
|
2022-05-26 15:18:53 +08:00
|
|
|
}
|
|
|
|
|
2025-07-16 00:16:04 +08:00
|
|
|
if err = model.WriteTreeUpsertQueue(oldTree); err != nil {
|
|
|
|
ret.Code = -1
|
|
|
|
ret.Msg = "write tree upsert queue failed: " + err.Error()
|
|
|
|
return
|
|
|
|
}
|
2025-07-16 00:05:51 +08:00
|
|
|
model.ReloadProtyle(oldTree.ID)
|
2022-05-26 15:18:53 +08:00
|
|
|
}
|
|
|
|
|
2025-03-11 11:45:24 +08:00
|
|
|
func batchUpdateBlock(c *gin.Context) {
|
|
|
|
ret := gulu.Ret.NewResult()
|
|
|
|
defer c.JSON(http.StatusOK, ret)
|
|
|
|
|
|
|
|
arg, ok := util.JsonArg(c, ret)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
blocksArg := arg["blocks"].([]interface{})
|
2025-07-16 00:05:51 +08:00
|
|
|
blocks := map[string]*parse.Tree{}
|
2025-03-11 11:45:24 +08:00
|
|
|
luteEngine := util.NewLute()
|
|
|
|
for _, blockArg := range blocksArg {
|
|
|
|
blockMap := blockArg.(map[string]interface{})
|
|
|
|
id := blockMap["id"].(string)
|
|
|
|
if util.InvalidIDPattern(id, ret) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
data := blockMap["data"].(string)
|
|
|
|
dataType := blockMap["dataType"].(string)
|
|
|
|
if "markdown" == dataType {
|
|
|
|
var err error
|
|
|
|
data, err = dataBlockDOM(data, luteEngine)
|
|
|
|
if err != nil {
|
|
|
|
ret.Code = -1
|
|
|
|
ret.Msg = "data block DOM failed: " + err.Error()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tree := luteEngine.BlockDOM2Tree(data)
|
|
|
|
if nil == tree || nil == tree.Root || nil == tree.Root.FirstChild {
|
|
|
|
ret.Code = -1
|
|
|
|
ret.Msg = "parse tree failed"
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-07-16 00:05:51 +08:00
|
|
|
blocks[id] = tree
|
|
|
|
}
|
|
|
|
|
|
|
|
trees := map[string]*parse.Tree{}
|
|
|
|
for id, tree := range blocks {
|
|
|
|
oldTree, err := model.LoadTreeWithCache(id, &trees)
|
2025-03-11 11:45:24 +08:00
|
|
|
if err != nil {
|
|
|
|
ret.Code = -1
|
2025-07-16 00:05:51 +08:00
|
|
|
ret.Msg = "load tree failed: " + err.Error()
|
2025-03-11 11:45:24 +08:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-07-16 00:05:51 +08:00
|
|
|
node := treenode.GetNodeInTree(oldTree, id)
|
|
|
|
if nil == node {
|
|
|
|
ret.Code = -1
|
|
|
|
ret.Msg = "block not found [id=" + id + "]"
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if ast.NodeDocument == node.Type {
|
2025-03-11 11:45:24 +08:00
|
|
|
var toRemoves []*ast.Node
|
|
|
|
for n := oldTree.Root.FirstChild; nil != n; n = n.Next {
|
|
|
|
toRemoves = append(toRemoves, n)
|
|
|
|
}
|
|
|
|
for _, n := range toRemoves {
|
|
|
|
n.Unlink()
|
|
|
|
}
|
2025-07-15 23:39:59 +08:00
|
|
|
var toAppends []*ast.Node
|
|
|
|
for n := tree.Root.FirstChild; nil != n; n = n.Next {
|
|
|
|
toAppends = append(toAppends, n)
|
|
|
|
}
|
|
|
|
for _, n := range toAppends {
|
|
|
|
oldTree.Root.AppendChild(n)
|
|
|
|
}
|
2025-03-11 11:45:24 +08:00
|
|
|
} else {
|
2025-07-16 00:05:51 +08:00
|
|
|
if ast.NodeListItem == node.Type && ast.NodeList == tree.Root.FirstChild.Type {
|
2025-03-11 11:45:24 +08:00
|
|
|
// 使用 API `api/block/updateBlock` 更新列表项时渲染错误 https://github.com/siyuan-note/siyuan/issues/4658
|
|
|
|
tree.Root.AppendChild(tree.Root.FirstChild.FirstChild) // 将列表下的第一个列表项移到文档结尾,移动以后根下面直接挂列表项,渲染器可以正常工作
|
|
|
|
tree.Root.FirstChild.Unlink() // 删除列表
|
|
|
|
tree.Root.FirstChild.Unlink() // 继续删除列表 IAL
|
|
|
|
}
|
|
|
|
tree.Root.FirstChild.SetIALAttr("id", id)
|
2025-07-16 00:05:51 +08:00
|
|
|
tree.Root.FirstChild.ID = id
|
|
|
|
node.InsertBefore(tree.Root.FirstChild)
|
|
|
|
node.Unlink()
|
2025-03-11 11:45:24 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-07-16 00:05:51 +08:00
|
|
|
for _, tree := range trees {
|
2025-07-16 00:16:04 +08:00
|
|
|
if err := model.WriteTreeUpsertQueue(tree); nil != err {
|
|
|
|
ret.Code = -1
|
|
|
|
ret.Msg = "write tree upsert queue failed: " + err.Error()
|
|
|
|
return
|
|
|
|
}
|
2025-07-16 00:05:51 +08:00
|
|
|
model.ReloadProtyle(tree.ID)
|
2025-07-15 23:39:59 +08:00
|
|
|
}
|
2025-03-11 11:45:24 +08:00
|
|
|
}
|
|
|
|
|
2022-05-26 15:18:53 +08:00
|
|
|
func deleteBlock(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)
|
2023-02-02 11:06:29 +08:00
|
|
|
if util.InvalidIDPattern(id, ret) {
|
|
|
|
return
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
|
|
transactions := []*model.Transaction{
|
|
|
|
{
|
|
|
|
DoOperations: []*model.Operation{
|
|
|
|
{
|
|
|
|
Action: "delete",
|
|
|
|
ID: id,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2023-03-19 00:12:28 +08:00
|
|
|
model.PerformTransactions(&transactions)
|
2022-05-26 15:18:53 +08:00
|
|
|
|
|
|
|
ret.Data = transactions
|
|
|
|
broadcastTransactions(transactions)
|
|
|
|
}
|
|
|
|
|
|
|
|
func broadcastTransactions(transactions []*model.Transaction) {
|
2023-01-01 14:09:36 +08:00
|
|
|
evt := util.NewCmdResult("transactions", 0, util.PushModeBroadcast)
|
2022-05-26 15:18:53 +08:00
|
|
|
evt.Data = transactions
|
|
|
|
util.PushEvent(evt)
|
|
|
|
}
|
|
|
|
|
2024-01-11 11:11:52 +08:00
|
|
|
func dataBlockDOM(data string, luteEngine *lute.Lute) (ret string, err error) {
|
2022-10-01 21:41:59 +08:00
|
|
|
luteEngine.SetHTMLTag2TextMark(true) // API `/api/block/**` 无法使用 `<u>foo</u>` 与 `<kbd>bar</kbd>` 插入/更新行内元素 https://github.com/siyuan-note/siyuan/issues/6039
|
|
|
|
|
2024-01-12 19:58:44 +08:00
|
|
|
ret, tree := luteEngine.Md2BlockDOMTree(data, true)
|
2022-05-26 15:18:53 +08:00
|
|
|
if "" == ret {
|
|
|
|
// 使用 API 插入空字符串出现错误 https://github.com/siyuan-note/siyuan/issues/3931
|
2024-11-10 12:04:56 +08:00
|
|
|
blankParagraph := treenode.NewParagraph("")
|
2023-06-02 22:36:27 +08:00
|
|
|
ret = luteEngine.RenderNodeBlockDOM(blankParagraph)
|
2022-05-26 15:18:53 +08:00
|
|
|
}
|
2024-01-11 11:11:52 +08:00
|
|
|
|
|
|
|
invalidID := ""
|
|
|
|
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
|
|
|
|
if !entering {
|
|
|
|
return ast.WalkContinue
|
|
|
|
}
|
|
|
|
|
|
|
|
if "" != n.ID {
|
|
|
|
if !ast.IsNodeIDPattern(n.ID) {
|
|
|
|
invalidID = n.ID
|
|
|
|
return ast.WalkStop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ast.WalkContinue
|
|
|
|
})
|
|
|
|
|
|
|
|
if "" != invalidID {
|
|
|
|
err = errors.New("found invalid ID [" + invalidID + "]")
|
|
|
|
ret = ""
|
|
|
|
return
|
|
|
|
}
|
2022-05-26 15:18:53 +08:00
|
|
|
return
|
|
|
|
}
|