From 24f6f6337940d1f0c752efa4460550824ac2283c Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Sat, 24 Jun 2023 17:01:11 +0800 Subject: [PATCH] :art: Convert `[[wikilink]]` and `#Tag` syntax when importing Markdown https://github.com/siyuan-note/siyuan/issues/8603 --- kernel/model/import.go | 192 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 181 insertions(+), 11 deletions(-) diff --git a/kernel/model/import.go b/kernel/model/import.go index a23869e1e..a8e3c8d9a 100644 --- a/kernel/model/import.go +++ b/kernel/model/import.go @@ -502,7 +502,6 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) { assetsDone := map[string]string{} // md 转换 sy - i := 0 filepath.Walk(localPath, func(currentPath string, info os.FileInfo, walkErr error) error { if strings.HasPrefix(info.Name(), ".") { if info.IsDir() { @@ -538,9 +537,7 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) { if info.IsDir() { tree = treenode.NewTree(boxID, targetPath, hPath, title) - if err = indexWriteJSONQueue(tree); nil != err { - return io.EOF - } + importTrees = append(importTrees, tree) return nil } @@ -623,12 +620,7 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) { }) reassignIDUpdated(tree) - indexWriteJSONQueue(tree) - - i++ - if 0 == i%4 { - util.PushEndlessProgress(fmt.Sprintf(Conf.Language(66), fmt.Sprintf("%d ", i)+util.ShortPathForBootingDisplay(tree.Path))) - } + importTrees = append(importTrees, tree) return nil }) } else { // 导入单个文件 @@ -707,7 +699,23 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) { }) reassignIDUpdated(tree) - indexWriteJSONQueue(tree) + importTrees = append(importTrees, tree) + } + + if 0 < len(importTrees) { + initSearchLinks() + convertWikiLinksAndTags() + buildBlockRefInText() + + for i, tree := range importTrees { + indexWriteJSONQueue(tree) + if 0 == i%4 { + util.PushEndlessProgress(fmt.Sprintf(Conf.Language(66), fmt.Sprintf("%d/%d ", i, len(importTrees))+tree.HPath)) + } + } + + importTrees = []*parse.Tree{} + searchLinks = map[string]string{} } IncSync() @@ -909,3 +917,165 @@ func domAttrValue(n *html.Node, attrName string) string { } return "" } + +var importTrees []*parse.Tree +var searchLinks = map[string]string{} + +func initSearchLinks() { + for _, tree := range importTrees { + ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { + if !entering || (ast.NodeDocument != n.Type && ast.NodeHeading != n.Type) { + return ast.WalkContinue + } + + nodePath := tree.HPath + "#" + if ast.NodeHeading == n.Type { + nodePath += n.Text() + } + + searchLinks[nodePath] = n.ID + return ast.WalkContinue + }) + } +} + +func convertWikiLinksAndTags() { + for _, tree := range importTrees { + convertWikiLinksAndTags0(tree) + } +} + +func convertWikiLinksAndTags0(tree *parse.Tree) { + ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { + if !entering || ast.NodeText != n.Type { + return ast.WalkContinue + } + + text := n.TokensStr() + length := len(text) + start, end := 0, length + for { + part := text[start:end] + if idx := strings.Index(part, "]]"); 0 > idx { + break + } else { + end = start + idx + } + if idx := strings.Index(part, "[["); 0 > idx { + break + } else { + start += idx + } + if end <= start { + break + } + + link := path.Join(path.Dir(tree.HPath), text[start+2:end]) // 统一转为绝对路径方便后续查找 + linkText := path.Base(link) + if linkParts := strings.Split(link, "|"); 1 < len(linkParts) { + link = linkParts[0] + linkText = linkParts[1] + } + link, linkText = strings.TrimSpace(link), strings.TrimSpace(linkText) + if !strings.Contains(link, "#") { + link += "#" // 在结尾统一带上锚点方便后续查找 + } + + id := searchLinkID(link) + if "" == id { + start, end = end, length + continue + } + + linkText = strings.TrimPrefix(linkText, "/") + repl := "((" + id + " '" + linkText + "'))" + end += 2 + text = text[:start] + repl + text[end:] + start, end = start+len(repl), len(text) + length = end + } + + text = convertTags(text) // 导入标签语法 + n.Tokens = gulu.Str.ToBytes(text) + return ast.WalkContinue + }) +} + +func convertTags(text string) (ret string) { + pos, i := -1, 0 + tokens := []byte(text) + for ; i < len(tokens); i++ { + if '#' == tokens[i] && (0 == i || ' ' == tokens[i-1] || (-1 < pos && '#' == tokens[pos])) { + if i < len(tokens)-1 && '#' == tokens[i+1] { + pos = -1 + continue + } + pos = i + continue + } + + if -1 < pos && ' ' == tokens[i] { + tokens = append(tokens, 0) + copy(tokens[i+1:], tokens[i:]) + tokens[i] = '#' + pos = -1 + i++ + } + } + if -1 < pos && pos < i { + tokens = append(tokens, '#') + } + return string(tokens) +} + +// buildBlockRefInText 将文本节点进行结构化处理。 +func buildBlockRefInText() { + lute := NewLute() + for _, tree := range importTrees { + var unlinkTextNodes []*ast.Node + ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { + if !entering || ast.NodeText != n.Type { + return ast.WalkContinue + } + + if nil == n.Tokens { + return ast.WalkContinue + } + + t := parse.Inline("", n.Tokens, lute.ParseOptions) // 使用行级解析 + var children []*ast.Node + for c := t.Root.FirstChild.FirstChild; nil != c; c = c.Next { + children = append(children, c) + } + for _, c := range children { + n.InsertBefore(c) + } + unlinkTextNodes = append(unlinkTextNodes, n) + return ast.WalkContinue + }) + + for _, node := range unlinkTextNodes { + node.Unlink() + } + } +} + +func searchLinkID(link string) (id string) { + id = searchLinks[link] + if "" != id { + return + } + + baseName := path.Base(link) + for searchLink, searchID := range searchLinks { + if path.Base(searchLink) == baseName { + return searchID + } + } + return +} + +func cleanImport() { + importTrees = []*parse.Tree{} + searchLinks = map[string]string{} +}