浏览代码

:zap: 改进多个功能点的性能 Fix https://github.com/siyuan-note/siyuan/issues/7177

Liang Ding 2 年之前
父节点
当前提交
04f47749f7

文件差异内容过多而无法显示
+ 0 - 0
app/stage/protyle/js/lute/lute.min.js


+ 2 - 2
kernel/api/block_op.go

@@ -17,12 +17,12 @@
 package api
 package api
 
 
 import (
 import (
+	"github.com/siyuan-note/siyuan/kernel/treenode"
 	"net/http"
 	"net/http"
 
 
 	"github.com/88250/gulu"
 	"github.com/88250/gulu"
 	"github.com/88250/lute"
 	"github.com/88250/lute"
 	"github.com/88250/lute/ast"
 	"github.com/88250/lute/ast"
-	"github.com/88250/lute/parse"
 	"github.com/gin-gonic/gin"
 	"github.com/gin-gonic/gin"
 	"github.com/siyuan-note/siyuan/kernel/model"
 	"github.com/siyuan-note/siyuan/kernel/model"
 	"github.com/siyuan-note/siyuan/kernel/util"
 	"github.com/siyuan-note/siyuan/kernel/util"
@@ -299,7 +299,7 @@ func dataBlockDOM(data string, luteEngine *lute.Lute) (ret string) {
 	ret = luteEngine.Md2BlockDOM(data, true)
 	ret = luteEngine.Md2BlockDOM(data, true)
 	if "" == ret {
 	if "" == ret {
 		// 使用 API 插入空字符串出现错误 https://github.com/siyuan-note/siyuan/issues/3931
 		// 使用 API 插入空字符串出现错误 https://github.com/siyuan-note/siyuan/issues/3931
-		blankParagraph := parse.NewParagraph()
+		blankParagraph := treenode.NewParagraph()
 		ret = lute.RenderNodeBlockDOM(blankParagraph, luteEngine.ParseOptions, luteEngine.RenderOptions)
 		ret = lute.RenderNodeBlockDOM(blankParagraph, luteEngine.ParseOptions, luteEngine.RenderOptions)
 	}
 	}
 	return
 	return

+ 2 - 1
kernel/api/lute.go

@@ -17,6 +17,7 @@
 package api
 package api
 
 
 import (
 import (
+	"github.com/siyuan-note/siyuan/kernel/treenode"
 	"net/http"
 	"net/http"
 	"path/filepath"
 	"path/filepath"
 	"strings"
 	"strings"
@@ -70,7 +71,7 @@ func html2BlockDOM(c *gin.Context) {
 		}
 		}
 
 
 		if ast.NodeListItem == n.Type && nil == n.FirstChild {
 		if ast.NodeListItem == n.Type && nil == n.FirstChild {
-			newNode := parse.NewParagraph()
+			newNode := treenode.NewParagraph()
 			n.AppendChild(newNode)
 			n.AppendChild(newNode)
 			n.SetIALAttr("updated", util.TimeFromID(newNode.ID))
 			n.SetIALAttr("updated", util.TimeFromID(newNode.ID))
 			return ast.WalkSkipChildren
 			return ast.WalkSkipChildren

+ 245 - 0
kernel/filesys/json_parser.go

@@ -0,0 +1,245 @@
+// Lute - 一款结构化的 Markdown 引擎,支持 Go 和 JavaScript
+// Copyright (c) 2019-present, b3log.org
+//
+// Lute is licensed under Mulan PSL v2.
+// You can use this software according to the terms and conditions of the Mulan PSL v2.
+// You may obtain a copy of Mulan PSL v2 at:
+//         http://license.coscl.org.cn/MulanPSL2
+// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
+// See the Mulan PSL v2 for more details.
+
+package filesys
+
+import (
+	"bytes"
+	"strings"
+
+	"github.com/88250/lute/ast"
+	"github.com/88250/lute/editor"
+	"github.com/88250/lute/parse"
+	"github.com/88250/lute/util"
+	"github.com/goccy/go-json"
+	"github.com/siyuan-note/siyuan/kernel/treenode"
+)
+
+func ParseJSONWithoutFix(jsonData []byte, options *parse.Options) (ret *parse.Tree, err error) {
+	root := &ast.Node{}
+	err = json.Unmarshal(jsonData, root)
+	if nil != err {
+		return
+	}
+
+	ret = &parse.Tree{Name: "", ID: root.ID, Root: &ast.Node{Type: ast.NodeDocument, ID: root.ID, Spec: root.Spec}, Context: &parse.Context{ParseOption: options}}
+	ret.Root.KramdownIAL = parse.Map2IAL(root.Properties)
+	ret.Context.Tip = ret.Root
+	if nil == root.Children {
+		return
+	}
+
+	idMap := map[string]bool{}
+	for _, child := range root.Children {
+		genTreeByJSON(child, ret, &idMap, nil, nil, true)
+	}
+	return
+}
+
+func ParseJSON(jsonData []byte, options *parse.Options) (ret *parse.Tree, needFix bool, err error) {
+	root := &ast.Node{}
+	err = json.Unmarshal(jsonData, root)
+	if nil != err {
+		return
+	}
+
+	ret = &parse.Tree{Name: "", ID: root.ID, Root: &ast.Node{Type: ast.NodeDocument, ID: root.ID, Spec: root.Spec}, Context: &parse.Context{ParseOption: options}}
+	ret.Root.KramdownIAL = parse.Map2IAL(root.Properties)
+	for _, kv := range ret.Root.KramdownIAL {
+		if strings.Contains(kv[1], "\n") {
+			val := kv[1]
+			val = strings.ReplaceAll(val, "\n", editor.IALValEscNewLine)
+			ret.Root.SetIALAttr(kv[0], val)
+			needFix = true
+		}
+	}
+
+	ret.Context.Tip = ret.Root
+	if nil == root.Children {
+		newPara := &ast.Node{Type: ast.NodeParagraph, ID: ast.NewNodeID()}
+		newPara.SetIALAttr("id", newPara.ID)
+		ret.Root.AppendChild(newPara)
+		needFix = true
+		return
+	}
+
+	needMigrate2Spec1 := false
+	idMap := map[string]bool{}
+	for _, child := range root.Children {
+		genTreeByJSON(child, ret, &idMap, &needFix, &needMigrate2Spec1, false)
+	}
+
+	if nil == ret.Root.FirstChild {
+		// 如果是空文档的话挂一个空段落上去
+		newP := treenode.NewParagraph()
+		ret.Root.AppendChild(newP)
+		ret.Root.SetIALAttr("updated", newP.ID[:14])
+	}
+
+	if needMigrate2Spec1 {
+		parse.NestedInlines2FlattedSpans(ret)
+		needFix = true
+	}
+	return
+}
+
+func genTreeByJSON(node *ast.Node, tree *parse.Tree, idMap *map[string]bool, needFix, needMigrate2Spec1 *bool, ignoreFix bool) {
+	node.Tokens, node.Type = util.StrToBytes(node.Data), ast.Str2NodeType(node.TypeStr)
+	node.Data, node.TypeStr = "", ""
+	node.KramdownIAL = parse.Map2IAL(node.Properties)
+	node.Properties = nil
+
+	if !ignoreFix {
+		// 历史数据订正
+
+		if -1 == node.Type {
+			*needFix = true
+			node.Type = ast.NodeParagraph
+			node.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: node.Tokens})
+			node.Children = nil
+		}
+
+		switch node.Type {
+		case ast.NodeList:
+			if 1 > len(node.Children) {
+				*needFix = true
+				return // 忽略空列表
+			}
+		case ast.NodeListItem:
+			if 1 > len(node.Children) {
+				*needFix = true
+				return // 忽略空列表项
+			}
+		case ast.NodeBlockquote:
+			if 2 > len(node.Children) {
+				*needFix = true
+				return // 忽略空引述
+			}
+		case ast.NodeSuperBlock:
+			if 4 > len(node.Children) {
+				*needFix = true
+				return // 忽略空超级块
+			}
+		case ast.NodeMathBlock:
+			if 1 > len(node.Children) {
+				*needFix = true
+				return // 忽略空公式
+			}
+		case ast.NodeBlockQueryEmbed:
+			if 1 > len(node.Children) {
+				*needFix = true
+				return // 忽略空查询嵌入块
+			}
+		}
+
+		fixLegacyData(tree.Context.Tip, node, idMap, needFix, needMigrate2Spec1)
+	}
+
+	tree.Context.Tip.AppendChild(node)
+	tree.Context.Tip = node
+	defer tree.Context.ParentTip()
+	if nil == node.Children {
+		return
+	}
+	for _, child := range node.Children {
+		genTreeByJSON(child, tree, idMap, needFix, needMigrate2Spec1, ignoreFix)
+	}
+	node.Children = nil
+}
+
+func fixLegacyData(tip, node *ast.Node, idMap *map[string]bool, needFix, needMigrate2Spec1 *bool) {
+	if node.IsBlock() {
+		if "" == node.ID {
+			node.ID = ast.NewNodeID()
+			node.SetIALAttr("id", node.ID)
+			*needFix = true
+		}
+		if 0 < len(node.Children) && ast.NodeBr.String() == node.Children[len(node.Children)-1].TypeStr {
+			// 剔除块尾多余的软换行 https://github.com/siyuan-note/siyuan/issues/6191
+			node.Children = node.Children[:len(node.Children)-1]
+			*needFix = true
+		}
+	}
+	if "" != node.ID {
+		if _, ok := (*idMap)[node.ID]; ok {
+			node.ID = ast.NewNodeID()
+			node.SetIALAttr("id", node.ID)
+			*needFix = true
+		}
+		(*idMap)[node.ID] = true
+	}
+
+	switch node.Type {
+	case ast.NodeIFrame:
+		if bytes.Contains(node.Tokens, util.StrToBytes("iframe-content")) {
+			start := bytes.Index(node.Tokens, util.StrToBytes("<iframe"))
+			end := bytes.Index(node.Tokens, util.StrToBytes("</iframe>"))
+			node.Tokens = node.Tokens[start : end+9]
+			*needFix = true
+		}
+	case ast.NodeWidget:
+		if bytes.Contains(node.Tokens, util.StrToBytes("http://127.0.0.1:6806")) {
+			node.Tokens = bytes.ReplaceAll(node.Tokens, []byte("http://127.0.0.1:6806"), nil)
+			*needFix = true
+		}
+	case ast.NodeList:
+		if nil != node.ListData && 3 != node.ListData.Typ && 0 < len(node.Children) &&
+			nil != node.Children[0].ListData && 3 == node.Children[0].ListData.Typ {
+			node.ListData.Typ = 3
+			*needFix = true
+		}
+	case ast.NodeMark:
+		if 3 == len(node.Children) && "NodeText" == node.Children[1].TypeStr {
+			if strings.HasPrefix(node.Children[1].Data, " ") || strings.HasSuffix(node.Children[1].Data, " ") {
+				node.Children[1].Data = strings.TrimSpace(node.Children[1].Data)
+				*needFix = true
+			}
+		}
+	case ast.NodeHeading:
+		if 6 < node.HeadingLevel {
+			node.HeadingLevel = 6
+			*needFix = true
+		}
+	case ast.NodeLinkDest:
+		if bytes.HasPrefix(node.Tokens, []byte("assets/")) && bytes.HasSuffix(node.Tokens, []byte(" ")) {
+			node.Tokens = bytes.TrimSpace(node.Tokens)
+			*needFix = true
+		}
+	case ast.NodeText:
+		if nil != tip.LastChild && ast.NodeTagOpenMarker == tip.LastChild.Type && 1 > len(node.Tokens) {
+			node.Tokens = []byte("Untitled")
+			*needFix = true
+		}
+	case ast.NodeTagCloseMarker:
+		if nil != tip.LastChild {
+			if ast.NodeTagOpenMarker == tip.LastChild.Type {
+				tip.AppendChild(&ast.Node{Type: ast.NodeText, Tokens: []byte("Untitled")})
+				*needFix = true
+			} else if "" == tip.LastChild.Text() {
+				tip.LastChild.Type = ast.NodeText
+				tip.LastChild.Tokens = []byte("Untitled")
+				*needFix = true
+			}
+		}
+	case ast.NodeBlockRef:
+		// 建立索引时无法解析 `v2.2.0-` 版本的块引用 https://github.com/siyuan-note/siyuan/issues/6889
+		// 早先的迁移程序有缺陷,漏迁移了块引用节点,这里检测到块引用节点后标识需要迁移
+		*needMigrate2Spec1 = true
+	}
+
+	for _, kv := range node.KramdownIAL {
+		if strings.Contains(kv[1], "\n") {
+			val := kv[1]
+			val = strings.ReplaceAll(val, "\n", editor.IALValEscNewLine)
+			node.SetIALAttr(kv[0], val)
+			*needFix = true
+		}
+	}
+}

+ 2 - 2
kernel/filesys/tree.go

@@ -151,7 +151,7 @@ func prepareWriteTree(tree *parse.Tree) (data []byte, filePath string, err error
 	luteEngine := util.NewLute() // 不关注用户的自定义解析渲染选项
 	luteEngine := util.NewLute() // 不关注用户的自定义解析渲染选项
 
 
 	if nil == tree.Root.FirstChild {
 	if nil == tree.Root.FirstChild {
-		newP := parse.NewParagraph()
+		newP := treenode.NewParagraph()
 		tree.Root.AppendChild(newP)
 		tree.Root.AppendChild(newP)
 		tree.Root.SetIALAttr("updated", util.TimeFromID(newP.ID))
 		tree.Root.SetIALAttr("updated", util.TimeFromID(newP.ID))
 		treenode.IndexBlockTree(tree)
 		treenode.IndexBlockTree(tree)
@@ -229,7 +229,7 @@ func recoverParseJSON2Tree(boxID, p, filePath string, luteEngine *lute.Lute) (re
 func parseJSON2Tree(boxID, p string, jsonData []byte, luteEngine *lute.Lute) (ret *parse.Tree) {
 func parseJSON2Tree(boxID, p string, jsonData []byte, luteEngine *lute.Lute) (ret *parse.Tree) {
 	var err error
 	var err error
 	var needFix bool
 	var needFix bool
-	ret, needFix, err = parse.ParseJSON(jsonData, luteEngine.ParseOptions)
+	ret, needFix, err = ParseJSON(jsonData, luteEngine.ParseOptions)
 	if nil != err {
 	if nil != err {
 		logging.LogErrorf("parse json [%s] to tree failed: %s", boxID+p, err)
 		logging.LogErrorf("parse json [%s] to tree failed: %s", boxID+p, err)
 		return
 		return

+ 2 - 2
kernel/go.mod

@@ -6,7 +6,7 @@ require (
 	github.com/88250/clipboard v0.1.5
 	github.com/88250/clipboard v0.1.5
 	github.com/88250/css v0.1.2
 	github.com/88250/css v0.1.2
 	github.com/88250/gulu v1.2.3-0.20221117052724-cd06804db798
 	github.com/88250/gulu v1.2.3-0.20221117052724-cd06804db798
-	github.com/88250/lute v1.7.5
+	github.com/88250/lute v1.7.6-0.20230127125719-f7a1c703548a
 	github.com/88250/pdfcpu v0.3.13
 	github.com/88250/pdfcpu v0.3.13
 	github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1
 	github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1
 	github.com/ConradIrwin/font v0.0.0-20210318200717-ce8d41cc0732
 	github.com/ConradIrwin/font v0.0.0-20210318200717-ce8d41cc0732
@@ -27,6 +27,7 @@ require (
 	github.com/gin-contrib/gzip v0.0.6
 	github.com/gin-contrib/gzip v0.0.6
 	github.com/gin-contrib/sessions v0.0.5
 	github.com/gin-contrib/sessions v0.0.5
 	github.com/gin-gonic/gin v1.8.2
 	github.com/gin-gonic/gin v1.8.2
+	github.com/goccy/go-json v0.10.0
 	github.com/gofrs/flock v0.8.1
 	github.com/gofrs/flock v0.8.1
 	github.com/imroc/req/v3 v3.30.0
 	github.com/imroc/req/v3 v3.30.0
 	github.com/jinzhu/copier v0.3.5
 	github.com/jinzhu/copier v0.3.5
@@ -73,7 +74,6 @@ require (
 	github.com/go-playground/universal-translator v0.18.0 // indirect
 	github.com/go-playground/universal-translator v0.18.0 // indirect
 	github.com/go-playground/validator/v10 v10.11.1 // indirect
 	github.com/go-playground/validator/v10 v10.11.1 // indirect
 	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
 	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
-	github.com/goccy/go-json v0.10.0 // indirect
 	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
 	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
 	github.com/golang/glog v1.0.0 // indirect
 	github.com/golang/glog v1.0.0 // indirect
 	github.com/google/uuid v1.3.0 // indirect
 	github.com/google/uuid v1.3.0 // indirect

+ 2 - 2
kernel/go.sum

@@ -17,8 +17,8 @@ github.com/88250/go-sqlite3 v1.14.13-0.20220714142610-fbbda1ee84f5 h1:8HdZozCsXS
 github.com/88250/go-sqlite3 v1.14.13-0.20220714142610-fbbda1ee84f5/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/88250/go-sqlite3 v1.14.13-0.20220714142610-fbbda1ee84f5/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/88250/gulu v1.2.3-0.20221117052724-cd06804db798 h1:sR/s/Y9wyl79ZRCUERwLPo9zqaB3KhNRodCMTJ4ozEU=
 github.com/88250/gulu v1.2.3-0.20221117052724-cd06804db798 h1:sR/s/Y9wyl79ZRCUERwLPo9zqaB3KhNRodCMTJ4ozEU=
 github.com/88250/gulu v1.2.3-0.20221117052724-cd06804db798/go.mod h1:I1qBzsksFL2ciGSuqDE7R3XW4BUMrfDgOvSXEk7FsAI=
 github.com/88250/gulu v1.2.3-0.20221117052724-cd06804db798/go.mod h1:I1qBzsksFL2ciGSuqDE7R3XW4BUMrfDgOvSXEk7FsAI=
-github.com/88250/lute v1.7.5 h1:mcPFURh5sK1WH1kFRjqK5DkMWOfVN2BhyrXitN8GmpQ=
-github.com/88250/lute v1.7.5/go.mod h1:cEoBGi0zArPqAsp0MdG9SKinvH/xxZZWXU7sRx8vHSA=
+github.com/88250/lute v1.7.6-0.20230127125719-f7a1c703548a h1:zqWeg1AOmN2X9tD0AD0V5BE40eQOOxuFqB0LSGiw6tQ=
+github.com/88250/lute v1.7.6-0.20230127125719-f7a1c703548a/go.mod h1:cEoBGi0zArPqAsp0MdG9SKinvH/xxZZWXU7sRx8vHSA=
 github.com/88250/pdfcpu v0.3.13 h1:touMWMZkCGalMIbEg9bxYp7rETM+zwb9hXjwhqi4I7Q=
 github.com/88250/pdfcpu v0.3.13 h1:touMWMZkCGalMIbEg9bxYp7rETM+zwb9hXjwhqi4I7Q=
 github.com/88250/pdfcpu v0.3.13/go.mod h1:S5YT38L/GCjVjmB4PB84PymA1qfopjEhfhTNQilLpv4=
 github.com/88250/pdfcpu v0.3.13/go.mod h1:S5YT38L/GCjVjmB4PB84PymA1qfopjEhfhTNQilLpv4=
 github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1 h1:48T899JQDwyyRu9yXHePYlPdHtpJfrJEUGBMH3SMBWY=
 github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1 h1:48T899JQDwyyRu9yXHePYlPdHtpJfrJEUGBMH3SMBWY=

+ 1 - 1
kernel/model/block.go

@@ -138,7 +138,7 @@ func SwapBlockRef(refID, defID string, includeChildren bool) (err error) {
 		}
 		}
 	}
 	}
 
 
-	refPivot := parse.NewParagraph()
+	refPivot := treenode.NewParagraph()
 	refNode.InsertBefore(refPivot)
 	refNode.InsertBefore(refPivot)
 
 
 	if ast.NodeListItem == defNode.Type {
 	if ast.NodeListItem == defNode.Type {

+ 1 - 1
kernel/model/box.go

@@ -425,7 +425,7 @@ func parseKTree(kramdown []byte) (ret *parse.Tree) {
 
 
 func genTreeID(tree *parse.Tree) {
 func genTreeID(tree *parse.Tree) {
 	if nil == tree.Root.FirstChild {
 	if nil == tree.Root.FirstChild {
-		tree.Root.AppendChild(parse.NewParagraph())
+		tree.Root.AppendChild(treenode.NewParagraph())
 	}
 	}
 
 
 	ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
 	ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {

+ 6 - 1
kernel/model/file.go

@@ -447,6 +447,11 @@ func getMarkSpanEnd() string {
 }
 }
 
 
 func GetDoc(startID, endID, id string, index int, keyword string, mode int, size int, isBacklink bool) (blockCount, childBlockCount int, dom, parentID, parent2ID, rootID, typ string, eof bool, boxID, docPath string, isBacklinkExpand bool, err error) {
 func GetDoc(startID, endID, id string, index int, keyword string, mode int, size int, isBacklink bool) (blockCount, childBlockCount int, dom, parentID, parent2ID, rootID, typ string, eof bool, boxID, docPath string, isBacklinkExpand bool, err error) {
+	//os.MkdirAll("pprof", 0755)
+	//cpuProfile, _ := os.Create("pprof/GetDoc")
+	//pprof.StartCPUProfile(cpuProfile)
+	//defer pprof.StopCPUProfile()
+
 	WaitForWritingFiles() // 写入数据时阻塞,避免获取到的数据不一致
 	WaitForWritingFiles() // 写入数据时阻塞,避免获取到的数据不一致
 
 
 	inputIndex := index
 	inputIndex := index
@@ -1451,7 +1456,7 @@ func createDoc(boxID, p, title, dom string) (tree *parse.Tree, err error) {
 	updated := util.TimeFromID(id)
 	updated := util.TimeFromID(id)
 	tree.Root.KramdownIAL = [][]string{{"id", id}, {"title", html.EscapeAttrVal(title)}, {"updated", updated}}
 	tree.Root.KramdownIAL = [][]string{{"id", id}, {"title", html.EscapeAttrVal(title)}, {"updated", updated}}
 	if nil == tree.Root.FirstChild {
 	if nil == tree.Root.FirstChild {
-		tree.Root.AppendChild(parse.NewParagraph())
+		tree.Root.AppendChild(treenode.NewParagraph())
 	}
 	}
 
 
 	transaction := &Transaction{DoOperations: []*Operation{{Action: "create", Data: tree}}}
 	transaction := &Transaction{DoOperations: []*Operation{{Action: "create", Data: tree}}}

+ 2 - 2
kernel/model/heading.go

@@ -166,7 +166,7 @@ func Doc2Heading(srcID, targetID string, after bool) (srcTreeBox, srcTreePath st
 	if "" != tagIAL && 0 < len(tags) {
 	if "" != tagIAL && 0 < len(tags) {
 		// 带标签的文档块转换为标题块时将标签移动到标题块下方 https://github.com/siyuan-note/siyuan/issues/6550
 		// 带标签的文档块转换为标题块时将标签移动到标题块下方 https://github.com/siyuan-note/siyuan/issues/6550
 
 
-		tagPara := parse.NewParagraph()
+		tagPara := treenode.NewParagraph()
 		for i, tag := range tags {
 		for i, tag := range tags {
 			if "" == tag {
 			if "" == tag {
 				continue
 				continue
@@ -319,7 +319,7 @@ func Heading2Doc(srcHeadingID, targetBoxID, targetPath string) (srcRootBlockID,
 	headingNode.Unlink()
 	headingNode.Unlink()
 	srcTree.Root.SetIALAttr("updated", util.CurrentTimeSecondsStr())
 	srcTree.Root.SetIALAttr("updated", util.CurrentTimeSecondsStr())
 	if nil == srcTree.Root.FirstChild {
 	if nil == srcTree.Root.FirstChild {
-		srcTree.Root.AppendChild(parse.NewParagraph())
+		srcTree.Root.AppendChild(treenode.NewParagraph())
 	}
 	}
 	if err = indexWriteJSONQueue(srcTree); nil != err {
 	if err = indexWriteJSONQueue(srcTree); nil != err {
 		return "", "", err
 		return "", "", err

+ 3 - 2
kernel/model/history.go

@@ -19,7 +19,6 @@ package model
 import (
 import (
 	"encoding/json"
 	"encoding/json"
 	"fmt"
 	"fmt"
-	"github.com/siyuan-note/siyuan/kernel/task"
 	"io/fs"
 	"io/fs"
 	"math"
 	"math"
 	"os"
 	"os"
@@ -37,8 +36,10 @@ import (
 	"github.com/siyuan-note/filelock"
 	"github.com/siyuan-note/filelock"
 	"github.com/siyuan-note/logging"
 	"github.com/siyuan-note/logging"
 	"github.com/siyuan-note/siyuan/kernel/conf"
 	"github.com/siyuan-note/siyuan/kernel/conf"
+	"github.com/siyuan-note/siyuan/kernel/filesys"
 	"github.com/siyuan-note/siyuan/kernel/search"
 	"github.com/siyuan-note/siyuan/kernel/search"
 	"github.com/siyuan-note/siyuan/kernel/sql"
 	"github.com/siyuan-note/siyuan/kernel/sql"
+	"github.com/siyuan-note/siyuan/kernel/task"
 	"github.com/siyuan-note/siyuan/kernel/treenode"
 	"github.com/siyuan-note/siyuan/kernel/treenode"
 	"github.com/siyuan-note/siyuan/kernel/util"
 	"github.com/siyuan-note/siyuan/kernel/util"
 )
 )
@@ -154,7 +155,7 @@ func GetDocHistoryContent(historyPath, keyword string) (id, rootID, content stri
 	isLargeDoc = 1024*1024*1 <= len(data)
 	isLargeDoc = 1024*1024*1 <= len(data)
 
 
 	luteEngine := NewLute()
 	luteEngine := NewLute()
-	historyTree, err := parse.ParseJSONWithoutFix(data, luteEngine.ParseOptions)
+	historyTree, err := filesys.ParseJSONWithoutFix(data, luteEngine.ParseOptions)
 	if nil != err {
 	if nil != err {
 		logging.LogErrorf("parse tree from file [%s] failed, remove it", historyPath)
 		logging.LogErrorf("parse tree from file [%s] failed, remove it", historyPath)
 		os.RemoveAll(historyPath)
 		os.RemoveAll(historyPath)

+ 2 - 1
kernel/model/import.go

@@ -45,6 +45,7 @@ import (
 	"github.com/88250/lute/render"
 	"github.com/88250/lute/render"
 	"github.com/siyuan-note/filelock"
 	"github.com/siyuan-note/filelock"
 	"github.com/siyuan-note/logging"
 	"github.com/siyuan-note/logging"
+	"github.com/siyuan-note/siyuan/kernel/filesys"
 	"github.com/siyuan-note/siyuan/kernel/sql"
 	"github.com/siyuan-note/siyuan/kernel/sql"
 	"github.com/siyuan-note/siyuan/kernel/treenode"
 	"github.com/siyuan-note/siyuan/kernel/treenode"
 	"github.com/siyuan-note/siyuan/kernel/util"
 	"github.com/siyuan-note/siyuan/kernel/util"
@@ -125,7 +126,7 @@ func ImportSY(zipPath, boxID, toPath string) (err error) {
 			err = readErr
 			err = readErr
 			return
 			return
 		}
 		}
-		tree, _, parseErr := parse.ParseJSON(data, luteEngine.ParseOptions)
+		tree, _, parseErr := filesys.ParseJSON(data, luteEngine.ParseOptions)
 		if nil != parseErr {
 		if nil != parseErr {
 			logging.LogErrorf("parse .sy [%s] failed: %s", syPath, parseErr)
 			logging.LogErrorf("parse .sy [%s] failed: %s", syPath, parseErr)
 			err = parseErr
 			err = parseErr

+ 2 - 2
kernel/model/listitem.go

@@ -72,7 +72,7 @@ func ListItem2Doc(srcListItemID, targetBoxID, targetPath string) (srcRootBlockID
 		children = append(children, c)
 		children = append(children, c)
 	}
 	}
 	if 1 > len(children) {
 	if 1 > len(children) {
-		newNode := parse.NewParagraph()
+		newNode := treenode.NewParagraph()
 		children = append(children, newNode)
 		children = append(children, newNode)
 	}
 	}
 
 
@@ -96,7 +96,7 @@ func ListItem2Doc(srcListItemID, targetBoxID, targetPath string) (srcRootBlockID
 	}
 	}
 	srcTree.Root.SetIALAttr("updated", util.CurrentTimeSecondsStr())
 	srcTree.Root.SetIALAttr("updated", util.CurrentTimeSecondsStr())
 	if nil == srcTree.Root.FirstChild {
 	if nil == srcTree.Root.FirstChild {
-		srcTree.Root.AppendChild(parse.NewParagraph())
+		srcTree.Root.AppendChild(treenode.NewParagraph())
 	}
 	}
 	if err = indexWriteJSONQueue(srcTree); nil != err {
 	if err = indexWriteJSONQueue(srcTree); nil != err {
 		return "", "", err
 		return "", "", err

+ 4 - 3
kernel/model/repository.go

@@ -23,7 +23,6 @@ import (
 	"encoding/base64"
 	"encoding/base64"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
-	"github.com/siyuan-note/siyuan/kernel/task"
 	"math"
 	"math"
 	"net/http"
 	"net/http"
 	"os"
 	"os"
@@ -49,7 +48,9 @@ import (
 	"github.com/siyuan-note/logging"
 	"github.com/siyuan-note/logging"
 	"github.com/siyuan-note/siyuan/kernel/cache"
 	"github.com/siyuan-note/siyuan/kernel/cache"
 	"github.com/siyuan-note/siyuan/kernel/conf"
 	"github.com/siyuan-note/siyuan/kernel/conf"
+	"github.com/siyuan-note/siyuan/kernel/filesys"
 	"github.com/siyuan-note/siyuan/kernel/sql"
 	"github.com/siyuan-note/siyuan/kernel/sql"
+	"github.com/siyuan-note/siyuan/kernel/task"
 	"github.com/siyuan-note/siyuan/kernel/treenode"
 	"github.com/siyuan-note/siyuan/kernel/treenode"
 	"github.com/siyuan-note/siyuan/kernel/util"
 	"github.com/siyuan-note/siyuan/kernel/util"
 	"github.com/studio-b12/gowebdav"
 	"github.com/studio-b12/gowebdav"
@@ -255,7 +256,7 @@ func parseTitleInSnapshot(fileID string, repo *dejavu.Repo, luteEngine *lute.Lut
 	}
 	}
 
 
 	var tree *parse.Tree
 	var tree *parse.Tree
-	tree, err = parse.ParseJSONWithoutFix(data, luteEngine.ParseOptions)
+	tree, err = filesys.ParseJSONWithoutFix(data, luteEngine.ParseOptions)
 	if nil != err {
 	if nil != err {
 		logging.LogErrorf("parse file [%s] failed: %s", fileID, err)
 		logging.LogErrorf("parse file [%s] failed: %s", fileID, err)
 		return
 		return
@@ -277,7 +278,7 @@ func parseTreeInSnapshot(fileID string, repo *dejavu.Repo, luteEngine *lute.Lute
 	}
 	}
 
 
 	isLargeDoc = 1024*1024*1 <= len(data)
 	isLargeDoc = 1024*1024*1 <= len(data)
-	tree, err = parse.ParseJSONWithoutFix(data, luteEngine.ParseOptions)
+	tree, err = filesys.ParseJSONWithoutFix(data, luteEngine.ParseOptions)
 	if nil != err {
 	if nil != err {
 		return
 		return
 	}
 	}

+ 8 - 8
kernel/model/template.go

@@ -20,13 +20,6 @@ import (
 	"bytes"
 	"bytes"
 	"errors"
 	"errors"
 	"fmt"
 	"fmt"
-	"github.com/88250/lute/ast"
-	"github.com/88250/lute/parse"
-	"github.com/88250/lute/render"
-	"github.com/araddon/dateparse"
-	"github.com/siyuan-note/logging"
-	"github.com/siyuan-note/siyuan/kernel/treenode"
-	"github.com/siyuan-note/siyuan/kernel/util"
 	"io/fs"
 	"io/fs"
 	"os"
 	"os"
 	"path/filepath"
 	"path/filepath"
@@ -36,9 +29,16 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/88250/gulu"
 	"github.com/88250/gulu"
+	"github.com/88250/lute/ast"
+	"github.com/88250/lute/parse"
+	"github.com/88250/lute/render"
 	sprig "github.com/Masterminds/sprig/v3"
 	sprig "github.com/Masterminds/sprig/v3"
+	"github.com/araddon/dateparse"
+	"github.com/siyuan-note/logging"
 	"github.com/siyuan-note/siyuan/kernel/search"
 	"github.com/siyuan-note/siyuan/kernel/search"
 	"github.com/siyuan-note/siyuan/kernel/sql"
 	"github.com/siyuan-note/siyuan/kernel/sql"
+	"github.com/siyuan-note/siyuan/kernel/treenode"
+	"github.com/siyuan-note/siyuan/kernel/util"
 )
 )
 
 
 func RenderGoTemplate(templateContent string) (ret string, err error) {
 func RenderGoTemplate(templateContent string) (ret string, err error) {
@@ -258,7 +258,7 @@ func renderTemplate(p, id string) (string, error) {
 		return ast.WalkContinue
 		return ast.WalkContinue
 	})
 	})
 	for _, n := range nodesNeedAppendChild {
 	for _, n := range nodesNeedAppendChild {
-		n.AppendChild(parse.NewParagraph())
+		n.AppendChild(treenode.NewParagraph())
 	}
 	}
 	for _, n := range unlinks {
 	for _, n := range unlinks {
 		n.Unlink()
 		n.Unlink()

+ 1 - 1
kernel/model/tree.go

@@ -131,7 +131,7 @@ func loadTree(localPath string, luteEngine *lute.Lute) (ret *parse.Tree, err err
 		return
 		return
 	}
 	}
 
 
-	ret, err = parse.ParseJSONWithoutFix(data, luteEngine.ParseOptions)
+	ret, err = filesys.ParseJSONWithoutFix(data, luteEngine.ParseOptions)
 	if nil != err {
 	if nil != err {
 		logging.LogErrorf("parse json to tree [%s] failed: %s", localPath, err)
 		logging.LogErrorf("parse json to tree [%s] failed: %s", localPath, err)
 		return
 		return

+ 1 - 2
kernel/model/widget.go

@@ -22,9 +22,8 @@ import (
 	"strings"
 	"strings"
 
 
 	"github.com/siyuan-note/logging"
 	"github.com/siyuan-note/logging"
-	"github.com/siyuan-note/siyuan/kernel/util"
-
 	"github.com/siyuan-note/siyuan/kernel/search"
 	"github.com/siyuan-note/siyuan/kernel/search"
+	"github.com/siyuan-note/siyuan/kernel/util"
 )
 )
 
 
 func SearchWidget(keyword string) (ret []*Block) {
 func SearchWidget(keyword string) (ret []*Block) {

+ 8 - 0
kernel/treenode/tree.go

@@ -114,3 +114,11 @@ func RootChildIDs(rootID string) (ret []string) {
 	})
 	})
 	return
 	return
 }
 }
+
+func NewParagraph() (ret *ast.Node) {
+	newID := ast.NewNodeID()
+	ret = &ast.Node{ID: newID, Type: ast.NodeParagraph}
+	ret.SetIALAttr("id", newID)
+	ret.SetIALAttr("updated", newID[:14])
+	return
+}

部分文件因为文件数量过多而无法显示