${window.siyuan.languages.md35}
@@ -248,6 +256,7 @@ export const editor = {
katexMacros: (editor.element.querySelector("#katexMacros") as HTMLTextAreaElement).value,
codeLineWrap: (editor.element.querySelector("#codeLineWrap") as HTMLInputElement).checked,
virtualBlockRef: (editor.element.querySelector("#virtualBlockRef") as HTMLInputElement).checked,
+ virtualBlockRefInclude: (editor.element.querySelector("#virtualBlockRefInclude") as HTMLInputElement).value,
virtualBlockRefExclude: (editor.element.querySelector("#virtualBlockRefExclude") as HTMLInputElement).value,
blockRefDynamicAnchorTextMaxLen: parseInt((editor.element.querySelector("#blockRefDynamicAnchorTextMaxLen") as HTMLInputElement).value),
codeLigatures: (editor.element.querySelector("#codeLigatures") as HTMLInputElement).checked,
diff --git a/app/src/types/index.d.ts b/app/src/types/index.d.ts
index 766a5726d..b9e8e3ae5 100644
--- a/app/src/types/index.d.ts
+++ b/app/src/types/index.d.ts
@@ -249,6 +249,7 @@ declare interface IEditor {
fontFamily: string;
virtualBlockRef: string;
virtualBlockRefExclude: string;
+ virtualBlockRefInclude: string;
blockRefDynamicAnchorTextMaxLen: number;
emoji: string[];
diff --git a/kernel/conf/editor.go b/kernel/conf/editor.go
index 8517617a7..fe64c87aa 100644
--- a/kernel/conf/editor.go
+++ b/kernel/conf/editor.go
@@ -30,6 +30,7 @@ type Editor struct {
Emoji []string `json:"emoji"` // 常用表情
VirtualBlockRef bool `json:"virtualBlockRef"` // 是否启用虚拟引用
VirtualBlockRefExclude string `json:"virtualBlockRefExclude"` // 虚拟引用关键字排除列表
+ VirtualBlockRefInclude string `json:"virtualBlockRefInclude"` // 虚拟引用关键字包含列表
BlockRefDynamicAnchorTextMaxLen int `json:"blockRefDynamicAnchorTextMaxLen"` // 块引动态锚文本最大长度
PlantUMLServePath string `json:"plantUMLServePath"` // PlantUML 伺服地址
FullWidth bool `json:"fullWidth"` // 是否使用最大宽度
diff --git a/kernel/model/backlink.go b/kernel/model/backlink.go
index 5b1b6d38f..66da9cc01 100644
--- a/kernel/model/backlink.go
+++ b/kernel/model/backlink.go
@@ -301,14 +301,13 @@ func buildBacklink(refID string, refTree *parse.Tree, mentionKeywords []string,
}
if ast.NodeText == n.Type {
text := string(n.Tokens)
- newText := markReplaceSpan(text, mentionKeywords, searchMarkSpanStart, searchMarkSpanEnd)
+ newText := markReplaceSpanWithSplit(text, mentionKeywords, searchMarkSpanStart, searchMarkSpanEnd)
if text == newText {
return ast.WalkContinue
}
n.Tokens = gulu.Str.ToBytes(newText)
if bytes.Contains(n.Tokens, []byte("search-mark")) {
- n.Tokens = bytes.ReplaceAll(n.Tokens, []byte("\\"+searchMarkSpanStart), []byte("\\\\"+searchMarkSpanEnd))
n.Tokens = lex.EscapeMarkers(n.Tokens)
linkTree := parse.Inline("", n.Tokens, luteEngine.ParseOptions)
var children []*ast.Node
@@ -794,7 +793,7 @@ func searchBackmention(mentionKeywords []string, keyword string, excludeBacklink
continue
}
- newText := markReplaceSpan(text, mentionKeywords, searchMarkSpanStart, searchMarkSpanEnd)
+ newText := markReplaceSpanWithSplit(text, mentionKeywords, searchMarkSpanStart, searchMarkSpanEnd)
if text != newText {
tmp = append(tmp, b)
}
diff --git a/kernel/model/file.go b/kernel/model/file.go
index da6bd4c20..55c28e850 100644
--- a/kernel/model/file.go
+++ b/kernel/model/file.go
@@ -17,7 +17,6 @@
package model
import (
- "bytes"
"errors"
"fmt"
"io/fs"
@@ -34,7 +33,6 @@ import (
"github.com/88250/gulu"
"github.com/88250/lute/ast"
"github.com/88250/lute/html"
- "github.com/88250/lute/lex"
"github.com/88250/lute/parse"
util2 "github.com/88250/lute/util"
"github.com/dustin/go-humanize"
@@ -598,27 +596,9 @@ func GetDoc(startID, endID, id string, index int, keyword string, mode int, size
} else {
nodes, eof = loadNodesByMode(node, inputIndex, mode, size, isDoc, isHeading)
}
+
refCount := sql.QueryRootChildrenRefCount(rootID)
-
- var virtualBlockRefKeywords []string
- if Conf.Editor.VirtualBlockRef {
- virtualBlockRefKeywords = sql.QueryVirtualRefKeywords(Conf.Search.VirtualRefName, Conf.Search.VirtualRefAlias, Conf.Search.VirtualRefAnchor, Conf.Search.VirtualRefDoc)
- if "" != strings.TrimSpace(Conf.Editor.VirtualBlockRefExclude) {
- exclude := strings.ReplaceAll(Conf.Editor.VirtualBlockRefExclude, "\\,", "__comma@sep__")
- excludes := strings.Split(exclude, ",")
- var tmp []string
- for _, e := range excludes {
- e = strings.ReplaceAll(e, "__comma@sep__", ",")
- tmp = append(tmp, e)
- }
- excludes = tmp
- virtualBlockRefKeywords = gulu.Str.ExcludeElem(virtualBlockRefKeywords, excludes)
- }
-
- // 虚拟引用排除当前文档名 https://github.com/siyuan-note/siyuan/issues/4537
- virtualBlockRefKeywords = gulu.Str.ExcludeElem(virtualBlockRefKeywords, []string{tree.Root.IALAttr("title")})
- virtualBlockRefKeywords = prepareMarkKeywords(virtualBlockRefKeywords)
- }
+ virtualBlockRefKeywords := getVirtualRefKeywords(tree.Root.IALAttr("title"))
subTree := &parse.Tree{ID: rootID, Root: &ast.Node{Type: ast.NodeDocument}, Marks: tree.Marks}
keyword = strings.Join(strings.Split(keyword, " "), search.TermSep)
@@ -660,65 +640,14 @@ func GetDoc(startID, endID, id string, index int, keyword string, mode int, size
}
}
if hitBlock {
- // 搜索高亮
- text := string(n.Tokens)
- text = search.EncloseHighlighting(text, keywords, searchMarkSpanStart, searchMarkSpanEnd, Conf.Search.CaseSensitive)
- n.Tokens = gulu.Str.ToBytes(text)
- if bytes.Contains(n.Tokens, []byte("search-mark")) {
- n.Tokens = bytes.ReplaceAll(n.Tokens, []byte("\\"+searchMarkSpanStart), []byte("\\\\"+searchMarkSpanEnd))
- n.Tokens = lex.EscapeMarkers(n.Tokens)
- linkTree := parse.Inline("", n.Tokens, luteEngine.ParseOptions)
- var children []*ast.Node
- for c := linkTree.Root.FirstChild.FirstChild; nil != c; c = c.Next {
- children = append(children, c)
- }
- for _, c := range children {
- n.InsertBefore(c)
- }
- unlinks = append(unlinks, n)
+ if markReplaceSpan(n, &unlinks, string(n.Tokens), keywords, searchMarkSpanStart, searchMarkSpanEnd, luteEngine) {
return ast.WalkContinue
}
}
}
- // 虚拟引用
- if Conf.Editor.VirtualBlockRef && 0 < len(virtualBlockRefKeywords) {
- parentBlock := treenode.ParentBlock(n)
- if nil != parentBlock && 1 > refCount[parentBlock.ID] {
- content := string(n.Tokens)
- newContent := markReplaceSpan(content, virtualBlockRefKeywords, virtualBlockRefSpanStart, virtualBlockRefSpanEnd)
- if content != newContent {
- // 虚拟引用排除命中自身块命名和别名的情况 https://github.com/siyuan-note/siyuan/issues/3185
- var blockKeys []string
- if name := parentBlock.IALAttr("name"); "" != name {
- blockKeys = append(blockKeys, name)
- }
- if alias := parentBlock.IALAttr("alias"); "" != alias {
- blockKeys = append(blockKeys, alias)
- }
- if 0 < len(blockKeys) {
- keys := gulu.Str.SubstringsBetween(newContent, virtualBlockRefSpanStart, virtualBlockRefSpanEnd)
- for _, k := range keys {
- if gulu.Str.Contains(k, blockKeys) {
- return ast.WalkContinue
- }
- }
- }
-
- n.Tokens = []byte(newContent)
- n.Tokens = lex.EscapeMarkers(n.Tokens)
- linkTree := parse.Inline("", n.Tokens, luteEngine.ParseOptions)
- var children []*ast.Node
- for c := linkTree.Root.FirstChild.FirstChild; nil != c; c = c.Next {
- children = append(children, c)
- }
- for _, c := range children {
- n.InsertBefore(c)
- }
- unlinks = append(unlinks, n)
- return ast.WalkContinue
- }
- }
+ if processVirtualRef(n, &unlinks, virtualBlockRefKeywords, refCount, luteEngine) {
+ return ast.WalkContinue
}
}
return ast.WalkContinue
diff --git a/kernel/model/history.go b/kernel/model/history.go
index 7a7945fc9..78d735e6f 100644
--- a/kernel/model/history.go
+++ b/kernel/model/history.go
@@ -17,7 +17,6 @@
package model
import (
- "bytes"
"encoding/json"
"fmt"
"io/fs"
@@ -32,7 +31,6 @@ import (
"github.com/88250/gulu"
"github.com/88250/lute"
"github.com/88250/lute/ast"
- "github.com/88250/lute/lex"
"github.com/88250/lute/parse"
"github.com/88250/lute/render"
"github.com/siyuan-note/filelock"
@@ -181,22 +179,7 @@ func GetDocHistoryContent(historyPath, keyword string) (id, rootID, content stri
if ast.NodeText == n.Type {
if 0 < len(keywords) {
- // 搜索高亮
- text := string(n.Tokens)
- text = search.EncloseHighlighting(text, keywords, searchMarkSpanStart, searchMarkSpanEnd, false)
- n.Tokens = gulu.Str.ToBytes(text)
- if bytes.Contains(n.Tokens, []byte("search-mark")) {
- n.Tokens = bytes.ReplaceAll(n.Tokens, []byte("\\"+searchMarkSpanStart), []byte("\\\\"+searchMarkSpanStart))
- n.Tokens = lex.EscapeMarkers(n.Tokens)
- linkTree := parse.Inline("", n.Tokens, luteEngine.ParseOptions)
- var children []*ast.Node
- for c := linkTree.Root.FirstChild.FirstChild; nil != c; c = c.Next {
- children = append(children, c)
- }
- for _, c := range children {
- n.InsertBefore(c)
- }
- unlinks = append(unlinks, n)
+ if markReplaceSpan(n, &unlinks, string(n.Tokens), keywords, searchMarkSpanStart, searchMarkSpanEnd, luteEngine) {
return ast.WalkContinue
}
}
diff --git a/kernel/model/search.go b/kernel/model/search.go
index 760d119a0..5994fa606 100644
--- a/kernel/model/search.go
+++ b/kernel/model/search.go
@@ -19,15 +19,16 @@ package model
import (
"bytes"
"path"
- "sort"
"strconv"
"strings"
"time"
"unicode/utf8"
"github.com/88250/gulu"
+ "github.com/88250/lute"
"github.com/88250/lute/ast"
"github.com/88250/lute/html"
+ "github.com/88250/lute/lex"
"github.com/88250/lute/parse"
"github.com/jinzhu/copier"
"github.com/siyuan-note/logging"
@@ -642,22 +643,28 @@ func stringQuery(query string) string {
return strings.TrimSpace(buf.String())
}
-func prepareMarkKeywords(keywords []string) (ret []string) {
- keywords = gulu.Str.RemoveDuplicatedElem(keywords)
- for _, k := range keywords {
- if strings.ContainsAny(k, "?*!@#$%^&()[]{}\\|;:'\",.<>~`") {
- continue
+// markReplaceSpan 用于处理搜索高亮。
+func markReplaceSpan(n *ast.Node, unlinks *[]*ast.Node, text string, keywords []string, replacementStart, replacementEnd string, luteEngine *lute.Lute) bool {
+ text = search.EncloseHighlighting(text, keywords, searchMarkSpanStart, searchMarkSpanEnd, Conf.Search.CaseSensitive)
+ n.Tokens = gulu.Str.ToBytes(text)
+ if bytes.Contains(n.Tokens, []byte("search-mark")) {
+ n.Tokens = lex.EscapeMarkers(n.Tokens)
+ linkTree := parse.Inline("", n.Tokens, luteEngine.ParseOptions)
+ var children []*ast.Node
+ for c := linkTree.Root.FirstChild.FirstChild; nil != c; c = c.Next {
+ children = append(children, c)
}
- ret = append(ret, k)
+ for _, c := range children {
+ n.InsertBefore(c)
+ }
+ *unlinks = append(*unlinks, n)
+ return true
}
-
- sort.SliceStable(ret, func(i, j int) bool {
- return len(ret[i]) < len(ret[j])
- })
- return
+ return false
}
-func markReplaceSpan(text string, keywords []string, replacementStart, replacementEnd string) (ret string) {
+// markReplaceSpanWithSplit 用于处理虚拟引用和反链提及高亮。
+func markReplaceSpanWithSplit(text string, keywords []string, replacementStart, replacementEnd string) (ret string) {
// 调用该函数前参数 keywords 必须使用 prepareMarkKeywords 函数进行预处理
parts := strings.Split(text, " ")
diff --git a/kernel/model/virutalref.go b/kernel/model/virutalref.go
new file mode 100644
index 000000000..91c3930bd
--- /dev/null
+++ b/kernel/model/virutalref.go
@@ -0,0 +1,128 @@
+// SiYuan - Build Your Eternal Digital Garden
+// Copyright (c) 2020-present, b3log.org
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package model
+
+import (
+ "sort"
+ "strings"
+
+ "github.com/88250/gulu"
+ "github.com/88250/lute"
+ "github.com/88250/lute/ast"
+ "github.com/88250/lute/lex"
+ "github.com/88250/lute/parse"
+ "github.com/siyuan-note/siyuan/kernel/sql"
+ "github.com/siyuan-note/siyuan/kernel/treenode"
+)
+
+func processVirtualRef(n *ast.Node, unlinks *[]*ast.Node, virtualBlockRefKeywords []string, refCount map[string]int, luteEngine *lute.Lute) bool {
+ if !Conf.Editor.VirtualBlockRef || 1 > len(virtualBlockRefKeywords) {
+ return false
+ }
+
+ parentBlock := treenode.ParentBlock(n)
+ if nil == parentBlock || 0 < refCount[parentBlock.ID] {
+ return false
+ }
+
+ content := string(n.Tokens)
+ newContent := markReplaceSpanWithSplit(content, virtualBlockRefKeywords, virtualBlockRefSpanStart, virtualBlockRefSpanEnd)
+ if content != newContent {
+ // 虚拟引用排除命中自身块命名和别名的情况 https://github.com/siyuan-note/siyuan/issues/3185
+ var blockKeys []string
+ if name := parentBlock.IALAttr("name"); "" != name {
+ blockKeys = append(blockKeys, name)
+ }
+ if alias := parentBlock.IALAttr("alias"); "" != alias {
+ blockKeys = append(blockKeys, alias)
+ }
+ if 0 < len(blockKeys) {
+ keys := gulu.Str.SubstringsBetween(newContent, virtualBlockRefSpanStart, virtualBlockRefSpanEnd)
+ for _, k := range keys {
+ if gulu.Str.Contains(k, blockKeys) {
+ return true
+ }
+ }
+ }
+
+ n.Tokens = []byte(newContent)
+ n.Tokens = lex.EscapeMarkers(n.Tokens)
+ linkTree := parse.Inline("", n.Tokens, luteEngine.ParseOptions)
+ var children []*ast.Node
+ for c := linkTree.Root.FirstChild.FirstChild; nil != c; c = c.Next {
+ children = append(children, c)
+ }
+ for _, c := range children {
+ n.InsertBefore(c)
+ }
+ *unlinks = append(*unlinks, n)
+ return true
+ }
+ return false
+}
+
+func getVirtualRefKeywords(docName string) (ret []string) {
+ if !Conf.Editor.VirtualBlockRef {
+ return
+ }
+
+ ret = sql.QueryVirtualRefKeywords(Conf.Search.VirtualRefName, Conf.Search.VirtualRefAlias, Conf.Search.VirtualRefAnchor, Conf.Search.VirtualRefDoc)
+ if "" != strings.TrimSpace(Conf.Editor.VirtualBlockRefInclude) {
+ include := strings.ReplaceAll(Conf.Editor.VirtualBlockRefInclude, "\\,", "__comma@sep__")
+ includes := strings.Split(include, ",")
+ var tmp []string
+ for _, e := range includes {
+ e = strings.ReplaceAll(e, "__comma@sep__", ",")
+ tmp = append(tmp, e)
+ }
+ includes = tmp
+ ret = append(ret, includes...)
+ ret = gulu.Str.RemoveDuplicatedElem(ret)
+ }
+
+ if "" != strings.TrimSpace(Conf.Editor.VirtualBlockRefExclude) {
+ exclude := strings.ReplaceAll(Conf.Editor.VirtualBlockRefExclude, "\\,", "__comma@sep__")
+ excludes := strings.Split(exclude, ",")
+ var tmp []string
+ for _, e := range excludes {
+ e = strings.ReplaceAll(e, "__comma@sep__", ",")
+ tmp = append(tmp, e)
+ }
+ excludes = tmp
+ ret = gulu.Str.ExcludeElem(ret, excludes)
+ }
+
+ // 虚拟引用排除当前文档名 https://github.com/siyuan-note/siyuan/issues/4537
+ ret = gulu.Str.ExcludeElem(ret, []string{docName})
+ ret = prepareMarkKeywords(ret)
+ return
+}
+
+func prepareMarkKeywords(keywords []string) (ret []string) {
+ keywords = gulu.Str.RemoveDuplicatedElem(keywords)
+ for _, k := range keywords {
+ if strings.ContainsAny(k, "?*!@#$%^&()[]{}\\|;:'\",.<>~`") {
+ continue
+ }
+ ret = append(ret, k)
+ }
+
+ sort.SliceStable(ret, func(i, j int) bool {
+ return len(ret[i]) < len(ret[j])
+ })
+ return
+}