You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

markdown.go 5.0 kB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
9 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2018 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package markdown
  6. import (
  7. "bytes"
  8. "strings"
  9. "sync"
  10. "code.gitea.io/gitea/modules/log"
  11. "code.gitea.io/gitea/modules/markup"
  12. "code.gitea.io/gitea/modules/markup/common"
  13. "code.gitea.io/gitea/modules/setting"
  14. giteautil "code.gitea.io/gitea/modules/util"
  15. chromahtml "github.com/alecthomas/chroma/formatters/html"
  16. highlighting "github.com/yuin/goldmark-highlighting"
  17. "github.com/yuin/goldmark"
  18. meta "github.com/yuin/goldmark-meta"
  19. "github.com/yuin/goldmark/extension"
  20. "github.com/yuin/goldmark/parser"
  21. "github.com/yuin/goldmark/renderer"
  22. "github.com/yuin/goldmark/renderer/html"
  23. "github.com/yuin/goldmark/util"
  24. )
  25. var converter goldmark.Markdown
  26. var once = sync.Once{}
  27. var urlPrefixKey = parser.NewContextKey()
  28. var isWikiKey = parser.NewContextKey()
  29. // NewGiteaParseContext creates a parser.Context with the gitea context set
  30. func NewGiteaParseContext(urlPrefix string, isWiki bool) parser.Context {
  31. pc := parser.NewContext(parser.WithIDs(newPrefixedIDs()))
  32. pc.Set(urlPrefixKey, urlPrefix)
  33. pc.Set(isWikiKey, isWiki)
  34. return pc
  35. }
  36. // RenderRaw renders Markdown to HTML without handling special links.
  37. func RenderRaw(body []byte, urlPrefix string, wikiMarkdown bool) []byte {
  38. once.Do(func() {
  39. converter = goldmark.New(
  40. goldmark.WithExtensions(
  41. extension.NewTable(
  42. extension.WithTableCellAlignMethod(extension.TableCellAlignAttribute)),
  43. extension.Strikethrough,
  44. extension.TaskList,
  45. extension.DefinitionList,
  46. common.FootnoteExtension,
  47. highlighting.NewHighlighting(
  48. highlighting.WithFormatOptions(
  49. chromahtml.WithClasses(true),
  50. chromahtml.PreventSurroundingPre(true),
  51. ),
  52. highlighting.WithWrapperRenderer(func(w util.BufWriter, c highlighting.CodeBlockContext, entering bool) {
  53. if entering {
  54. language, _ := c.Language()
  55. if language == nil {
  56. language = []byte("text")
  57. }
  58. languageStr := string(language)
  59. preClasses := []string{"code-block"}
  60. if languageStr == "mermaid" {
  61. preClasses = append(preClasses, "is-loading")
  62. }
  63. _, err := w.WriteString(`<pre class="` + strings.Join(preClasses, " ") + `">`)
  64. if err != nil {
  65. return
  66. }
  67. // include language-x class as part of commonmark spec
  68. _, err = w.WriteString(`<code class="chroma language-` + string(language) + `">`)
  69. if err != nil {
  70. return
  71. }
  72. } else {
  73. _, err := w.WriteString("</code></pre>")
  74. if err != nil {
  75. return
  76. }
  77. }
  78. }),
  79. ),
  80. meta.Meta,
  81. ),
  82. goldmark.WithParserOptions(
  83. parser.WithAttribute(),
  84. parser.WithAutoHeadingID(),
  85. parser.WithASTTransformers(
  86. util.Prioritized(&ASTTransformer{}, 10000),
  87. ),
  88. ),
  89. goldmark.WithRendererOptions(
  90. html.WithUnsafe(),
  91. ),
  92. )
  93. // Override the original Tasklist renderer!
  94. converter.Renderer().AddOptions(
  95. renderer.WithNodeRenderers(
  96. util.Prioritized(NewHTMLRenderer(), 10),
  97. ),
  98. )
  99. if setting.Markdown.EnableHardLineBreak {
  100. converter.Renderer().AddOptions(html.WithHardWraps())
  101. }
  102. })
  103. pc := NewGiteaParseContext(urlPrefix, wikiMarkdown)
  104. var buf bytes.Buffer
  105. if err := converter.Convert(giteautil.NormalizeEOL(body), &buf, parser.WithContext(pc)); err != nil {
  106. log.Error("Unable to render: %v", err)
  107. }
  108. return markup.SanitizeReader(&buf).Bytes()
  109. }
  110. var (
  111. // MarkupName describes markup's name
  112. MarkupName = "markdown"
  113. )
  114. func init() {
  115. markup.RegisterParser(Parser{})
  116. }
  117. // Parser implements markup.Parser
  118. type Parser struct{}
  119. // Name implements markup.Parser
  120. func (Parser) Name() string {
  121. return MarkupName
  122. }
  123. // Extensions implements markup.Parser
  124. func (Parser) Extensions() []string {
  125. return setting.Markdown.FileExtensions
  126. }
  127. // Render implements markup.Parser
  128. func (Parser) Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte {
  129. return RenderRaw(rawBytes, urlPrefix, isWiki)
  130. }
  131. // Render renders Markdown to HTML with all specific handling stuff.
  132. func Render(rawBytes []byte, urlPrefix string, metas map[string]string) []byte {
  133. return markup.Render("a.md", rawBytes, urlPrefix, metas)
  134. }
  135. // RenderString renders Markdown to HTML with special links and returns string type.
  136. func RenderString(raw, urlPrefix string, metas map[string]string) string {
  137. return markup.RenderString("a.md", raw, urlPrefix, metas)
  138. }
  139. // RenderWiki renders markdown wiki page to HTML and return HTML string
  140. func RenderWiki(rawBytes []byte, urlPrefix string, metas map[string]string) string {
  141. return markup.RenderWiki("a.md", rawBytes, urlPrefix, metas)
  142. }
  143. // IsMarkdownFile reports whether name looks like a Markdown file
  144. // based on its extension.
  145. func IsMarkdownFile(name string) bool {
  146. return markup.IsMarkupFile(name, MarkupName)
  147. }