From 980f6dd0b8dd79ab1736a3a1952edf68372b7854 Mon Sep 17 00:00:00 2001 From: Daniel <845765@qq.com> Date: Sat, 6 Dec 2025 16:46:14 +0800 Subject: [PATCH] :sparkles: Callout block https://github.com/siyuan-note/siyuan/issues/16051 Signed-off-by: Daniel <845765@qq.com> --- kernel/conf/graph.go | 1 + kernel/model/blockinfo.go | 4 ++-- kernel/model/box.go | 2 +- kernel/model/export.go | 8 ++++---- kernel/model/graph.go | 8 ++++++++ kernel/model/outline.go | 4 ++-- kernel/model/template.go | 3 ++- kernel/sql/database.go | 3 ++- kernel/treenode/node.go | 5 ++++- 9 files changed, 26 insertions(+), 12 deletions(-) diff --git a/kernel/conf/graph.go b/kernel/conf/graph.go index 5f4abc493..c91501cc7 100644 --- a/kernel/conf/graph.go +++ b/kernel/conf/graph.go @@ -72,6 +72,7 @@ type TypeFilter struct { ListItem bool `json:"listItem"` Blockquote bool `json:"blockquote"` Super bool `json:"super"` + Callout bool `json:"callout"` } type D3 struct { diff --git a/kernel/model/blockinfo.go b/kernel/model/blockinfo.go index 43d6067bb..325dc9037 100644 --- a/kernel/model/blockinfo.go +++ b/kernel/model/blockinfo.go @@ -532,7 +532,7 @@ func buildBlockBreadcrumb(node *ast.Node, excludeTypes []string, isEmbedBlock bo name, _ = av.GetAttributeViewName(parent.AttributeViewID) } else { if "" == name { - if ast.NodeListItem == parent.Type || ast.NodeList == parent.Type || ast.NodeSuperBlock == parent.Type || ast.NodeBlockquote == parent.Type { + if ast.NodeListItem == parent.Type || ast.NodeList == parent.Type || ast.NodeSuperBlock == parent.Type || ast.NodeBlockquote == parent.Type || ast.NodeCallout == parent.Type { name = gulu.Str.SubStr(renderBlockText(fc, excludeTypes, true), maxNameLen) } else { name = gulu.Str.SubStr(renderBlockText(parent, excludeTypes, true), maxNameLen) @@ -544,7 +544,7 @@ func buildBlockBreadcrumb(node *ast.Node, excludeTypes []string, isEmbedBlock bo } add := true - if ast.NodeList == parent.Type || ast.NodeSuperBlock == parent.Type || ast.NodeBlockquote == parent.Type { + if ast.NodeList == parent.Type || ast.NodeSuperBlock == parent.Type || ast.NodeBlockquote == parent.Type || ast.NodeCallout == parent.Type { add = false if parent == node { // https://github.com/siyuan-note/siyuan/issues/13141#issuecomment-2476789553 diff --git a/kernel/model/box.go b/kernel/model/box.go index d6ca8147c..7d41d5e50 100644 --- a/kernel/model/box.go +++ b/kernel/model/box.go @@ -539,7 +539,7 @@ func normalizeTree(tree *parse.Tree) (yfmRootID, yfmTitle, yfmUpdated string) { if "" == n.IALAttr("id") && (ast.NodeParagraph == n.Type || ast.NodeList == n.Type || ast.NodeListItem == n.Type || ast.NodeBlockquote == n.Type || ast.NodeMathBlock == n.Type || ast.NodeCodeBlock == n.Type || ast.NodeHeading == n.Type || ast.NodeTable == n.Type || ast.NodeThematicBreak == n.Type || ast.NodeYamlFrontMatter == n.Type || ast.NodeBlockQueryEmbed == n.Type || ast.NodeSuperBlock == n.Type || ast.NodeAttributeView == n.Type || - ast.NodeHTMLBlock == n.Type || ast.NodeIFrame == n.Type || ast.NodeWidget == n.Type || ast.NodeAudio == n.Type || ast.NodeVideo == n.Type) { + ast.NodeHTMLBlock == n.Type || ast.NodeIFrame == n.Type || ast.NodeWidget == n.Type || ast.NodeAudio == n.Type || ast.NodeVideo == n.Type || ast.NodeCallout == n.Type) { n.ID = ast.NewNodeID() n.KramdownIAL = [][]string{{"id", n.ID}} n.InsertAfter(&ast.Node{Type: ast.NodeKramdownBlockIAL, Tokens: []byte("{: id=\"" + n.ID + "\"}")}) diff --git a/kernel/model/export.go b/kernel/model/export.go index 49ffef2cb..86b30572d 100644 --- a/kernel/model/export.go +++ b/kernel/model/export.go @@ -905,7 +905,7 @@ func ExportHTML(id, savePath string, pdf, image, keepFold, merge bool) (name, do var headings []*ast.Node if pdf { // 导出 PDF 需要标记目录书签 ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { - if entering && ast.NodeHeading == n.Type && !n.ParentIs(ast.NodeBlockquote) { + if entering && ast.NodeHeading == n.Type && !n.ParentIs(ast.NodeBlockquote) && !n.ParentIs(ast.NodeCallout) { headings = append(headings, n) return ast.WalkSkipChildren } @@ -1128,7 +1128,7 @@ func ProcessPDF(id, p string, merge, removeAssets, watermark bool) (err error) { return ast.WalkContinue } - if ast.NodeHeading == n.Type && !n.ParentIs(ast.NodeBlockquote) { + if ast.NodeHeading == n.Type && !n.ParentIs(ast.NodeBlockquote) && !n.ParentIs(ast.NodeCallout) { headings = append(headings, n) return ast.WalkSkipChildren } @@ -3557,14 +3557,14 @@ func adjustHeadingLevel(bt *treenode.BlockTree, tree *parse.Tree) { var firstHeading *ast.Node if !Conf.Export.AddTitle { for n := tree.Root.FirstChild; nil != n; n = n.Next { - if ast.NodeHeading == n.Type && !n.ParentIs(ast.NodeBlockquote) { + if ast.NodeHeading == n.Type && !n.ParentIs(ast.NodeBlockquote) && !n.ParentIs(ast.NodeCallout) { firstHeading = n break } } } else { for n := tree.Root.FirstChild.Next; nil != n; n = n.Next { - if ast.NodeHeading == n.Type && !n.ParentIs(ast.NodeBlockquote) { + if ast.NodeHeading == n.Type && !n.ParentIs(ast.NodeBlockquote) && !n.ParentIs(ast.NodeCallout) { firstHeading = n break } diff --git a/kernel/model/graph.go b/kernel/model/graph.go index f051a9818..82fdaa02b 100644 --- a/kernel/model/graph.go +++ b/kernel/model/graph.go @@ -596,6 +596,14 @@ func graphTypeFilter(local bool) string { inList = append(inList, "'s'") } + callout := Conf.Graph.Local.Callout + if !local { + callout = Conf.Graph.Global.Callout + } + if callout { + inList = append(inList, "'callout'") + } + inList = append(inList, "'d'") return " AND ref.type IN (" + strings.Join(inList, ",") + ")" } diff --git a/kernel/model/outline.go b/kernel/model/outline.go index da9fa70f7..cd130307a 100644 --- a/kernel/model/outline.go +++ b/kernel/model/outline.go @@ -54,7 +54,7 @@ func (tx *Transaction) doMoveOutlineHeading(operation *Operation) (ret *TxErr) { headings := []*ast.Node{} ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { - if entering && ast.NodeHeading == n.Type && !n.ParentIs(ast.NodeBlockquote) { + if entering && ast.NodeHeading == n.Type && !n.ParentIs(ast.NodeBlockquote) && !n.ParentIs(ast.NodeCallout) { headings = append(headings, n) } return ast.WalkContinue @@ -305,7 +305,7 @@ func outline(tree *parse.Tree) (ret []*Path) { luteEngine := NewLute() var headings []*Block ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus { - if entering && ast.NodeHeading == n.Type && !n.ParentIs(ast.NodeBlockquote) { + if entering && ast.NodeHeading == n.Type && !n.ParentIs(ast.NodeBlockquote) && !n.ParentIs(ast.NodeCallout) { n.Box, n.Path = tree.Box, tree.Path block := &Block{ RootID: tree.Root.ID, diff --git a/kernel/model/template.go b/kernel/model/template.go index cf28ca617..8add29777 100644 --- a/kernel/model/template.go +++ b/kernel/model/template.go @@ -373,7 +373,8 @@ func RenderTemplate(p, id string, preview bool) (tree *parse.Tree, dom string, e if (ast.NodeListItem == n.Type && (nil == n.FirstChild || (3 == n.ListData.Typ && (nil == n.FirstChild.Next || ast.NodeKramdownBlockIAL == n.FirstChild.Next.Type)))) || - (ast.NodeBlockquote == n.Type && nil != n.FirstChild && nil != n.FirstChild.Next && ast.NodeKramdownBlockIAL == n.FirstChild.Next.Type) { + (ast.NodeBlockquote == n.Type && nil != n.FirstChild && nil != n.FirstChild.Next && ast.NodeKramdownBlockIAL == n.FirstChild.Next.Type) || + (ast.NodeCallout == n.Type && nil != n.FirstChild && ast.NodeKramdownBlockIAL == n.FirstChild.Next.Type) { nodesNeedAppendChild = append(nodesNeedAppendChild, n) } diff --git a/kernel/sql/database.go b/kernel/sql/database.go index 8068cff1f..dc706a2bd 100644 --- a/kernel/sql/database.go +++ b/kernel/sql/database.go @@ -1433,7 +1433,6 @@ func execStmtTx(tx *sql.Tx, stmt string, args ...interface{}) (err error) { func nSort(n *ast.Node) int { switch n.Type { - // 以下为块级元素 case ast.NodeHeading: return 5 case ast.NodeParagraph: @@ -1452,6 +1451,8 @@ func nSort(n *ast.Node) int { return 20 case ast.NodeBlockquote: return 20 + case ast.NodeCallout: + return 20 case ast.NodeSuperBlock: return 30 case ast.NodeAttributeView: diff --git a/kernel/treenode/node.go b/kernel/treenode/node.go index c7a4c7a73..ddd5ef52d 100644 --- a/kernel/treenode/node.go +++ b/kernel/treenode/node.go @@ -224,7 +224,7 @@ func FirstLeafBlock(node *ast.Node) (ret *ast.Node) { func CountBlockNodes(node *ast.Node) (ret int) { ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus { - if !entering || !n.IsBlock() || ast.NodeList == n.Type || ast.NodeBlockquote == n.Type || ast.NodeSuperBlock == n.Type { + if !entering || !n.IsBlock() || ast.NodeList == n.Type || ast.NodeBlockquote == n.Type || ast.NodeSuperBlock == n.Type || ast.NodeCallout == n.Type { return ast.WalkContinue } @@ -403,6 +403,7 @@ var typeAbbrMap = map[string]string{ "NodeThematicBreak": "tb", "NodeVideo": "video", "NodeAudio": "audio", + "NodeCallout": "callout", // 行级元素 "NodeText": "text", "NodeImage": "img", @@ -458,6 +459,8 @@ func SubTypeAbbr(n *ast.Node) string { if 6 == n.HeadingLevel { return "h6" } + case ast.NodeCallout: + return n.CalloutType } return "" }