From 91ae813000ff5c8cb95cf01496b8c0ff9695b98f Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Sun, 28 Jan 2024 00:22:47 +0800 Subject: [PATCH 1/7] :art: Add a Ref export mode `Anchor hash` for notebook Markdown exporting https://github.com/siyuan-note/siyuan/issues/10265 --- kernel/conf/export.go | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/kernel/conf/export.go b/kernel/conf/export.go index 0bfbf1962..38012b671 100644 --- a/kernel/conf/export.go +++ b/kernel/conf/export.go @@ -17,23 +17,29 @@ package conf type Export struct { - ParagraphBeginningSpace bool `json:"paragraphBeginningSpace"` // 是否使用中文排版段落开头空两格 - AddTitle bool `json:"addTitle"` // 是否添加标题 - BlockRefMode int `json:"blockRefMode"` // 内容块引用导出模式,2:锚文本块链,3:仅锚文本,4:块引转脚注,(0:使用原始文本,1:使用 Blockquote。0 和 1 都已经废弃 https://github.com/siyuan-note/siyuan/issues/3155) - BlockEmbedMode int `json:"blockEmbedMode"` // 内容块引用导出模式,0:使用原始文本,1:使用 Blockquote - BlockRefTextLeft string `json:"blockRefTextLeft"` // 内容块引用导出锚文本左侧符号,默认留空 - BlockRefTextRight string `json:"blockRefTextRight"` // 内容块引用导出锚文本右侧符号,默认留空 - TagOpenMarker string `json:"tagOpenMarker"` // 标签开始标记符,默认是 # - TagCloseMarker string `json:"tagCloseMarker"` // 标签结束标记符,默认是 # - FileAnnotationRefMode int `json:"fileAnnotationRefMode"` // 文件标注引用导出模式,0:文件名 - 页码 - 锚文本,1:仅锚文本 - PandocBin string `json:"pandocBin"` // Pandoc 可执行文件路径 - MarkdownYFM bool `json:"markdownYFM"` // Markdown 导出时是否添加 YAML Front Matter https://github.com/siyuan-note/siyuan/issues/7727 - PDFFooter string `json:"pdfFooter"` // PDF 导出时页脚内容 - DocxTemplate string `json:"docxTemplate"` // Docx 导出时模板文件路径 - PDFWatermarkStr string `json:"pdfWatermarkStr"` // PDF 导出时水印文本或水印文件路径 - PDFWatermarkDesc string `json:"pdfWatermarkDesc"` // PDF 导出时水印位置、大小和样式等 - ImageWatermarkStr string `json:"imageWatermarkStr"` // 图片导出时水印文本或水印文件路径 - ImageWatermarkDesc string `json:"imageWatermarkDesc"` // 图片导出时水印位置、大小和样式等 + ParagraphBeginningSpace bool `json:"paragraphBeginningSpace"` // 是否使用中文排版段落开头空两格 + AddTitle bool `json:"addTitle"` // 是否添加标题 + // 内容块引用导出模式 + // 2:锚文本块链 + // 3:仅锚文本 + // 4:块引转脚注 + // 5:锚点哈希 https://github.com/siyuan-note/siyuan/issues/10265 + // (0:使用原始文本,1:使用 Blockquote。0 和 1 都已经废弃 https://github.com/siyuan-note/siyuan/issues/3155) + BlockRefMode int `json:"blockRefMode"` + BlockEmbedMode int `json:"blockEmbedMode"` // 内容块引用导出模式,0:使用原始文本,1:使用 Blockquote + BlockRefTextLeft string `json:"blockRefTextLeft"` // 内容块引用导出锚文本左侧符号,默认留空 + BlockRefTextRight string `json:"blockRefTextRight"` // 内容块引用导出锚文本右侧符号,默认留空 + TagOpenMarker string `json:"tagOpenMarker"` // 标签开始标记符,默认是 # + TagCloseMarker string `json:"tagCloseMarker"` // 标签结束标记符,默认是 # + FileAnnotationRefMode int `json:"fileAnnotationRefMode"` // 文件标注引用导出模式,0:文件名 - 页码 - 锚文本,1:仅锚文本 + PandocBin string `json:"pandocBin"` // Pandoc 可执行文件路径 + MarkdownYFM bool `json:"markdownYFM"` // Markdown 导出时是否添加 YAML Front Matter https://github.com/siyuan-note/siyuan/issues/7727 + PDFFooter string `json:"pdfFooter"` // PDF 导出时页脚内容 + DocxTemplate string `json:"docxTemplate"` // Docx 导出时模板文件路径 + PDFWatermarkStr string `json:"pdfWatermarkStr"` // PDF 导出时水印文本或水印文件路径 + PDFWatermarkDesc string `json:"pdfWatermarkDesc"` // PDF 导出时水印位置、大小和样式等 + ImageWatermarkStr string `json:"imageWatermarkStr"` // 图片导出时水印文本或水印文件路径 + ImageWatermarkDesc string `json:"imageWatermarkDesc"` // 图片导出时水印位置、大小和样式等 } func NewExport() *Export { 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 2/7] :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 +} From 0dd90ecef656a05e23c75c8e0adf7b317d3fa4c0 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Sun, 28 Jan 2024 10:13:51 +0800 Subject: [PATCH 3/7] :art: Add internal kernel API `/api/setting/setEditorReadOnly` https://github.com/siyuan-note/siyuan/issues/10268 --- kernel/api/router.go | 1 + kernel/api/setting.go | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/kernel/api/router.go b/kernel/api/router.go index 0be3f8526..271f30306 100644 --- a/kernel/api/router.go +++ b/kernel/api/router.go @@ -317,6 +317,7 @@ func ServeAPI(ginServer *gin.Engine) { ginServer.Handle("POST", "/api/setting/addVirtualBlockRefInclude", model.CheckAuth, model.CheckReadonly, addVirtualBlockRefInclude) ginServer.Handle("POST", "/api/setting/addVirtualBlockRefExclude", model.CheckAuth, model.CheckReadonly, addVirtualBlockRefExclude) ginServer.Handle("POST", "/api/setting/setSnippet", model.CheckAuth, model.CheckReadonly, setConfSnippet) + ginServer.Handle("POST", "/api/setting/setEditorReadOnly", model.CheckAuth, model.CheckReadonly, setEditorReadOnly) ginServer.Handle("POST", "/api/graph/resetGraph", model.CheckAuth, model.CheckReadonly, resetGraph) ginServer.Handle("POST", "/api/graph/resetLocalGraph", model.CheckAuth, model.CheckReadonly, resetLocalGraph) diff --git a/kernel/api/setting.go b/kernel/api/setting.go index e9c9c9d33..126966952 100644 --- a/kernel/api/setting.go +++ b/kernel/api/setting.go @@ -29,6 +29,27 @@ import ( "github.com/siyuan-note/siyuan/kernel/util" ) +func setEditorReadOnly(c *gin.Context) { + ret := gulu.Ret.NewResult() + defer c.JSON(http.StatusOK, ret) + + arg, ok := util.JsonArg(c, ret) + if !ok { + return + } + + readOnly := arg["readonly"].(bool) + + oldReadOnly := model.Conf.Editor.ReadOnly + model.Conf.Editor.ReadOnly = readOnly + model.Conf.Save() + + if oldReadOnly != model.Conf.Editor.ReadOnly { + util.BroadcastByType("protyle", "readonly", 0, "", model.Conf.Editor.ReadOnly) + util.BroadcastByType("main", "readonly", 0, "", model.Conf.Editor.ReadOnly) + } +} + func setConfSnippet(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret) From 9ffa5b7e8256ce22faafc0591843c85a5693ecc4 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Sun, 28 Jan 2024 10:35:09 +0800 Subject: [PATCH 4/7] :art: Add a Ref export mode `Anchor hash` for notebook Markdown exporting https://github.com/siyuan-note/siyuan/issues/10265 --- app/appearance/langs/en_US.json | 3 ++- app/appearance/langs/es_ES.json | 3 ++- app/appearance/langs/fr_FR.json | 3 ++- app/appearance/langs/zh_CHT.json | 3 ++- app/appearance/langs/zh_CN.json | 3 ++- app/src/config/exportConfig.ts | 3 ++- app/src/protyle/export/util.ts | 2 +- kernel/model/export.go | 12 +++++++++--- 8 files changed, 22 insertions(+), 10 deletions(-) diff --git a/app/appearance/langs/en_US.json b/app/appearance/langs/en_US.json index c793a4980..5b9e78212 100644 --- a/app/appearance/langs/en_US.json +++ b/app/appearance/langs/en_US.json @@ -957,7 +957,7 @@ "export27": "Export PDF watermark", "export28": "Watermark text or watermark file path", "export29": "Watermark position, size and style, etc.", - "export9": "Export image watermark", + "export30": "Export image watermark", "theme11": "Use theme in light mode", "theme12": "Use theme in dark mode", "theme2": "Select the icons used in the user interface", @@ -975,6 +975,7 @@ "export6": "About the handling of anchor text in PDF annotations when exporting", "export7": "File Name - Page Number - Anchor Text", "export8": "Just anchor text", + "export9": "Anchor hash (only for exporting Notebook)", "graphConfig2": "Reference Count filter", "selectOpen": "Always Select Opened Doc", "selectOpen1": "Select Opened Doc", diff --git a/app/appearance/langs/es_ES.json b/app/appearance/langs/es_ES.json index f1b9c6a63..6916a8166 100644 --- a/app/appearance/langs/es_ES.json +++ b/app/appearance/langs/es_ES.json @@ -957,7 +957,7 @@ "export27": "Exportar marca de agua PDF", "export28": "Texto de marca de agua o ruta del archivo de marca de agua", "export29": "Posición, tamaño y estilo de la marca de agua, etc.", - "export9": "Exportar marca de agua de imagen", + "export30": "Exportar marca de agua de imagen", "theme11": "Usar tema en modo claro", "theme12": "Usar tema en modo oscuro", "theme2": "Selecciona los iconos utilizados en la interfaz de usuario", @@ -975,6 +975,7 @@ "export6": "Sobre el manejo del texto ancla en las anotaciones PDF al exportar", "export7": "Nombre de archivo - Número de página - Texto ancla", "export8": "Sólo texto ancla", + "export9": "Hash de anclaje (sólo para exportar Notebook)", "graphConfig2": "Filtro de recuento de referencias", "selectOpen": "Seleccionar siempre el documento abierto", "selectOpen1": "Seleccionar documento abierto", diff --git a/app/appearance/langs/fr_FR.json b/app/appearance/langs/fr_FR.json index 58afe8d05..f3484456e 100644 --- a/app/appearance/langs/fr_FR.json +++ b/app/appearance/langs/fr_FR.json @@ -957,7 +957,7 @@ "export27": "Exporter le filigrane PDF", "export28": "Texte du filigrane ou chemin du fichier de filigrane", "export29": "Position, taille et style du filigrane, etc.", - "export9": "Exporter le filigrane de l'image", + "export30": "Exporter le filigrane de l'image", "theme11": "Utiliser le thème en mode Clair", "theme12": "Utiliser le thème en mode sombre", "theme2": "Sélectionnez les icônes utilisées dans l'interface utilisateur", @@ -975,6 +975,7 @@ "export6": "À propos de la gestion du texte d'ancrage dans les annotations PDF lors de l'exportation", "export7": "Nom de fichier - Numéro de page - Texte d'ancrage", "export8": "Anchor text only", + "export9": "Hash d'ancrage (uniquement pour l'exportation de Notebook)", "graphConfig2": "Filtre de compte de blocs de référence", "selectOpen": "Localisez toujours les documents ouverts", "selectOpen1": "Localiser les documents ouverts", diff --git a/app/appearance/langs/zh_CHT.json b/app/appearance/langs/zh_CHT.json index e21649ce9..ac9e40e32 100644 --- a/app/appearance/langs/zh_CHT.json +++ b/app/appearance/langs/zh_CHT.json @@ -957,7 +957,7 @@ "export27": "導出 PDF 浮水印", "export28": "浮水印文字或浮水印檔案路徑", "export29": "浮水印位置、大小和樣式等", - "export9": "匯出圖片浮水印", + "export30": "匯出圖片浮水印", "theme11": "淺色模式下使用主題", "theme12": "深色模式下使用主題", "theme2": "選擇外觀使用的圖示", @@ -975,6 +975,7 @@ "export6": "導出時關於 PDF 標註引出處錨文字的處理方式", "export7": "文件名 - 頁碼 - 錨文字", "export8": "僅錨文字", + "export9": "錨點哈希(僅支援導出筆記本)", "graphConfig2": "引用塊次數過濾", "selectOpen": "定位打開的文檔", "selectOpen1": "定位打開的文檔", diff --git a/app/appearance/langs/zh_CN.json b/app/appearance/langs/zh_CN.json index 7881a0c34..a88395198 100644 --- a/app/appearance/langs/zh_CN.json +++ b/app/appearance/langs/zh_CN.json @@ -957,7 +957,7 @@ "export27": "导出 PDF 水印", "export28": "水印文本或水印文件路径", "export29": "水印位置、大小和样式等", - "export9": "导出图片水印", + "export30": "导出图片水印", "theme11": "明亮模式下使用主题", "theme12": "暗黑模式下使用主题", "theme2": "选择外观使用的图标", @@ -975,6 +975,7 @@ "export6": "导出时关于 PDF 标注引出处锚文本的处理方式", "export7": "文件名 - 页码 - 锚文本", "export8": "仅锚文本", + "export9": "锚点哈希(仅支持导出笔记本)", "graphConfig2": "引用块次数过滤", "selectOpen": "始终定位打开的文档", "selectOpen1": "定位打开的文档", diff --git a/app/src/config/exportConfig.ts b/app/src/config/exportConfig.ts index bad8637a3..392a355f0 100644 --- a/app/src/config/exportConfig.ts +++ b/app/src/config/exportConfig.ts @@ -46,6 +46,7 @@ export const exportConfig = { +
@@ -88,7 +89,7 @@ export const exportConfig = {
- ${window.siyuan.languages.export9} + ${window.siyuan.languages.export30}
${window.siyuan.languages.export28}
diff --git a/app/src/protyle/export/util.ts b/app/src/protyle/export/util.ts index 1f0de91d0..3697c4544 100644 --- a/app/src/protyle/export/util.ts +++ b/app/src/protyle/export/util.ts @@ -42,7 +42,7 @@ export const exportImage = (id: string) => { diff --git a/kernel/model/export.go b/kernel/model/export.go index 0bf005791..a26753307 100644 --- a/kernel/model/export.go +++ b/kernel/model/export.go @@ -1338,7 +1338,7 @@ func ExportPandocConvertZip(id, pandocTo, ext string) (name, zipPath string) { docPaths = append(docPaths, docFile.path) } - zipPath = exportPandocConvertZip(false, boxID, baseFolderName, docPaths, Conf.Export.BlockRefMode, "gfm+footnotes+hard_line_breaks", pandocTo, ext) + zipPath = exportPandocConvertZip(false, boxID, baseFolderName, docPaths, "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(true, boxID, baseFolderName, docPaths, Conf.Export.BlockRefMode, "", "", ".md") + zipPath = exportPandocConvertZip(true, boxID, baseFolderName, docPaths, "", "", ".md") return } @@ -2583,7 +2583,7 @@ func processFileAnnotationRef(refID string, n *ast.Node, fileAnnotationRefMode i return ast.WalkSkipChildren } -func exportPandocConvertZip(exportNotebook bool, boxID, baseFolderName string, docPaths []string, exportRefMode int, +func exportPandocConvertZip(exportNotebook bool, boxID, baseFolderName string, docPaths []string, pandocFrom, pandocTo, ext string) (zipPath string) { dir, name := path.Split(baseFolderName) name = util.FilterFileName(name) @@ -2602,6 +2602,12 @@ func exportPandocConvertZip(exportNotebook bool, boxID, baseFolderName string, d return } + exportRefMode := Conf.Export.BlockRefMode + if !exportNotebook && 5 == exportRefMode { + // 非笔记本导出不支持锚点哈希,将其切换为锚文本块链 + exportRefMode = 2 + } + 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 From f59f738e5eddc591601d79f6ff948650184bf29b Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Sun, 28 Jan 2024 10:51:31 +0800 Subject: [PATCH 5/7] :art: Add a Ref export mode `Anchor hash` for notebook Markdown exporting https://github.com/siyuan-note/siyuan/issues/10265 --- kernel/model/export.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kernel/model/export.go b/kernel/model/export.go index a26753307..d34a64b24 100644 --- a/kernel/model/export.go +++ b/kernel/model/export.go @@ -1838,8 +1838,9 @@ func exportMarkdownContent0(tree *parse.Tree, cloudAssetsBase string, assetsDest // 如果是定义块,则在开头处添加锚点 anchorSpan := &ast.Node{Type: ast.NodeInlineHTML, Tokens: []byte("")} if ast.NodeDocument != n.Type { - if nil != n.FirstChild { - n.FirstChild.InsertBefore(anchorSpan) + firstLeaf := treenode.FirstLeafBlock(n) + if nil != firstLeaf && nil != firstLeaf.FirstChild { + firstLeaf.FirstChild.InsertBefore(anchorSpan) } else { n.AppendChild(anchorSpan) } From af73610010075f5659c0eefc55c549272cdd6982 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Sun, 28 Jan 2024 11:36:54 +0800 Subject: [PATCH 6/7] :memo: Update flashcard user guide chapter --- .../20221223221636-ms2b4w9.sy | 244 +++++++++++++++++- .../assets/image-20240128113001-pee8l86.png | Bin 0 -> 2764 bytes .../20221223215557-o6gfsoy.sy | 244 +++++++++++++++++- .../assets/image-20240128111414-l8l7d89.png | Bin 0 -> 2764 bytes .../20221223221501-mops33i.sy | 244 +++++++++++++++++- .../assets/image-20240128113232-ny2q076.png | Bin 0 -> 2764 bytes 6 files changed, 726 insertions(+), 6 deletions(-) create mode 100644 app/guide/20210808180117-6v0mkxr/assets/image-20240128113001-pee8l86.png create mode 100644 app/guide/20210808180117-czj9bvb/assets/image-20240128111414-l8l7d89.png create mode 100644 app/guide/20211226090932-5lcq56f/assets/image-20240128113232-ny2q076.png diff --git a/app/guide/20210808180117-6v0mkxr/20200923234011-ieuun1p/20210808180303-xaduj2o/20221223221636-ms2b4w9.sy b/app/guide/20210808180117-6v0mkxr/20200923234011-ieuun1p/20210808180303-xaduj2o/20221223221636-ms2b4w9.sy index 26a841453..23c8c2aa4 100644 --- a/app/guide/20210808180117-6v0mkxr/20200923234011-ieuun1p/20210808180303-xaduj2o/20221223221636-ms2b4w9.sy +++ b/app/guide/20210808180117-6v0mkxr/20200923234011-ieuun1p/20210808180303-xaduj2o/20221223221636-ms2b4w9.sy @@ -3,9 +3,10 @@ "Spec": "1", "Type": "NodeDocument", "Properties": { + "icon": "1f4f8", "id": "20221223221636-ms2b4w9", "title": "Flashcards", - "updated": "20231013104018" + "updated": "20240128113415" }, "Children": [ { @@ -518,6 +519,217 @@ } ] }, + { + "ID": "20240128112003-595bhoa", + "Type": "NodeHeading", + "HeadingLevel": 2, + "Properties": { + "id": "20240128112003-595bhoa", + "updated": "20240128112005" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "Settings" + } + ] + }, + { + "ID": "20240128112016-je04b7i", + "Type": "NodeParagraph", + "Properties": { + "id": "20240128112016-je04b7i", + "updated": "20240128112236" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "You can adjust the global configuration related to flashcards in " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "Settings" + }, + { + "Type": "NodeText", + "Data": "​ - " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "Flashcard" + }, + { + "Type": "NodeText", + "Data": "​, including " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "New Card Limit" + }, + { + "Type": "NodeText", + "Data": "​ and " + }, + { + "Type": "NodeTextMark", + "TextMarkType": "kbd", + "TextMarkTextContent": "Review Card Limit" + }, + { + "Type": "NodeText", + "Data": "​ can be overridden using document block custom attributes:" + } + ] + }, + { + "ID": "20240128112016-aa4ipl9", + "Type": "NodeList", + "ListData": {}, + "Properties": { + "id": "20240128112016-aa4ipl9", + "updated": "20240128112016" + }, + "Children": [ + { + "ID": "20240128112016-ta1n5t2", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240128112016-ta1n5t2", + "updated": "20240128112016" + }, + "Children": [ + { + "ID": "20240128112016-jm62abq", + "Type": "NodeParagraph", + "Properties": { + "id": "20240128112016-jm62abq", + "updated": "20240128112016" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "​" + }, + { + "Type": "NodeTextMark", + "TextMarkType": "code", + "TextMarkTextContent": "riff-new-card-limit" + }, + { + "Type": "NodeText", + "Data": "​​" + } + ] + } + ] + }, + { + "ID": "20240128112016-3s0opfu", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240128112016-3s0opfu", + "updated": "20240128112016" + }, + "Children": [ + { + "ID": "20240128112016-ozz5ukr", + "Type": "NodeParagraph", + "Properties": { + "id": "20240128112016-ozz5ukr", + "updated": "20240128112016" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "​" + }, + { + "Type": "NodeTextMark", + "TextMarkType": "code", + "TextMarkTextContent": "riff-review-card-limit" + }, + { + "Type": "NodeText", + "Data": "​​" + } + ] + } + ] + } + ] + }, + { + "ID": "20240128112146-85828oq", + "Type": "NodeParagraph", + "Properties": { + "id": "20240128112146-85828oq", + "updated": "20240128113415" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "​" + }, + { + "Type": "NodeImage", + "Data": "span", + "Children": [ + { + "Type": "NodeBang" + }, + { + "Type": "NodeOpenBracket" + }, + { + "Type": "NodeLinkText", + "Data": "image" + }, + { + "Type": "NodeCloseBracket" + }, + { + "Type": "NodeOpenParen" + }, + { + "Type": "NodeLinkDest", + "Data": "assets/image-20240128113001-pee8l86.png" + }, + { + "Type": "NodeCloseParen" + } + ] + }, + { + "Type": "NodeText", + "Data": "​" + } + ] + }, + { + "ID": "20240128112208-ulxm0sf", + "Type": "NodeParagraph", + "Properties": { + "id": "20240128112208-ulxm0sf", + "updated": "20240128112214" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "They are configured separately on different documents and do not affect each other. For example, if the parent document is configured as 5 and the sub-document is 10, then it will be 5 when reviewing from the parent document, and the configuration of the sub-document will not be superimposed." + } + ] + }, { "ID": "20221223221639-djlafd4", "Type": "NodeHeading", @@ -548,7 +760,7 @@ "ListData": {}, "Properties": { "id": "20221223221639-ycrps4p", - "updated": "20221223221824" + "updated": "20240128112834" }, "Children": [ { @@ -578,6 +790,34 @@ ] } ] + }, + { + "ID": "20240128112355-nzfivrh", + "Type": "NodeListItem", + "ListData": { + "BulletChar": 42, + "Marker": "Kg==" + }, + "Properties": { + "id": "20240128112355-nzfivrh", + "updated": "20240128112834" + }, + "Children": [ + { + "ID": "20240128112355-ie927dm", + "Type": "NodeParagraph", + "Properties": { + "id": "20240128112355-ie927dm", + "updated": "20240128112834" + }, + "Children": [ + { + "Type": "NodeText", + "Data": "It is not recommended to enable card decks. This method has been deprecated. If you continue to use it, you may encounter unsolvable problems" + } + ] + } + ] } ] }, diff --git a/app/guide/20210808180117-6v0mkxr/assets/image-20240128113001-pee8l86.png b/app/guide/20210808180117-6v0mkxr/assets/image-20240128113001-pee8l86.png new file mode 100644 index 0000000000000000000000000000000000000000..52f690824dab0ad57f0f8873f14790185a3fae16 GIT binary patch literal 2764 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 Date: Sun, 28 Jan 2024 12:30:05 +0800 Subject: [PATCH 7/7] :bug: Revert Some symbols should not be escaped to avoid inaccurate searches https://github.com/siyuan-note/siyuan/issues/10185 --- kernel/treenode/tree.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kernel/treenode/tree.go b/kernel/treenode/tree.go index 324ea87d3..154a37f6f 100644 --- a/kernel/treenode/tree.go +++ b/kernel/treenode/tree.go @@ -28,7 +28,6 @@ import ( "github.com/88250/gulu" "github.com/88250/lute" "github.com/88250/lute/ast" - "github.com/88250/lute/html" "github.com/88250/lute/parse" "github.com/siyuan-note/filelock" "github.com/siyuan-note/siyuan/kernel/util" @@ -88,7 +87,9 @@ func IALStr(n *ast.Node) string { if 1 > len(n.KramdownIAL) { return "" } - return html.UnescapeString(string(parse.IAL2Tokens(n.KramdownIAL))) + // 这里不能进行转义,否则会导致从数据库中读取后转换为 IAL 时解析错误 + // 所以 Some symbols should not be escaped to avoid inaccurate searches https://github.com/siyuan-note/siyuan/issues/10185 无法被修复了 + return string(parse.IAL2Tokens(n.KramdownIAL)) } func RootChildIDs(rootID string) (ret []string) {