gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package markdown
  4. import (
  5. "fmt"
  6. "code.gitea.io/gitea/modules/container"
  7. "code.gitea.io/gitea/modules/markup"
  8. "code.gitea.io/gitea/modules/markup/internal"
  9. "github.com/yuin/goldmark/ast"
  10. east "github.com/yuin/goldmark/extension/ast"
  11. "github.com/yuin/goldmark/parser"
  12. "github.com/yuin/goldmark/renderer"
  13. "github.com/yuin/goldmark/renderer/html"
  14. "github.com/yuin/goldmark/text"
  15. "github.com/yuin/goldmark/util"
  16. )
  17. // ASTTransformer is a default transformer of the goldmark tree.
  18. type ASTTransformer struct {
  19. renderInternal *internal.RenderInternal
  20. attentionTypes container.Set[string]
  21. }
  22. func NewASTTransformer(renderInternal *internal.RenderInternal) *ASTTransformer {
  23. return &ASTTransformer{
  24. renderInternal: renderInternal,
  25. attentionTypes: container.SetOf("note", "tip", "important", "warning", "caution"),
  26. }
  27. }
  28. func (g *ASTTransformer) applyElementDir(n ast.Node) {
  29. if !markup.RenderBehaviorForTesting.DisableAdditionalAttributes {
  30. n.SetAttributeString("dir", "auto")
  31. }
  32. }
  33. // Transform transforms the given AST tree.
  34. func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
  35. firstChild := node.FirstChild()
  36. tocMode := ""
  37. ctx := pc.Get(renderContextKey).(*markup.RenderContext)
  38. rc := pc.Get(renderConfigKey).(*RenderConfig)
  39. tocList := make([]Header, 0, 20)
  40. if rc.yamlNode != nil {
  41. metaNode := rc.toMetaNode(g)
  42. if metaNode != nil {
  43. node.InsertBefore(node, firstChild, metaNode)
  44. }
  45. tocMode = rc.TOC
  46. }
  47. _ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
  48. if !entering {
  49. return ast.WalkContinue, nil
  50. }
  51. switch v := n.(type) {
  52. case *ast.Heading:
  53. g.transformHeading(ctx, v, reader, &tocList)
  54. case *ast.Paragraph:
  55. g.applyElementDir(v)
  56. case *ast.List:
  57. g.transformList(ctx, v, rc)
  58. case *ast.Text:
  59. if v.SoftLineBreak() && !v.HardLineBreak() {
  60. newLineHardBreak := ctx.RenderOptions.Metas["markdownNewLineHardBreak"] == "true"
  61. v.SetHardLineBreak(newLineHardBreak)
  62. }
  63. case *ast.CodeSpan:
  64. g.transformCodeSpan(ctx, v, reader)
  65. case *ast.Blockquote:
  66. return g.transformBlockquote(v, reader)
  67. }
  68. return ast.WalkContinue, nil
  69. })
  70. showTocInMain := tocMode == "true" /* old behavior, in main view */ || tocMode == "main"
  71. showTocInSidebar := !showTocInMain && tocMode != "false" // not hidden, not main, then show it in sidebar
  72. if len(tocList) > 0 && (showTocInMain || showTocInSidebar) {
  73. if showTocInMain {
  74. tocNode := createTOCNode(tocList, rc.Lang, nil)
  75. node.InsertBefore(node, firstChild, tocNode)
  76. } else {
  77. tocNode := createTOCNode(tocList, rc.Lang, map[string]string{"open": "open"})
  78. ctx.SidebarTocNode = tocNode
  79. }
  80. }
  81. if len(rc.Lang) > 0 {
  82. node.SetAttributeString("lang", []byte(rc.Lang))
  83. }
  84. }
  85. // NewHTMLRenderer creates a HTMLRenderer to render in the gitea form.
  86. func NewHTMLRenderer(renderInternal *internal.RenderInternal, opts ...html.Option) renderer.NodeRenderer {
  87. r := &HTMLRenderer{
  88. renderInternal: renderInternal,
  89. Config: html.NewConfig(),
  90. }
  91. for _, opt := range opts {
  92. opt.SetHTMLOption(&r.Config)
  93. }
  94. return r
  95. }
  96. // HTMLRenderer is a renderer.NodeRenderer implementation that
  97. // renders gitea specific features.
  98. type HTMLRenderer struct {
  99. html.Config
  100. renderInternal *internal.RenderInternal
  101. }
  102. // RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
  103. func (r *HTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
  104. reg.Register(ast.KindDocument, r.renderDocument)
  105. reg.Register(KindDetails, r.renderDetails)
  106. reg.Register(KindSummary, r.renderSummary)
  107. reg.Register(ast.KindCodeSpan, r.renderCodeSpan)
  108. reg.Register(KindAttention, r.renderAttention)
  109. reg.Register(KindTaskCheckBoxListItem, r.renderTaskCheckBoxListItem)
  110. reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox)
  111. reg.Register(KindRawHTML, r.renderRawHTML)
  112. }
  113. func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
  114. n := node.(*ast.Document)
  115. if val, has := n.AttributeString("lang"); has {
  116. var err error
  117. if entering {
  118. _, err = w.WriteString("<div")
  119. if err == nil {
  120. _, err = fmt.Fprintf(w, ` lang=%q`, val)
  121. }
  122. if err == nil {
  123. _, err = w.WriteRune('>')
  124. }
  125. } else {
  126. _, err = w.WriteString("</div>")
  127. }
  128. if err != nil {
  129. return ast.WalkStop, err
  130. }
  131. }
  132. return ast.WalkContinue, nil
  133. }
  134. func (r *HTMLRenderer) renderDetails(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
  135. var err error
  136. if entering {
  137. if _, err = w.WriteString("<details"); err != nil {
  138. return ast.WalkStop, err
  139. }
  140. html.RenderAttributes(w, node, nil)
  141. _, err = w.WriteString(">")
  142. } else {
  143. _, err = w.WriteString("</details>")
  144. }
  145. if err != nil {
  146. return ast.WalkStop, err
  147. }
  148. return ast.WalkContinue, nil
  149. }
  150. func (r *HTMLRenderer) renderSummary(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
  151. var err error
  152. if entering {
  153. _, err = w.WriteString("<summary>")
  154. } else {
  155. _, err = w.WriteString("</summary>")
  156. }
  157. if err != nil {
  158. return ast.WalkStop, err
  159. }
  160. return ast.WalkContinue, nil
  161. }
  162. func (r *HTMLRenderer) renderRawHTML(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
  163. if !entering {
  164. return ast.WalkContinue, nil
  165. }
  166. n := node.(*RawHTML)
  167. _, err := w.WriteString(string(r.renderInternal.ProtectSafeAttrs(n.rawHTML)))
  168. if err != nil {
  169. return ast.WalkStop, err
  170. }
  171. return ast.WalkContinue, nil
  172. }