gitea源码

transform_blockquote.go 5.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. // Copyright 2024 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package markdown
  4. import (
  5. "strings"
  6. "code.gitea.io/gitea/modules/svg"
  7. "github.com/yuin/goldmark/ast"
  8. "github.com/yuin/goldmark/text"
  9. "github.com/yuin/goldmark/util"
  10. "golang.org/x/text/cases"
  11. "golang.org/x/text/language"
  12. )
  13. // renderAttention renders a quote marked with i.e. "> **Note**" or "> [!Warning]" with a corresponding svg
  14. func (r *HTMLRenderer) renderAttention(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
  15. if entering {
  16. n := node.(*Attention)
  17. var octiconName string
  18. switch n.AttentionType {
  19. case "tip":
  20. octiconName = "light-bulb"
  21. case "important":
  22. octiconName = "report"
  23. case "warning":
  24. octiconName = "alert"
  25. case "caution":
  26. octiconName = "stop"
  27. default: // including "note"
  28. octiconName = "info"
  29. }
  30. svgHTML := svg.RenderHTML("octicon-"+octiconName, 16, "attention-icon attention-"+n.AttentionType)
  31. _, _ = w.WriteString(string(r.renderInternal.ProtectSafeAttrs(svgHTML)))
  32. }
  33. return ast.WalkContinue, nil
  34. }
  35. func (g *ASTTransformer) extractBlockquoteAttentionEmphasis(firstParagraph ast.Node, reader text.Reader) (string, []ast.Node) {
  36. if firstParagraph.ChildCount() < 1 {
  37. return "", nil
  38. }
  39. node1, ok := firstParagraph.FirstChild().(*ast.Emphasis)
  40. if !ok {
  41. return "", nil
  42. }
  43. val1 := string(node1.Text(reader.Source())) //nolint:staticcheck // Text is deprecated
  44. attentionType := strings.ToLower(val1)
  45. if g.attentionTypes.Contains(attentionType) {
  46. return attentionType, []ast.Node{node1}
  47. }
  48. return "", nil
  49. }
  50. func (g *ASTTransformer) extractBlockquoteAttention2(firstParagraph ast.Node, reader text.Reader) (string, []ast.Node) {
  51. if firstParagraph.ChildCount() < 2 {
  52. return "", nil
  53. }
  54. node1, ok := firstParagraph.FirstChild().(*ast.Text)
  55. if !ok {
  56. return "", nil
  57. }
  58. node2, ok := node1.NextSibling().(*ast.Text)
  59. if !ok {
  60. return "", nil
  61. }
  62. val1 := string(node1.Segment.Value(reader.Source()))
  63. val2 := string(node2.Segment.Value(reader.Source()))
  64. if strings.HasPrefix(val1, `\[!`) && val2 == `\]` {
  65. attentionType := strings.ToLower(val1[3:])
  66. if g.attentionTypes.Contains(attentionType) {
  67. return attentionType, []ast.Node{node1, node2}
  68. }
  69. }
  70. return "", nil
  71. }
  72. func (g *ASTTransformer) extractBlockquoteAttention3(firstParagraph ast.Node, reader text.Reader) (string, []ast.Node) {
  73. if firstParagraph.ChildCount() < 3 {
  74. return "", nil
  75. }
  76. node1, ok := firstParagraph.FirstChild().(*ast.Text)
  77. if !ok {
  78. return "", nil
  79. }
  80. node2, ok := node1.NextSibling().(*ast.Text)
  81. if !ok {
  82. return "", nil
  83. }
  84. node3, ok := node2.NextSibling().(*ast.Text)
  85. if !ok {
  86. return "", nil
  87. }
  88. val1 := string(node1.Segment.Value(reader.Source()))
  89. val2 := string(node2.Segment.Value(reader.Source()))
  90. val3 := string(node3.Segment.Value(reader.Source()))
  91. if val1 != "[" || val3 != "]" || !strings.HasPrefix(val2, "!") {
  92. return "", nil
  93. }
  94. attentionType := strings.ToLower(val2[1:])
  95. if g.attentionTypes.Contains(attentionType) {
  96. return attentionType, []ast.Node{node1, node2, node3}
  97. }
  98. return "", nil
  99. }
  100. func (g *ASTTransformer) transformBlockquote(v *ast.Blockquote, reader text.Reader) (ast.WalkStatus, error) {
  101. // We only want attention blockquotes when the AST looks like:
  102. // > Text("[") Text("!TYPE") Text("]")
  103. // > Text("\[!TYPE") TEXT("\]")
  104. // > Text("**TYPE**")
  105. // grab these nodes and make sure we adhere to the attention blockquote structure
  106. firstParagraph := v.FirstChild()
  107. if firstParagraph == nil {
  108. return ast.WalkContinue, nil
  109. }
  110. g.applyElementDir(firstParagraph)
  111. attentionType, processedNodes := g.extractBlockquoteAttentionEmphasis(firstParagraph, reader)
  112. if attentionType == "" {
  113. attentionType, processedNodes = g.extractBlockquoteAttention2(firstParagraph, reader)
  114. }
  115. if attentionType == "" {
  116. attentionType, processedNodes = g.extractBlockquoteAttention3(firstParagraph, reader)
  117. }
  118. if attentionType == "" {
  119. return ast.WalkContinue, nil
  120. }
  121. // color the blockquote
  122. v.SetAttributeString(g.renderInternal.SafeAttr("class"), []byte(g.renderInternal.SafeValue("attention-header attention-"+attentionType)))
  123. // create an emphasis to make it bold
  124. attentionParagraph := ast.NewParagraph()
  125. g.applyElementDir(attentionParagraph)
  126. emphasis := ast.NewEmphasis(2)
  127. emphasis.SetAttributeString(g.renderInternal.SafeAttr("class"), []byte(g.renderInternal.SafeValue("attention-"+attentionType)))
  128. attentionAstString := ast.NewString([]byte(cases.Title(language.English).String(attentionType)))
  129. // replace the ![TYPE] with a dedicated paragraph of icon+Type
  130. emphasis.AppendChild(emphasis, attentionAstString)
  131. attentionParagraph.AppendChild(attentionParagraph, NewAttention(attentionType))
  132. attentionParagraph.AppendChild(attentionParagraph, emphasis)
  133. firstParagraph.Parent().InsertBefore(firstParagraph.Parent(), firstParagraph, attentionParagraph)
  134. for _, processed := range processedNodes {
  135. firstParagraph.RemoveChild(firstParagraph, processed)
  136. }
  137. if firstParagraph.ChildCount() == 0 {
  138. firstParagraph.Parent().RemoveChild(firstParagraph.Parent(), firstParagraph)
  139. }
  140. return ast.WalkContinue, nil
  141. }