Something went wrong. Try again.
A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425// 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") hPath = util.TrimSpaceInPath(hPath) 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.GetBlockTreeByHPathPreferredParentID(boxID, parentHPath, parentID) if nil != preferredParent && preferredParent.RootID == parentID { // 如果父文档存在且 ID 一致,则直接在父文档下创建 p := strings.TrimSuffix(preferredParent.Path, ".sy") + "/" + id + ".sy" if _, err = createDoc(boxID, p, name, content); err != nil { 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, ""); err != nil { return } } else { if _, err = createDoc(boxID, docP, part, content); err != nil { return } }
if isNotLast { dirPath := filepath.Join(util.DataDir, boxID, pathBuilder.String()) if err = os.MkdirAll(dirPath, 0755); err != nil { 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) }
folded := false if "outline" == typ { folded = true }
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), Folded: folded,
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}