From bb4e8c9a8eb6de14dfdc4f5db69a2dd18929472c Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Sun, 28 Jan 2024 00:23:47 +0800 Subject: [PATCH] :art: Add a Ref export mode `Anchor hash` for notebook Markdown exporting https://github.com/siyuan-note/siyuan/issues/10265 --- kernel/model/export.go | 128 ++++++++++++++++++++++++++++++++--------- 1 file changed, 101 insertions(+), 27 deletions(-) diff --git a/kernel/model/export.go b/kernel/model/export.go index 8d1cb113b..0bf005791 100644 --- a/kernel/model/export.go +++ b/kernel/model/export.go @@ -248,7 +248,7 @@ func Export2Liandi(id string) (err error) { 4, 1, 0, "#", "#", "", "", - false) + false, nil) result := gulu.Ret.NewResult() request := httpclient.NewCloudRequest30s() request = request. @@ -1316,7 +1316,7 @@ func ExportStdMarkdown(id string) string { Conf.Export.BlockRefMode, Conf.Export.BlockEmbedMode, Conf.Export.FileAnnotationRefMode, Conf.Export.TagOpenMarker, Conf.Export.TagCloseMarker, Conf.Export.BlockRefTextLeft, Conf.Export.BlockRefTextRight, - Conf.Export.AddTitle) + Conf.Export.AddTitle, nil) } func ExportPandocConvertZip(id, pandocTo, ext string) (name, zipPath string) { @@ -1338,7 +1338,7 @@ func ExportPandocConvertZip(id, pandocTo, ext string) (name, zipPath string) { docPaths = append(docPaths, docFile.path) } - zipPath = exportPandocConvertZip(boxID, baseFolderName, docPaths, "gfm+footnotes+hard_line_breaks", pandocTo, ext) + zipPath = exportPandocConvertZip(false, boxID, baseFolderName, docPaths, Conf.Export.BlockRefMode, "gfm+footnotes+hard_line_breaks", pandocTo, ext) name = strings.TrimSuffix(filepath.Base(block.Path), ".sy") return } @@ -1366,7 +1366,7 @@ func BatchExportMarkdown(boxID, folderPath string) (zipPath string) { for _, docFile := range docFiles { docPaths = append(docPaths, docFile.path) } - zipPath = exportPandocConvertZip(boxID, baseFolderName, docPaths, "", "", ".md") + zipPath = exportPandocConvertZip(true, boxID, baseFolderName, docPaths, Conf.Export.BlockRefMode, "", "", ".md") return } @@ -1762,10 +1762,10 @@ func walkRelationAvs(avID string, exportAvIDs *hashset.Set) { } func ExportMarkdownContent(id string) (hPath, exportedMd string) { - return exportMarkdownContent(id) + return exportMarkdownContent(id, Conf.Export.BlockRefMode, nil) } -func exportMarkdownContent(id string) (hPath, exportedMd string) { +func exportMarkdownContent(id string, exportRefMode int, defBlockIDs []string) (hPath, exportedMd string) { tree, err := loadTreeByBlockID(id) if nil != err { logging.LogErrorf("load tree by block id [%s] failed: %s", id, err) @@ -1773,10 +1773,10 @@ func exportMarkdownContent(id string) (hPath, exportedMd string) { } hPath = tree.HPath exportedMd = exportMarkdownContent0(tree, "", false, - Conf.Export.BlockRefMode, Conf.Export.BlockEmbedMode, Conf.Export.FileAnnotationRefMode, + exportRefMode, Conf.Export.BlockEmbedMode, Conf.Export.FileAnnotationRefMode, Conf.Export.TagOpenMarker, Conf.Export.TagCloseMarker, Conf.Export.BlockRefTextLeft, Conf.Export.BlockRefTextRight, - Conf.Export.AddTitle) + Conf.Export.AddTitle, defBlockIDs) docIAL := parse.IAL2Map(tree.Root.KramdownIAL) exportedMd = yfm(docIAL) + exportedMd return @@ -1786,7 +1786,8 @@ func exportMarkdownContent0(tree *parse.Tree, cloudAssetsBase string, assetsDest blockRefMode, blockEmbedMode, fileAnnotationRefMode int, tagOpenMarker, tagCloseMarker string, blockRefTextLeft, blockRefTextRight string, - addTitle bool) (ret string) { + addTitle bool, + defBlockIDs []string) (ret string) { tree = exportTree(tree, false, true, false, blockRefMode, blockEmbedMode, fileAnnotationRefMode, tagOpenMarker, tagCloseMarker, @@ -1818,7 +1819,6 @@ func exportMarkdownContent0(tree *parse.Tree, cloudAssetsBase string, assetsDest }) } - // When exporting Markdown, `
` nodes in non-tables are replaced with `\n` text nodes https://github.com/siyuan-note/siyuan/issues/9509 var unlinks []*ast.Node ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { if !entering { @@ -1827,10 +1827,44 @@ func exportMarkdownContent0(tree *parse.Tree, cloudAssetsBase string, assetsDest if ast.NodeBr == n.Type { if !n.ParentIs(ast.NodeTableCell) { + // When exporting Markdown, `
` nodes in non-tables are replaced with `\n` text nodes https://github.com/siyuan-note/siyuan/issues/9509 n.InsertBefore(&ast.Node{Type: ast.NodeText, Tokens: []byte("\n")}) unlinks = append(unlinks, n) } } + + if 5 == blockRefMode { // 锚点哈希 + if n.IsBlock() && gulu.Str.Contains(n.ID, defBlockIDs) { + // 如果是定义块,则在开头处添加锚点 + anchorSpan := &ast.Node{Type: ast.NodeInlineHTML, Tokens: []byte("")} + if ast.NodeDocument != n.Type { + if nil != n.FirstChild { + n.FirstChild.InsertBefore(anchorSpan) + } else { + n.AppendChild(anchorSpan) + } + } + } + + if treenode.IsBlockRef(n) { + // 如果是引用元素,则将其转换为超链接,指向 xxx.md#block-id + defID, linkText := getExportBlockRefLinkText(n, blockRefTextLeft, blockRefTextRight) + if gulu.Str.Contains(defID, defBlockIDs) { + var href string + bt := treenode.GetBlockTree(defID) + if nil != bt { + href += strings.TrimPrefix(bt.HPath, "/") + ".md" + if "d" != bt.Type { + href += "#" + defID + } + } + blockRefLink := &ast.Node{Type: ast.NodeTextMark, TextMarkType: "a", TextMarkTextContent: linkText, TextMarkAHref: href} + blockRefLink.KramdownIAL = n.KramdownIAL + n.InsertBefore(blockRefLink) + unlinks = append(unlinks, n) + } + } + } return ast.WalkContinue }) for _, unlink := range unlinks { @@ -2017,16 +2051,7 @@ func exportTree(tree *parse.Tree, wysiwyg, expandKaTexMacros, keepFold bool, // 处理引用节点 - defID, linkText, _ := treenode.GetBlockRef(n) - if "" == linkText { - linkText = sql.GetRefText(defID) - } - linkText = html.UnescapeHTMLStr(linkText) // 块引锚文本导出时 `&` 变为实体 `&` https://github.com/siyuan-note/siyuan/issues/7659 - if Conf.Editor.BlockRefDynamicAnchorTextMaxLen < utf8.RuneCountInString(linkText) { - linkText = gulu.Str.SubStr(linkText, Conf.Editor.BlockRefDynamicAnchorTextMaxLen) + "..." - } - linkText = blockRefTextLeft + linkText + blockRefTextRight - + defID, linkText := getExportBlockRefLinkText(n, blockRefTextLeft, blockRefTextRight) defTree, _ := loadTreeByBlockID(defID) if nil == defTree { return ast.WalkContinue @@ -2034,21 +2059,24 @@ func exportTree(tree *parse.Tree, wysiwyg, expandKaTexMacros, keepFold bool, switch blockRefMode { case 2: // 锚文本块链 - var blockRefLink *ast.Node - blockRefLink = &ast.Node{Type: ast.NodeTextMark, TextMarkType: "a", TextMarkTextContent: linkText, TextMarkAHref: "siyuan://blocks/" + defID} + blockRefLink := &ast.Node{Type: ast.NodeTextMark, TextMarkType: "a", TextMarkTextContent: linkText, TextMarkAHref: "siyuan://blocks/" + defID} blockRefLink.KramdownIAL = n.KramdownIAL n.InsertBefore(blockRefLink) + unlinks = append(unlinks, n) case 3: // 仅锚文本 - var blockRefLink *ast.Node - blockRefLink = &ast.Node{Type: ast.NodeTextMark, TextMarkType: "text", TextMarkTextContent: linkText} + blockRefLink := &ast.Node{Type: ast.NodeTextMark, TextMarkType: "text", TextMarkTextContent: linkText} blockRefLink.KramdownIAL = n.KramdownIAL n.InsertBefore(blockRefLink) + unlinks = append(unlinks, n) case 4: // 脚注 refFoot := getRefAsFootnotes(defID, &refFootnotes) n.InsertBefore(&ast.Node{Type: ast.NodeText, Tokens: []byte(linkText)}) n.InsertBefore(&ast.Node{Type: ast.NodeFootnotesRef, Tokens: []byte("^" + refFoot.refNum), FootnotesRefId: refFoot.refNum, FootnotesRefLabel: []byte("^" + refFoot.refNum)}) + unlinks = append(unlinks, n) + case 5: // 锚点哈希 + // 此处不做任何处理 } - unlinks = append(unlinks, n) + if nil != n.Next && ast.NodeKramdownSpanIAL == n.Next.Type { // 引用加排版标记(比如颜色)重叠时丢弃后面的排版属性节点 unlinks = append(unlinks, n.Next) @@ -2555,7 +2583,7 @@ func processFileAnnotationRef(refID string, n *ast.Node, fileAnnotationRefMode i return ast.WalkSkipChildren } -func exportPandocConvertZip(boxID, baseFolderName string, docPaths []string, +func exportPandocConvertZip(exportNotebook bool, boxID, baseFolderName string, docPaths []string, exportRefMode int, pandocFrom, pandocTo, ext string) (zipPath string) { dir, name := path.Split(baseFolderName) name = util.FilterFileName(name) @@ -2574,6 +2602,35 @@ func exportPandocConvertZip(boxID, baseFolderName string, docPaths []string, return } + var defBlockIDs []string + if exportNotebook && 5 == exportRefMode { + // Add a Ref export mode `Anchor hash` for notebook Markdown exporting https://github.com/siyuan-note/siyuan/issues/10265 + // 导出笔记本时导出锚点哈希,这里先记录下所有定义块的 ID + for _, p := range docPaths { + docIAL := box.docIAL(p) + if nil == docIAL { + continue + } + id := docIAL["id"] + tree, err := loadTreeByBlockID(id) + if nil != err { + continue + } + ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { + if !entering { + return ast.WalkContinue + } + + if treenode.IsBlockRef(n) { + defID, _, _ := treenode.GetBlockRef(n) + defBlockIDs = append(defBlockIDs, defID) + } + return ast.WalkContinue + }) + } + defBlockIDs = gulu.Str.RemoveDuplicatedElem(defBlockIDs) + } + luteEngine := util.NewLute() for _, p := range docPaths { docIAL := box.docIAL(p) @@ -2582,7 +2639,7 @@ func exportPandocConvertZip(boxID, baseFolderName string, docPaths []string, } id := docIAL["id"] - hPath, md := exportMarkdownContent(id) + hPath, md := exportMarkdownContent(id, exportRefMode, defBlockIDs) dir, name = path.Split(hPath) dir = util.FilterFilePath(dir) // 导出文档时未移除不支持的文件名符号 https://github.com/siyuan-note/siyuan/issues/4590 name = util.FilterFileName(name) @@ -2610,6 +2667,10 @@ func exportPandocConvertZip(boxID, baseFolderName string, docPaths []string, asset = asset[:strings.LastIndex(asset, "?")] } + if !strings.HasPrefix(asset, "assets/") { + continue + } + srcPath, err := GetAssetAbsPath(asset) if nil != err { logging.LogWarnf("get asset [%s] abs path failed: %s", asset, err) @@ -2666,3 +2727,16 @@ func exportPandocConvertZip(boxID, baseFolderName string, docPaths []string, zipPath = "/export/" + url.PathEscape(filepath.Base(zipPath)) return } + +func getExportBlockRefLinkText(blockRef *ast.Node, blockRefTextLeft, blockRefTextRight string) (defID, linkText string) { + defID, linkText, _ = treenode.GetBlockRef(blockRef) + if "" == linkText { + linkText = sql.GetRefText(defID) + } + linkText = html.UnescapeHTMLStr(linkText) // 块引锚文本导出时 `&` 变为实体 `&` https://github.com/siyuan-note/siyuan/issues/7659 + if Conf.Editor.BlockRefDynamicAnchorTextMaxLen < utf8.RuneCountInString(linkText) { + linkText = gulu.Str.SubStr(linkText, Conf.Editor.BlockRefDynamicAnchorTextMaxLen) + "..." + } + linkText = blockRefTextLeft + linkText + blockRefTextRight + return +}