Browse Source

:art: Add a Ref export mode `Anchor hash` for notebook Markdown exporting https://github.com/siyuan-note/siyuan/issues/10265

Daniel 1 year ago
parent
commit
bb4e8c9a8e
1 changed files with 101 additions and 27 deletions
  1. 101 27
      kernel/model/export.go

+ 101 - 27
kernel/model/export.go

@@ -248,7 +248,7 @@ func Export2Liandi(id string) (err error) {
 		4, 1, 0,
 		4, 1, 0,
 		"#", "#",
 		"#", "#",
 		"", "",
 		"", "",
-		false)
+		false, nil)
 	result := gulu.Ret.NewResult()
 	result := gulu.Ret.NewResult()
 	request := httpclient.NewCloudRequest30s()
 	request := httpclient.NewCloudRequest30s()
 	request = request.
 	request = request.
@@ -1316,7 +1316,7 @@ func ExportStdMarkdown(id string) string {
 		Conf.Export.BlockRefMode, Conf.Export.BlockEmbedMode, Conf.Export.FileAnnotationRefMode,
 		Conf.Export.BlockRefMode, Conf.Export.BlockEmbedMode, Conf.Export.FileAnnotationRefMode,
 		Conf.Export.TagOpenMarker, Conf.Export.TagCloseMarker,
 		Conf.Export.TagOpenMarker, Conf.Export.TagCloseMarker,
 		Conf.Export.BlockRefTextLeft, Conf.Export.BlockRefTextRight,
 		Conf.Export.BlockRefTextLeft, Conf.Export.BlockRefTextRight,
-		Conf.Export.AddTitle)
+		Conf.Export.AddTitle, nil)
 }
 }
 
 
 func ExportPandocConvertZip(id, pandocTo, ext string) (name, zipPath string) {
 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)
 		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")
 	name = strings.TrimSuffix(filepath.Base(block.Path), ".sy")
 	return
 	return
 }
 }
@@ -1366,7 +1366,7 @@ func BatchExportMarkdown(boxID, folderPath string) (zipPath string) {
 	for _, docFile := range docFiles {
 	for _, docFile := range docFiles {
 		docPaths = append(docPaths, docFile.path)
 		docPaths = append(docPaths, docFile.path)
 	}
 	}
-	zipPath = exportPandocConvertZip(boxID, baseFolderName, docPaths, "", "", ".md")
+	zipPath = exportPandocConvertZip(true, boxID, baseFolderName, docPaths, Conf.Export.BlockRefMode, "", "", ".md")
 	return
 	return
 }
 }
 
 
@@ -1762,10 +1762,10 @@ func walkRelationAvs(avID string, exportAvIDs *hashset.Set) {
 }
 }
 
 
 func ExportMarkdownContent(id string) (hPath, exportedMd string) {
 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)
 	tree, err := loadTreeByBlockID(id)
 	if nil != err {
 	if nil != err {
 		logging.LogErrorf("load tree by block id [%s] failed: %s", id, 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
 	hPath = tree.HPath
 	exportedMd = exportMarkdownContent0(tree, "", false,
 	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.TagOpenMarker, Conf.Export.TagCloseMarker,
 		Conf.Export.BlockRefTextLeft, Conf.Export.BlockRefTextRight,
 		Conf.Export.BlockRefTextLeft, Conf.Export.BlockRefTextRight,
-		Conf.Export.AddTitle)
+		Conf.Export.AddTitle, defBlockIDs)
 	docIAL := parse.IAL2Map(tree.Root.KramdownIAL)
 	docIAL := parse.IAL2Map(tree.Root.KramdownIAL)
 	exportedMd = yfm(docIAL) + exportedMd
 	exportedMd = yfm(docIAL) + exportedMd
 	return
 	return
@@ -1786,7 +1786,8 @@ func exportMarkdownContent0(tree *parse.Tree, cloudAssetsBase string, assetsDest
 	blockRefMode, blockEmbedMode, fileAnnotationRefMode int,
 	blockRefMode, blockEmbedMode, fileAnnotationRefMode int,
 	tagOpenMarker, tagCloseMarker string,
 	tagOpenMarker, tagCloseMarker string,
 	blockRefTextLeft, blockRefTextRight string,
 	blockRefTextLeft, blockRefTextRight string,
-	addTitle bool) (ret string) {
+	addTitle bool,
+	defBlockIDs []string) (ret string) {
 	tree = exportTree(tree, false, true, false,
 	tree = exportTree(tree, false, true, false,
 		blockRefMode, blockEmbedMode, fileAnnotationRefMode,
 		blockRefMode, blockEmbedMode, fileAnnotationRefMode,
 		tagOpenMarker, tagCloseMarker,
 		tagOpenMarker, tagCloseMarker,
@@ -1818,7 +1819,6 @@ func exportMarkdownContent0(tree *parse.Tree, cloudAssetsBase string, assetsDest
 		})
 		})
 	}
 	}
 
 
-	// When exporting Markdown, `<br />` nodes in non-tables are replaced with `\n` text nodes https://github.com/siyuan-note/siyuan/issues/9509
 	var unlinks []*ast.Node
 	var unlinks []*ast.Node
 	ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
 	ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
 		if !entering {
 		if !entering {
@@ -1827,10 +1827,44 @@ func exportMarkdownContent0(tree *parse.Tree, cloudAssetsBase string, assetsDest
 
 
 		if ast.NodeBr == n.Type {
 		if ast.NodeBr == n.Type {
 			if !n.ParentIs(ast.NodeTableCell) {
 			if !n.ParentIs(ast.NodeTableCell) {
+				// When exporting Markdown, `<br />` 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")})
 				n.InsertBefore(&ast.Node{Type: ast.NodeText, Tokens: []byte("\n")})
 				unlinks = append(unlinks, 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("<span id=\"" + n.ID + "\"></span>")}
+				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
 		return ast.WalkContinue
 	})
 	})
 	for _, unlink := range unlinks {
 	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) // 块引锚文本导出时 `&` 变为实体 `&amp;` 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)
 		defTree, _ := loadTreeByBlockID(defID)
 		if nil == defTree {
 		if nil == defTree {
 			return ast.WalkContinue
 			return ast.WalkContinue
@@ -2034,21 +2059,24 @@ func exportTree(tree *parse.Tree, wysiwyg, expandKaTexMacros, keepFold bool,
 
 
 		switch blockRefMode {
 		switch blockRefMode {
 		case 2: // 锚文本块链
 		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
 			blockRefLink.KramdownIAL = n.KramdownIAL
 			n.InsertBefore(blockRefLink)
 			n.InsertBefore(blockRefLink)
+			unlinks = append(unlinks, n)
 		case 3: // 仅锚文本
 		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
 			blockRefLink.KramdownIAL = n.KramdownIAL
 			n.InsertBefore(blockRefLink)
 			n.InsertBefore(blockRefLink)
+			unlinks = append(unlinks, n)
 		case 4: // 脚注
 		case 4: // 脚注
 			refFoot := getRefAsFootnotes(defID, &refFootnotes)
 			refFoot := getRefAsFootnotes(defID, &refFootnotes)
 			n.InsertBefore(&ast.Node{Type: ast.NodeText, Tokens: []byte(linkText)})
 			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)})
 			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 {
 		if nil != n.Next && ast.NodeKramdownSpanIAL == n.Next.Type {
 			// 引用加排版标记(比如颜色)重叠时丢弃后面的排版属性节点
 			// 引用加排版标记(比如颜色)重叠时丢弃后面的排版属性节点
 			unlinks = append(unlinks, n.Next)
 			unlinks = append(unlinks, n.Next)
@@ -2555,7 +2583,7 @@ func processFileAnnotationRef(refID string, n *ast.Node, fileAnnotationRefMode i
 	return ast.WalkSkipChildren
 	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) {
 	pandocFrom, pandocTo, ext string) (zipPath string) {
 	dir, name := path.Split(baseFolderName)
 	dir, name := path.Split(baseFolderName)
 	name = util.FilterFileName(name)
 	name = util.FilterFileName(name)
@@ -2574,6 +2602,35 @@ func exportPandocConvertZip(boxID, baseFolderName string, docPaths []string,
 		return
 		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()
 	luteEngine := util.NewLute()
 	for _, p := range docPaths {
 	for _, p := range docPaths {
 		docIAL := box.docIAL(p)
 		docIAL := box.docIAL(p)
@@ -2582,7 +2639,7 @@ func exportPandocConvertZip(boxID, baseFolderName string, docPaths []string,
 		}
 		}
 
 
 		id := docIAL["id"]
 		id := docIAL["id"]
-		hPath, md := exportMarkdownContent(id)
+		hPath, md := exportMarkdownContent(id, exportRefMode, defBlockIDs)
 		dir, name = path.Split(hPath)
 		dir, name = path.Split(hPath)
 		dir = util.FilterFilePath(dir) // 导出文档时未移除不支持的文件名符号 https://github.com/siyuan-note/siyuan/issues/4590
 		dir = util.FilterFilePath(dir) // 导出文档时未移除不支持的文件名符号 https://github.com/siyuan-note/siyuan/issues/4590
 		name = util.FilterFileName(name)
 		name = util.FilterFileName(name)
@@ -2610,6 +2667,10 @@ func exportPandocConvertZip(boxID, baseFolderName string, docPaths []string,
 				asset = asset[:strings.LastIndex(asset, "?")]
 				asset = asset[:strings.LastIndex(asset, "?")]
 			}
 			}
 
 
+			if !strings.HasPrefix(asset, "assets/") {
+				continue
+			}
+
 			srcPath, err := GetAssetAbsPath(asset)
 			srcPath, err := GetAssetAbsPath(asset)
 			if nil != err {
 			if nil != err {
 				logging.LogWarnf("get asset [%s] abs path failed: %s", asset, 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))
 	zipPath = "/export/" + url.PathEscape(filepath.Base(zipPath))
 	return
 	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) // 块引锚文本导出时 `&` 变为实体 `&amp;` 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
+}