mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-12-16 06:30:14 +01:00
🧑💻 Add internal kernel API /api/block/getBlockDOMWithEmbed and /api/block/getBlockDOMsWithEmbed (#16237)
* 🎨 Add internal kernel API `/api/block/getBlockDOMWithEmbed` and `/api/block/getBlockDOMsWithEmbed` fix https://github.com/siyuan-note/siyuan/issues/16021 * 🎨 Add internal kernel API `/api/block/getBlockDOMWithEmbed` and `/api/block/getBlockDOMsWithEmbed` fix https://github.com/siyuan-note/siyuan/issues/16021
This commit is contained in:
parent
65c8344add
commit
4c2684d11d
3 changed files with 190 additions and 0 deletions
|
|
@ -738,6 +738,42 @@ func getBlockDOMs(c *gin.Context) {
|
|||
ret.Data = doms
|
||||
}
|
||||
|
||||
func getBlockDOMWithEmbed(c *gin.Context) {
|
||||
ret := gulu.Ret.NewResult()
|
||||
defer c.JSON(http.StatusOK, ret)
|
||||
|
||||
arg, ok := util.JsonArg(c, ret)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
id := arg["id"].(string)
|
||||
dom := model.GetBlockDOMWithEmbed(id)
|
||||
ret.Data = map[string]string{
|
||||
"id": id,
|
||||
"dom": dom,
|
||||
}
|
||||
}
|
||||
|
||||
func getBlockDOMsWithEmbed(c *gin.Context) {
|
||||
ret := gulu.Ret.NewResult()
|
||||
defer c.JSON(http.StatusOK, ret)
|
||||
|
||||
arg, ok := util.JsonArg(c, ret)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
idsArg := arg["ids"].([]interface{})
|
||||
var ids []string
|
||||
for _, id := range idsArg {
|
||||
ids = append(ids, id.(string))
|
||||
}
|
||||
|
||||
doms := model.GetBlockDOMsWithEmbed(ids)
|
||||
ret.Data = doms
|
||||
}
|
||||
|
||||
func getBlockKramdown(c *gin.Context) {
|
||||
ret := gulu.Ret.NewResult()
|
||||
defer c.JSON(http.StatusOK, ret)
|
||||
|
|
|
|||
|
|
@ -185,6 +185,8 @@ func ServeAPI(ginServer *gin.Engine) {
|
|||
ginServer.Handle("POST", "/api/block/getBlockInfo", model.CheckAuth, getBlockInfo)
|
||||
ginServer.Handle("POST", "/api/block/getBlockDOM", model.CheckAuth, getBlockDOM)
|
||||
ginServer.Handle("POST", "/api/block/getBlockDOMs", model.CheckAuth, getBlockDOMs)
|
||||
ginServer.Handle("POST", "/api/block/getBlockDOMWithEmbed", model.CheckAuth, getBlockDOMWithEmbed)
|
||||
ginServer.Handle("POST", "/api/block/getBlockDOMsWithEmbed", model.CheckAuth, getBlockDOMsWithEmbed)
|
||||
ginServer.Handle("POST", "/api/block/getBlockKramdown", model.CheckAuth, getBlockKramdown)
|
||||
ginServer.Handle("POST", "/api/block/getChildBlocks", model.CheckAuth, getChildBlocks)
|
||||
ginServer.Handle("POST", "/api/block/getTailChildBlocks", model.CheckAuth, getTailChildBlocks)
|
||||
|
|
|
|||
|
|
@ -20,12 +20,16 @@ import (
|
|||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
"github.com/88250/lute"
|
||||
"github.com/88250/lute/ast"
|
||||
"github.com/88250/lute/editor"
|
||||
"github.com/88250/lute/parse"
|
||||
"github.com/88250/lute/render"
|
||||
"github.com/open-spaced-repetition/go-fsrs/v3"
|
||||
|
|
@ -804,6 +808,154 @@ func GetBlockDOMs(ids []string) (ret map[string]string) {
|
|||
return
|
||||
}
|
||||
|
||||
func GetBlockDOMWithEmbed(id string) (ret string) {
|
||||
if "" == id {
|
||||
return
|
||||
}
|
||||
|
||||
doms := GetBlockDOMsWithEmbed([]string{id})
|
||||
ret = doms[id]
|
||||
return
|
||||
}
|
||||
|
||||
func GetBlockDOMsWithEmbed(ids []string) (ret map[string]string) {
|
||||
ret = map[string]string{}
|
||||
if 0 == len(ids) {
|
||||
return
|
||||
}
|
||||
|
||||
luteEngine := NewLute()
|
||||
trees := filesys.LoadTrees(ids)
|
||||
for id, tree := range trees {
|
||||
node := treenode.GetNodeInTree(tree, id)
|
||||
if nil == node {
|
||||
continue
|
||||
}
|
||||
|
||||
resolveEmbedContent(node, luteEngine)
|
||||
|
||||
// 处理折叠标题
|
||||
ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus {
|
||||
if !entering || !n.IsBlock() {
|
||||
return ast.WalkContinue
|
||||
}
|
||||
|
||||
if parentFoldedHeading := treenode.GetParentFoldedHeading(n); nil != parentFoldedHeading {
|
||||
n.SetIALAttr("parent-heading", parentFoldedHeading.ID)
|
||||
}
|
||||
return ast.WalkContinue
|
||||
})
|
||||
|
||||
htmlContent := luteEngine.RenderNodeBlockDOM(node)
|
||||
|
||||
htmlContent = processEmbedHTML(htmlContent)
|
||||
|
||||
ret[id] = htmlContent
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func resolveEmbedContent(n *ast.Node, luteEngine *lute.Lute) {
|
||||
ast.Walk(n, func(node *ast.Node, entering bool) ast.WalkStatus {
|
||||
if !entering || ast.NodeBlockQueryEmbed != node.Type {
|
||||
return ast.WalkContinue
|
||||
}
|
||||
|
||||
// 获取嵌入块的查询语句
|
||||
scriptNode := node.ChildByType(ast.NodeBlockQueryEmbedScript)
|
||||
if nil == scriptNode {
|
||||
return ast.WalkContinue
|
||||
}
|
||||
stmt := scriptNode.TokensStr()
|
||||
stmt = html.UnescapeString(stmt)
|
||||
stmt = strings.ReplaceAll(stmt, editor.IALValEscNewLine, "\n")
|
||||
|
||||
// 执行查询获取嵌入的块
|
||||
sqlBlocks := sql.SelectBlocksRawStmt(stmt, 1, Conf.Search.Limit)
|
||||
|
||||
// 收集所有嵌入块的内容 HTML
|
||||
var embedContents []string
|
||||
for _, sqlBlock := range sqlBlocks {
|
||||
if "query_embed" == sqlBlock.Type {
|
||||
continue
|
||||
}
|
||||
|
||||
subTree, _ := LoadTreeByBlockID(sqlBlock.ID)
|
||||
if nil == subTree {
|
||||
continue
|
||||
}
|
||||
|
||||
// 将内容转换为 HTML,直接使用原始 AST 节点渲染以保持正确的 data-node-id
|
||||
var contentHTML string
|
||||
if "d" == sqlBlock.Type {
|
||||
// 文档块:直接使用原始 AST 节点渲染,保持原始的 data-node-id
|
||||
contentHTML = luteEngine.RenderNodeBlockDOM(subTree.Root)
|
||||
} else if "h" == sqlBlock.Type {
|
||||
// 标题块:使用标题及其子块的原始 AST 节点渲染
|
||||
h := treenode.GetNodeInTree(subTree, sqlBlock.ID)
|
||||
if nil == h {
|
||||
continue
|
||||
}
|
||||
var hChildren []*ast.Node
|
||||
hChildren = append(hChildren, h)
|
||||
hChildren = append(hChildren, treenode.HeadingChildren(h)...)
|
||||
|
||||
// 创建一个临时的文档节点来包含所有子节点
|
||||
tempRoot := &ast.Node{Type: ast.NodeDocument}
|
||||
for _, hChild := range hChildren {
|
||||
tempRoot.AppendChild(hChild)
|
||||
}
|
||||
contentHTML = luteEngine.RenderNodeBlockDOM(tempRoot)
|
||||
} else {
|
||||
// 其他块:直接使用原始 AST 节点渲染
|
||||
blockNode := treenode.GetNodeInTree(subTree, sqlBlock.ID)
|
||||
if nil == blockNode {
|
||||
continue
|
||||
}
|
||||
contentHTML = luteEngine.RenderNodeBlockDOM(blockNode)
|
||||
}
|
||||
|
||||
if contentHTML != "" {
|
||||
embedContents = append(embedContents, contentHTML)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有内容,在嵌入块上添加内容标记
|
||||
if len(embedContents) > 0 {
|
||||
node.SetIALAttr("embed-content", strings.Join(embedContents, ""))
|
||||
}
|
||||
|
||||
return ast.WalkContinue
|
||||
})
|
||||
}
|
||||
|
||||
func processEmbedHTML(htmlStr string) string {
|
||||
// 使用正则表达式查找所有带有 embed-content 属性的嵌入块
|
||||
embedPattern := `<div[^>]*data-type="NodeBlockQueryEmbed"[^>]*embed-content="[^"]*"[^>]*>`
|
||||
re := regexp.MustCompile(embedPattern)
|
||||
|
||||
return re.ReplaceAllStringFunc(htmlStr, func(match string) string {
|
||||
// 提取 embed-content 属性值
|
||||
contentPattern := `embed-content="([^"]*)"`
|
||||
contentRe := regexp.MustCompile(contentPattern)
|
||||
contentMatches := contentRe.FindStringSubmatch(match)
|
||||
|
||||
if len(contentMatches) > 1 {
|
||||
embedContent := contentMatches[1]
|
||||
// HTML 解码
|
||||
embedContent = html.UnescapeString(embedContent)
|
||||
|
||||
// 移除 embed-content 属性,避免在最终 HTML 中显示
|
||||
cleanMatch := contentRe.ReplaceAllString(match, "")
|
||||
|
||||
// 将内容插入到嵌入块内部
|
||||
return cleanMatch + embedContent + "</div>"
|
||||
}
|
||||
|
||||
return match
|
||||
})
|
||||
}
|
||||
|
||||
func GetBlockKramdown(id, mode string) (ret string) {
|
||||
if "" == id {
|
||||
return
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue