render.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. // SiYuan - Refactor your thinking
  2. // Copyright (c) 2020-present, b3log.org
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. package model
  17. import (
  18. "bytes"
  19. "github.com/88250/lute/editor"
  20. "regexp"
  21. "strings"
  22. "github.com/88250/gulu"
  23. "github.com/88250/lute"
  24. "github.com/88250/lute/ast"
  25. "github.com/88250/lute/html"
  26. "github.com/88250/lute/parse"
  27. "github.com/88250/lute/render"
  28. "github.com/siyuan-note/siyuan/kernel/sql"
  29. "github.com/siyuan-note/siyuan/kernel/treenode"
  30. "github.com/siyuan-note/siyuan/kernel/util"
  31. )
  32. func renderOutline(heading *ast.Node, luteEngine *lute.Lute) (ret string) {
  33. if nil == heading {
  34. return ""
  35. }
  36. if ast.NodeDocument == heading.Type {
  37. return heading.IALAttr("title")
  38. }
  39. buf := bytes.Buffer{}
  40. buf.Grow(4096)
  41. ast.Walk(heading, func(n *ast.Node, entering bool) ast.WalkStatus {
  42. if !entering {
  43. switch n.Type {
  44. case ast.NodeHeading:
  45. // Show heading block appearance style in the Outline Panel https://github.com/siyuan-note/siyuan/issues/7872
  46. if style := n.IALAttr("style"); "" != style {
  47. buf.WriteString("</span>")
  48. }
  49. }
  50. return ast.WalkContinue
  51. }
  52. if style := n.IALAttr("style"); "" != style {
  53. if strings.Contains(style, "font-size") { // 大纲字号不应该跟随字体设置 https://github.com/siyuan-note/siyuan/issues/7202
  54. style = regexp.MustCompile("font-size:.*?;").ReplaceAllString(style, "font-size: inherit;")
  55. n.SetIALAttr("style", style)
  56. }
  57. }
  58. switch n.Type {
  59. case ast.NodeHeading:
  60. // Show heading block appearance style in the Outline Panel https://github.com/siyuan-note/siyuan/issues/7872
  61. if style := n.IALAttr("style"); "" != style {
  62. buf.WriteString("<span style=\"")
  63. buf.WriteString(style)
  64. buf.WriteString("\">")
  65. }
  66. case ast.NodeText, ast.NodeLinkText, ast.NodeCodeBlockCode, ast.NodeMathBlockContent:
  67. tokens := html.EscapeHTML(n.Tokens)
  68. tokens = bytes.ReplaceAll(tokens, []byte(" "), []byte("&nbsp;")) // 大纲面板条目中无法显示多个空格 https://github.com/siyuan-note/siyuan/issues/4370
  69. buf.Write(tokens)
  70. case ast.NodeBackslashContent:
  71. buf.Write(n.Tokens)
  72. case ast.NodeTextMark:
  73. dom := luteEngine.RenderNodeBlockDOM(n)
  74. buf.WriteString(dom)
  75. return ast.WalkSkipChildren
  76. case ast.NodeImage:
  77. return ast.WalkSkipChildren
  78. }
  79. return ast.WalkContinue
  80. })
  81. ret = strings.TrimSpace(buf.String())
  82. ret = strings.ReplaceAll(ret, "\n", "")
  83. return
  84. }
  85. func renderBlockText(node *ast.Node, excludeTypes []string) (ret string) {
  86. ret = treenode.NodeStaticContent(node, excludeTypes, false, false)
  87. ret = strings.TrimSpace(ret)
  88. ret = strings.ReplaceAll(ret, "\n", "")
  89. ret = util.EscapeHTML(ret)
  90. ret = strings.TrimSpace(ret)
  91. if "" == ret {
  92. // 复制内容为空的块作为块引用时粘贴无效 https://github.com/siyuan-note/siyuan/issues/4962
  93. buf := bytes.Buffer{}
  94. ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus {
  95. if !entering {
  96. return ast.WalkContinue
  97. }
  98. if ast.NodeImage == n.Type {
  99. title := n.ChildByType(ast.NodeLinkTitle)
  100. if nil == title {
  101. alt := n.ChildByType(ast.NodeLinkText)
  102. if nil != alt && 0 < len(alt.Tokens) {
  103. buf.Write(alt.Tokens)
  104. } else {
  105. buf.WriteString("image")
  106. }
  107. } else {
  108. buf.Write(title.Tokens)
  109. }
  110. }
  111. return ast.WalkContinue
  112. })
  113. ret = buf.String()
  114. }
  115. return
  116. }
  117. func renderBlockDOMByNodes(nodes []*ast.Node, luteEngine *lute.Lute) string {
  118. tree := &parse.Tree{Root: &ast.Node{Type: ast.NodeDocument}, Context: &parse.Context{ParseOption: luteEngine.ParseOptions}}
  119. blockRenderer := render.NewProtyleRenderer(tree, luteEngine.RenderOptions)
  120. for _, n := range nodes {
  121. ast.Walk(n, func(node *ast.Node, entering bool) ast.WalkStatus {
  122. rendererFunc := blockRenderer.RendererFuncs[node.Type]
  123. return rendererFunc(node, entering)
  124. })
  125. }
  126. h := strings.TrimSpace(blockRenderer.Writer.String())
  127. if strings.HasPrefix(h, "<li") {
  128. h = "<ul>" + h + "</ul>"
  129. }
  130. return h
  131. }
  132. func renderBlockContentByNodes(nodes []*ast.Node) string {
  133. var subNodes []*ast.Node
  134. for _, n := range nodes {
  135. if ast.NodeDocument == n.Type {
  136. for c := n.FirstChild; nil != c; c = c.Next {
  137. subNodes = append(subNodes, c)
  138. }
  139. } else {
  140. subNodes = append(subNodes, n)
  141. }
  142. }
  143. buf := bytes.Buffer{}
  144. for _, n := range subNodes {
  145. buf.WriteString(treenode.NodeStaticContent(n, nil, false, false))
  146. }
  147. return buf.String()
  148. }
  149. func renderBlockMarkdownR(id string) string {
  150. var rendered []string
  151. nodes := renderBlockMarkdownR0(id, &rendered)
  152. buf := bytes.Buffer{}
  153. buf.Grow(4096)
  154. luteEngine := NewLute()
  155. for _, n := range nodes {
  156. md := treenode.FormatNode(n, luteEngine)
  157. buf.WriteString(md)
  158. buf.WriteString("\n\n")
  159. }
  160. return buf.String()
  161. }
  162. func renderBlockMarkdownR0(id string, rendered *[]string) (ret []*ast.Node) {
  163. if gulu.Str.Contains(id, *rendered) {
  164. return
  165. }
  166. *rendered = append(*rendered, id)
  167. b := treenode.GetBlockTree(id)
  168. if nil == b {
  169. return
  170. }
  171. var err error
  172. var t *parse.Tree
  173. if t, err = loadTreeByBlockID(b.ID); nil != err {
  174. return
  175. }
  176. node := treenode.GetNodeInTree(t, b.ID)
  177. if nil == node {
  178. return
  179. }
  180. var children []*ast.Node
  181. if ast.NodeHeading == node.Type {
  182. children = append(children, node)
  183. children = append(children, treenode.HeadingChildren(node)...)
  184. } else if ast.NodeDocument == node.Type {
  185. for c := node.FirstChild; nil != c; c = c.Next {
  186. children = append(children, c)
  187. }
  188. } else {
  189. children = append(children, node)
  190. }
  191. for _, child := range children {
  192. var unlinks, inserts []*ast.Node
  193. ast.Walk(child, func(n *ast.Node, entering bool) ast.WalkStatus {
  194. if !entering || !n.IsBlock() {
  195. return ast.WalkContinue
  196. }
  197. if ast.NodeBlockQueryEmbed == n.Type {
  198. stmt := n.ChildByType(ast.NodeBlockQueryEmbedScript).TokensStr()
  199. stmt = html.UnescapeString(stmt)
  200. stmt = strings.ReplaceAll(stmt, editor.IALValEscNewLine, "\n")
  201. sqlBlocks := sql.SelectBlocksRawStmt(stmt, 1, Conf.Search.Limit)
  202. for _, sqlBlock := range sqlBlocks {
  203. subNodes := renderBlockMarkdownR0(sqlBlock.ID, rendered)
  204. for _, subNode := range subNodes {
  205. inserts = append(inserts, subNode)
  206. }
  207. }
  208. unlinks = append(unlinks, n)
  209. return ast.WalkSkipChildren
  210. }
  211. return ast.WalkContinue
  212. })
  213. for _, n := range unlinks {
  214. n.Unlink()
  215. }
  216. if ast.NodeBlockQueryEmbed != child.Type {
  217. ret = append(ret, child)
  218. } else {
  219. for _, n := range inserts {
  220. ret = append(ret, n)
  221. }
  222. }
  223. }
  224. return
  225. }