gitea源码

orgmode.go 4.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package orgmode
  4. import (
  5. "fmt"
  6. "html"
  7. "html/template"
  8. "io"
  9. "strings"
  10. "code.gitea.io/gitea/modules/highlight"
  11. "code.gitea.io/gitea/modules/htmlutil"
  12. "code.gitea.io/gitea/modules/log"
  13. "code.gitea.io/gitea/modules/markup"
  14. "code.gitea.io/gitea/modules/setting"
  15. "github.com/alecthomas/chroma/v2"
  16. "github.com/alecthomas/chroma/v2/lexers"
  17. "github.com/niklasfasching/go-org/org"
  18. )
  19. func init() {
  20. markup.RegisterRenderer(renderer{})
  21. }
  22. // Renderer implements markup.Renderer for orgmode
  23. type renderer struct{}
  24. var (
  25. _ markup.Renderer = (*renderer)(nil)
  26. _ markup.PostProcessRenderer = (*renderer)(nil)
  27. )
  28. // Name implements markup.Renderer
  29. func (renderer) Name() string {
  30. return "orgmode"
  31. }
  32. // NeedPostProcess implements markup.PostProcessRenderer
  33. func (renderer) NeedPostProcess() bool { return true }
  34. // Extensions implements markup.Renderer
  35. func (renderer) Extensions() []string {
  36. return []string{".org"}
  37. }
  38. // SanitizerRules implements markup.Renderer
  39. func (renderer) SanitizerRules() []setting.MarkupSanitizerRule {
  40. return []setting.MarkupSanitizerRule{}
  41. }
  42. // Render renders orgmode raw bytes to HTML
  43. func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
  44. htmlWriter := org.NewHTMLWriter()
  45. htmlWriter.HighlightCodeBlock = func(source, lang string, inline bool, params map[string]string) string {
  46. defer func() {
  47. if err := recover(); err != nil {
  48. log.Error("Panic in HighlightCodeBlock: %v\n%s", err, log.Stack(2))
  49. panic(err)
  50. }
  51. }()
  52. w := &strings.Builder{}
  53. lexer := lexers.Get(lang)
  54. if lexer == nil && lang == "" {
  55. lexer = lexers.Analyse(source)
  56. if lexer == nil {
  57. lexer = lexers.Fallback
  58. }
  59. lang = strings.ToLower(lexer.Config().Name)
  60. }
  61. // include language-x class as part of commonmark spec
  62. if err := ctx.RenderInternal.FormatWithSafeAttrs(w, `<pre><code class="chroma language-%s">`, lang); err != nil {
  63. return ""
  64. }
  65. if lexer == nil {
  66. if _, err := w.WriteString(html.EscapeString(source)); err != nil {
  67. return ""
  68. }
  69. } else {
  70. lexer = chroma.Coalesce(lexer)
  71. if _, err := w.WriteString(string(highlight.CodeFromLexer(lexer, source))); err != nil {
  72. return ""
  73. }
  74. }
  75. if _, err := w.WriteString("</code></pre>"); err != nil {
  76. return ""
  77. }
  78. return w.String()
  79. }
  80. w := &orgWriter{rctx: ctx, HTMLWriter: htmlWriter}
  81. htmlWriter.ExtendingWriter = w
  82. res, err := org.New().Silent().Parse(input, "").Write(w)
  83. if err != nil {
  84. return fmt.Errorf("orgmode.Render failed: %w", err)
  85. }
  86. _, err = io.Copy(output, strings.NewReader(res))
  87. return err
  88. }
  89. // RenderString renders orgmode string to HTML string
  90. func RenderString(ctx *markup.RenderContext, content string) (string, error) {
  91. var buf strings.Builder
  92. if err := Render(ctx, strings.NewReader(content), &buf); err != nil {
  93. return "", err
  94. }
  95. return buf.String(), nil
  96. }
  97. // Render renders orgmode string to HTML string
  98. func (renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error {
  99. return Render(ctx, input, output)
  100. }
  101. type orgWriter struct {
  102. *org.HTMLWriter
  103. rctx *markup.RenderContext
  104. }
  105. var _ org.Writer = (*orgWriter)(nil)
  106. func (r *orgWriter) resolveLink(link string) string {
  107. return strings.TrimPrefix(link, "file:")
  108. }
  109. // WriteRegularLink renders images, links or videos
  110. func (r *orgWriter) WriteRegularLink(l org.RegularLink) {
  111. link := r.resolveLink(l.URL)
  112. printHTML := func(html template.HTML, a ...any) {
  113. _, _ = fmt.Fprint(r, htmlutil.HTMLFormat(html, a...))
  114. }
  115. // Inspired by https://github.com/niklasfasching/go-org/blob/6eb20dbda93cb88c3503f7508dc78cbbc639378f/org/html_writer.go#L406-L427
  116. switch l.Kind() {
  117. case "image":
  118. if l.Description == nil {
  119. printHTML(`<img src="%s" alt="%s">`, link, link)
  120. } else {
  121. imageSrc := r.resolveLink(org.String(l.Description...))
  122. printHTML(`<a href="%s"><img src="%s" alt="%s"></a>`, link, imageSrc, imageSrc)
  123. }
  124. case "video":
  125. if l.Description == nil {
  126. printHTML(`<video src="%s">%s</video>`, link, link)
  127. } else {
  128. videoSrc := r.resolveLink(org.String(l.Description...))
  129. printHTML(`<a href="%s"><video src="%s">%s</video></a>`, link, videoSrc, videoSrc)
  130. }
  131. default:
  132. var description any = link
  133. if l.Description != nil {
  134. description = template.HTML(r.WriteNodesAsString(l.Description...)) // orgmode HTMLWriter outputs HTML content
  135. }
  136. printHTML(`<a href="%s">%s</a>`, link, description)
  137. }
  138. }