siyuan/kernel/model/path.go

417 lines
12 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SiYuan - Refactor your thinking
// 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 model
import (
"bytes"
"os"
"path"
"path/filepath"
"sort"
"strings"
"github.com/88250/lute/ast"
"github.com/88250/lute/parse"
"github.com/siyuan-note/logging"
"github.com/siyuan-note/siyuan/kernel/search"
"github.com/siyuan-note/siyuan/kernel/sql"
"github.com/siyuan-note/siyuan/kernel/treenode"
"github.com/siyuan-note/siyuan/kernel/util"
)
func createDocsByHPath(boxID, hPath, content, parentID, id string) (retID string, err error) {
if "" == id {
id = ast.NewNodeID()
}
retID = id
hPath = strings.TrimSuffix(hPath, ".sy")
if "" != parentID {
// The save path is incorrect when creating a sub-doc by ref in a doc with the same name https://github.com/siyuan-note/siyuan/issues/8138
// 在指定了父文档 ID 的情况下优先查找父文档
parentHPath, name := path.Split(hPath)
parentHPath = strings.TrimSuffix(parentHPath, "/")
preferredParent := treenode.GetBlockTreeRootByHPathPreferredParentID(boxID, parentHPath, parentID)
if nil != preferredParent && preferredParent.ID == parentID {
// 如果父文档存在且 ID 一致,则直接在父文档下创建
p := strings.TrimSuffix(preferredParent.Path, ".sy") + "/" + id + ".sy"
if _, err = createDoc(boxID, p, name, content); nil != err {
logging.LogErrorf("create doc [%s] failed: %s", p, err)
}
return
}
}
root := treenode.GetBlockTreeRootByPath(boxID, hPath)
if nil != root {
retID = root.ID
return
}
hPathBuilder := bytes.Buffer{}
hpathBtMap := map[string]*treenode.BlockTree{}
parts := strings.Split(hPath, "/")[1:]
// The subdoc creation path is unstable when a parent doc with the same name exists https://github.com/siyuan-note/siyuan/issues/9322
// 存在同名父文档时子文档创建路径不稳定,这里需要按照完整的 hpath 映射,不能在下面的循环中边构建 hpath 边构建 path否则虽然 hpath 相同,但是会导致 path 组装错位
for i, part := range parts {
if i == len(parts)-1 {
break
}
hPathBuilder.WriteString("/")
hPathBuilder.WriteString(part)
hp := hPathBuilder.String()
root = treenode.GetBlockTreeRootByHPath(boxID, hp)
if nil == root {
break
}
hpathBtMap[hp] = root
}
pathBuilder := bytes.Buffer{}
pathBuilder.WriteString("/")
hPathBuilder = bytes.Buffer{}
hPathBuilder.WriteString("/")
for i, part := range parts {
hPathBuilder.WriteString(part)
hp := hPathBuilder.String()
root = hpathBtMap[hp]
isNotLast := i < len(parts)-1
if nil == root {
rootID := ast.NewNodeID()
if i == len(parts)-1 {
rootID = retID
}
pathBuilder.WriteString(rootID)
docP := pathBuilder.String() + ".sy"
if isNotLast {
if _, err = createDoc(boxID, docP, part, ""); nil != err {
return
}
} else {
if _, err = createDoc(boxID, docP, part, content); nil != err {
return
}
}
if isNotLast {
dirPath := filepath.Join(util.DataDir, boxID, pathBuilder.String())
if err = os.MkdirAll(dirPath, 0755); nil != err {
logging.LogErrorf("mkdir [%s] failed: %s", dirPath, err)
return
}
}
} else {
pathBuilder.WriteString(root.ID)
if !isNotLast {
pathBuilder.WriteString(".sy")
}
}
if isNotLast {
pathBuilder.WriteString("/")
hPathBuilder.WriteString("/")
}
}
return
}
func toFlatTree(blocks []*Block, baseDepth int, typ string, tree *parse.Tree) (ret []*Path) {
var blockRoots []*Block
for _, block := range blocks {
root := getBlockIn(blockRoots, block.RootID)
if nil == root {
root, _ = getBlock(block.RootID, tree)
blockRoots = append(blockRoots, root)
}
if nil == root {
return
}
block.Depth = baseDepth + 1
block.Count = len(block.Children)
root.Children = append(root.Children, block)
}
for _, root := range blockRoots {
treeNode := &Path{
ID: root.ID,
Box: root.Box,
Name: path.Base(root.HPath),
NodeType: root.Type,
Type: typ,
SubType: root.SubType,
Depth: baseDepth,
Count: len(root.Children),
Updated: root.IAL["updated"],
Created: root.ID[:14],
}
for _, c := range root.Children {
treeNode.Blocks = append(treeNode.Blocks, c)
}
ret = append(ret, treeNode)
if "backlink" == typ {
treeNode.HPath = root.HPath
}
}
sort.Slice(ret, func(i, j int) bool {
return ret[i].ID > ret[j].ID
})
return
}
func toSubTree(blocks []*Block, keyword string) (ret []*Path) {
keyword = strings.TrimSpace(keyword)
var blockRoots []*Block
for _, block := range blocks {
root := getBlockIn(blockRoots, block.RootID)
if nil == root {
root, _ = getBlock(block.RootID, nil)
blockRoots = append(blockRoots, root)
}
block.Depth = 1
block.Count = len(block.Children)
root.Children = append(root.Children, block)
}
for _, root := range blockRoots {
treeNode := &Path{
ID: root.ID,
Box: root.Box,
Name: path.Base(root.HPath),
Type: "backlink",
NodeType: "NodeDocument",
SubType: root.SubType,
Depth: 0,
Count: len(root.Children),
}
for _, c := range root.Children {
if "NodeListItem" == c.Type {
tree, _ := LoadTreeByBlockID(c.RootID)
li := treenode.GetNodeInTree(tree, c.ID)
if nil == li || nil == li.FirstChild {
// 反链面板拖拽到文档以后可能会出现这种情况 https://github.com/siyuan-note/siyuan/issues/5363
continue
}
var first *sql.Block
if 3 != li.ListData.Typ {
first = sql.GetBlock(li.FirstChild.ID)
} else {
first = sql.GetBlock(li.FirstChild.Next.ID)
}
name := first.Content
parentPos := 0
if "" != keyword {
parentPos, name = search.MarkText(name, keyword, 12, Conf.Search.CaseSensitive)
}
subRoot := &Path{
ID: li.ID,
Box: li.Box,
Name: name,
Type: "backlink",
NodeType: li.Type.String(),
SubType: c.SubType,
Depth: 1,
Count: 1,
}
unfold := true
for liFirstBlockSpan := li.FirstChild.FirstChild; nil != liFirstBlockSpan; liFirstBlockSpan = liFirstBlockSpan.Next {
if treenode.IsBlockRef(liFirstBlockSpan) {
continue
}
if "" != strings.TrimSpace(liFirstBlockSpan.Text()) {
unfold = false
break
}
}
for next := li.FirstChild.Next; nil != next; next = next.Next {
subBlock, _ := getBlock(next.ID, tree)
if unfold {
if ast.NodeList == next.Type {
for subLi := next.FirstChild; nil != subLi; subLi = subLi.Next {
subLiBlock, _ := getBlock(subLi.ID, tree)
var subFirst *sql.Block
if 3 != subLi.ListData.Typ {
subFirst = sql.GetBlock(subLi.FirstChild.ID)
} else {
subFirst = sql.GetBlock(subLi.FirstChild.Next.ID)
}
subPos := 0
content := subFirst.Content
if "" != keyword {
subPos, content = search.MarkText(subFirst.Content, keyword, 12, Conf.Search.CaseSensitive)
}
if -1 < subPos {
parentPos = 0 // 需要显示父级
}
subLiBlock.Content = content
subLiBlock.Depth = 2
subRoot.Blocks = append(subRoot.Blocks, subLiBlock)
}
} else if ast.NodeHeading == next.Type {
subBlock.Depth = 2
subRoot.Blocks = append(subRoot.Blocks, subBlock)
headingChildren := treenode.HeadingChildren(next)
var breakSub bool
for _, n := range headingChildren {
block, _ := getBlock(n.ID, tree)
subPos := 0
content := block.Content
if "" != keyword {
subPos, content = search.MarkText(block.Content, keyword, 12, Conf.Search.CaseSensitive)
}
if -1 < subPos {
parentPos = 0
}
block.Content = content
block.Depth = 3
subRoot.Blocks = append(subRoot.Blocks, block)
if ast.NodeHeading == n.Type {
// 跳过子标题下面的块
breakSub = true
break
}
}
if breakSub {
break
}
} else {
if nil == treenode.HeadingParent(next) {
subBlock.Depth = 2
subRoot.Blocks = append(subRoot.Blocks, subBlock)
}
}
}
}
if -1 < parentPos {
treeNode.Children = append(treeNode.Children, subRoot)
}
} else if "NodeHeading" == c.Type {
tree, _ := LoadTreeByBlockID(c.RootID)
h := treenode.GetNodeInTree(tree, c.ID)
if nil == h {
continue
}
name := sql.GetBlock(h.ID).Content
parentPos := 0
if "" != keyword {
parentPos, name = search.MarkText(name, keyword, 12, Conf.Search.CaseSensitive)
}
subRoot := &Path{
ID: h.ID,
Box: h.Box,
Name: name,
Type: "backlink",
NodeType: h.Type.String(),
SubType: c.SubType,
Depth: 1,
Count: 1,
}
unfold := true
for headingFirstSpan := h.FirstChild; nil != headingFirstSpan; headingFirstSpan = headingFirstSpan.Next {
if treenode.IsBlockRef(headingFirstSpan) {
continue
}
if "" != strings.TrimSpace(headingFirstSpan.Text()) {
unfold = false
break
}
}
if unfold {
headingChildren := treenode.HeadingChildren(h)
for _, headingChild := range headingChildren {
if ast.NodeList == headingChild.Type {
for subLi := headingChild.FirstChild; nil != subLi; subLi = subLi.Next {
subLiBlock, _ := getBlock(subLi.ID, tree)
var subFirst *sql.Block
if 3 != subLi.ListData.Typ {
subFirst = sql.GetBlock(subLi.FirstChild.ID)
} else {
subFirst = sql.GetBlock(subLi.FirstChild.Next.ID)
}
subPos := 0
content := subFirst.Content
if "" != keyword {
subPos, content = search.MarkText(content, keyword, 12, Conf.Search.CaseSensitive)
}
if -1 < subPos {
parentPos = 0
}
subLiBlock.Content = subFirst.Content
subLiBlock.Depth = 2
subRoot.Blocks = append(subRoot.Blocks, subLiBlock)
}
} else {
subBlock, _ := getBlock(headingChild.ID, tree)
subBlock.Depth = 2
subRoot.Blocks = append(subRoot.Blocks, subBlock)
}
}
}
if -1 < parentPos {
treeNode.Children = append(treeNode.Children, subRoot)
}
} else {
pos := 0
content := c.Content
if "" != keyword {
pos, content = search.MarkText(content, keyword, 12, Conf.Search.CaseSensitive)
}
if -1 < pos {
treeNode.Blocks = append(treeNode.Blocks, c)
}
}
}
rootPos := -1
var rootContent string
if "" != keyword {
rootPos, rootContent = search.MarkText(treeNode.Name, keyword, 12, Conf.Search.CaseSensitive)
treeNode.Name = rootContent
}
if 0 < len(treeNode.Children) || 0 < len(treeNode.Blocks) || (-1 < rootPos && "" != keyword) {
ret = append(ret, treeNode)
}
}
sort.Slice(ret, func(i, j int) bool {
return ret[i].ID > ret[j].ID
})
return
}
func getBlockIn(blocks []*Block, id string) *Block {
if "" == id {
return nil
}
for _, block := range blocks {
if block.ID == id {
return block
}
}
return nil
}