mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-12-16 14:40:12 +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
|
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) {
|
func getBlockKramdown(c *gin.Context) {
|
||||||
ret := gulu.Ret.NewResult()
|
ret := gulu.Ret.NewResult()
|
||||||
defer c.JSON(http.StatusOK, ret)
|
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/getBlockInfo", model.CheckAuth, getBlockInfo)
|
||||||
ginServer.Handle("POST", "/api/block/getBlockDOM", model.CheckAuth, getBlockDOM)
|
ginServer.Handle("POST", "/api/block/getBlockDOM", model.CheckAuth, getBlockDOM)
|
||||||
ginServer.Handle("POST", "/api/block/getBlockDOMs", model.CheckAuth, getBlockDOMs)
|
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/getBlockKramdown", model.CheckAuth, getBlockKramdown)
|
||||||
ginServer.Handle("POST", "/api/block/getChildBlocks", model.CheckAuth, getChildBlocks)
|
ginServer.Handle("POST", "/api/block/getChildBlocks", model.CheckAuth, getChildBlocks)
|
||||||
ginServer.Handle("POST", "/api/block/getTailChildBlocks", model.CheckAuth, getTailChildBlocks)
|
ginServer.Handle("POST", "/api/block/getTailChildBlocks", model.CheckAuth, getTailChildBlocks)
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,16 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html"
|
||||||
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/88250/gulu"
|
"github.com/88250/gulu"
|
||||||
|
"github.com/88250/lute"
|
||||||
"github.com/88250/lute/ast"
|
"github.com/88250/lute/ast"
|
||||||
|
"github.com/88250/lute/editor"
|
||||||
"github.com/88250/lute/parse"
|
"github.com/88250/lute/parse"
|
||||||
"github.com/88250/lute/render"
|
"github.com/88250/lute/render"
|
||||||
"github.com/open-spaced-repetition/go-fsrs/v3"
|
"github.com/open-spaced-repetition/go-fsrs/v3"
|
||||||
|
|
@ -804,6 +808,154 @@ func GetBlockDOMs(ids []string) (ret map[string]string) {
|
||||||
return
|
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) {
|
func GetBlockKramdown(id, mode string) (ret string) {
|
||||||
if "" == id {
|
if "" == id {
|
||||||
return
|
return
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue