render.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  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. if nil == node {
  87. return
  88. }
  89. ret = sql.NodeStaticContent(node, excludeTypes, false, false, false, GetBlockAttrsWithoutWaitWriting)
  90. ret = strings.TrimSpace(ret)
  91. ret = strings.ReplaceAll(ret, "\n", "")
  92. ret = util.EscapeHTML(ret)
  93. ret = strings.TrimSpace(ret)
  94. if "" == ret {
  95. // 复制内容为空的块作为块引用时粘贴无效 https://github.com/siyuan-note/siyuan/issues/4962
  96. buf := bytes.Buffer{}
  97. ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus {
  98. if !entering {
  99. return ast.WalkContinue
  100. }
  101. if ast.NodeImage == n.Type {
  102. title := n.ChildByType(ast.NodeLinkTitle)
  103. if nil == title {
  104. alt := n.ChildByType(ast.NodeLinkText)
  105. if nil != alt && 0 < len(alt.Tokens) {
  106. buf.Write(alt.Tokens)
  107. } else {
  108. buf.WriteString("image")
  109. }
  110. } else {
  111. buf.Write(title.Tokens)
  112. }
  113. }
  114. return ast.WalkContinue
  115. })
  116. ret = buf.String()
  117. }
  118. return
  119. }
  120. func renderBlockDOMByNodes(nodes []*ast.Node, luteEngine *lute.Lute) string {
  121. tree := &parse.Tree{Root: &ast.Node{Type: ast.NodeDocument}, Context: &parse.Context{ParseOption: luteEngine.ParseOptions}}
  122. blockRenderer := render.NewProtyleRenderer(tree, luteEngine.RenderOptions)
  123. for _, n := range nodes {
  124. ast.Walk(n, func(node *ast.Node, entering bool) ast.WalkStatus {
  125. rendererFunc := blockRenderer.RendererFuncs[node.Type]
  126. return rendererFunc(node, entering)
  127. })
  128. }
  129. h := strings.TrimSpace(blockRenderer.Writer.String())
  130. if strings.HasPrefix(h, "<li") {
  131. h = "<ul>" + h + "</ul>"
  132. }
  133. return h
  134. }
  135. func renderBlockContentByNodes(nodes []*ast.Node) string {
  136. var subNodes []*ast.Node
  137. for _, n := range nodes {
  138. if ast.NodeDocument == n.Type {
  139. for c := n.FirstChild; nil != c; c = c.Next {
  140. subNodes = append(subNodes, c)
  141. }
  142. } else {
  143. subNodes = append(subNodes, n)
  144. }
  145. }
  146. buf := bytes.Buffer{}
  147. for _, n := range subNodes {
  148. buf.WriteString(sql.NodeStaticContent(n, nil, false, false, false, GetBlockAttrsWithoutWaitWriting))
  149. }
  150. return buf.String()
  151. }
  152. func resolveEmbedR(n *ast.Node, blockEmbedMode int, luteEngine *lute.Lute, resolved *[]string) {
  153. var children []*ast.Node
  154. if ast.NodeHeading == n.Type {
  155. children = append(children, n)
  156. children = append(children, treenode.HeadingChildren(n)...)
  157. } else if ast.NodeDocument == n.Type {
  158. for c := n.FirstChild; nil != c; c = c.Next {
  159. children = append(children, c)
  160. }
  161. } else {
  162. children = append(children, n)
  163. }
  164. for _, child := range children {
  165. var unlinks []*ast.Node
  166. ast.Walk(child, func(n *ast.Node, entering bool) ast.WalkStatus {
  167. if !entering || !n.IsBlock() {
  168. return ast.WalkContinue
  169. }
  170. if ast.NodeBlockQueryEmbed == n.Type {
  171. if gulu.Str.Contains(n.ID, *resolved) {
  172. return ast.WalkContinue
  173. }
  174. *resolved = append(*resolved, n.ID)
  175. stmt := n.ChildByType(ast.NodeBlockQueryEmbedScript).TokensStr()
  176. stmt = html.UnescapeString(stmt)
  177. stmt = strings.ReplaceAll(stmt, editor.IALValEscNewLine, "\n")
  178. sqlBlocks := sql.SelectBlocksRawStmt(stmt, 1, Conf.Search.Limit)
  179. for _, sqlBlock := range sqlBlocks {
  180. md := sqlBlock.Markdown
  181. if "d" == sqlBlock.Type {
  182. subTree, _ := LoadTreeByBlockID(sqlBlock.ID)
  183. md, _ = lute.FormatNodeSync(subTree.Root, luteEngine.ParseOptions, luteEngine.RenderOptions)
  184. } // 标题块不需要再单独解析,直接使用 Markdown,函数开头处会处理
  185. buf := &bytes.Buffer{}
  186. lines := strings.Split(md, "\n")
  187. for i, line := range lines {
  188. if 0 == blockEmbedMode { // 使用原始文本
  189. buf.WriteString(line)
  190. } else { // 使用引述块
  191. buf.WriteString("> " + line)
  192. }
  193. if i < len(lines)-1 {
  194. buf.WriteString("\n")
  195. }
  196. }
  197. buf.WriteString("\n\n")
  198. subTree := parse.Parse("", buf.Bytes(), luteEngine.ParseOptions)
  199. var inserts []*ast.Node
  200. for subNode := subTree.Root.FirstChild; nil != subNode; subNode = subNode.Next {
  201. if ast.NodeKramdownBlockIAL != subNode.Type {
  202. inserts = append(inserts, subNode)
  203. }
  204. }
  205. for _, insert := range inserts {
  206. n.InsertBefore(insert)
  207. resolveEmbedR(insert, blockEmbedMode, luteEngine, resolved)
  208. }
  209. }
  210. unlinks = append(unlinks, n)
  211. return ast.WalkSkipChildren
  212. }
  213. return ast.WalkContinue
  214. })
  215. for _, unlink := range unlinks {
  216. unlink.Unlink()
  217. }
  218. }
  219. return
  220. }
  221. func renderBlockMarkdownR(id string, rendered *[]string) (ret []*ast.Node) {
  222. if gulu.Str.Contains(id, *rendered) {
  223. return
  224. }
  225. *rendered = append(*rendered, id)
  226. b := treenode.GetBlockTree(id)
  227. if nil == b {
  228. return
  229. }
  230. var err error
  231. var t *parse.Tree
  232. if t, err = LoadTreeByBlockID(b.ID); nil != err {
  233. return
  234. }
  235. node := treenode.GetNodeInTree(t, b.ID)
  236. if nil == node {
  237. return
  238. }
  239. var children []*ast.Node
  240. if ast.NodeHeading == node.Type {
  241. children = append(children, node)
  242. children = append(children, treenode.HeadingChildren(node)...)
  243. } else if ast.NodeDocument == node.Type {
  244. for c := node.FirstChild; nil != c; c = c.Next {
  245. children = append(children, c)
  246. }
  247. } else {
  248. children = append(children, node)
  249. }
  250. for _, child := range children {
  251. var unlinks, inserts []*ast.Node
  252. ast.Walk(child, func(n *ast.Node, entering bool) ast.WalkStatus {
  253. if !entering || !n.IsBlock() {
  254. return ast.WalkContinue
  255. }
  256. if ast.NodeBlockQueryEmbed == n.Type {
  257. stmt := n.ChildByType(ast.NodeBlockQueryEmbedScript).TokensStr()
  258. stmt = html.UnescapeString(stmt)
  259. stmt = strings.ReplaceAll(stmt, editor.IALValEscNewLine, "\n")
  260. sqlBlocks := sql.SelectBlocksRawStmt(stmt, 1, Conf.Search.Limit)
  261. for _, sqlBlock := range sqlBlocks {
  262. subNodes := renderBlockMarkdownR(sqlBlock.ID, rendered)
  263. for _, subNode := range subNodes {
  264. inserts = append(inserts, subNode)
  265. }
  266. }
  267. unlinks = append(unlinks, n)
  268. return ast.WalkSkipChildren
  269. }
  270. return ast.WalkContinue
  271. })
  272. for _, n := range unlinks {
  273. n.Unlink()
  274. }
  275. if ast.NodeBlockQueryEmbed != child.Type {
  276. ret = append(ret, child)
  277. } else {
  278. for _, n := range inserts {
  279. ret = append(ret, n)
  280. }
  281. }
  282. }
  283. return
  284. }