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 = {
+