code.gitea.io/gitea@v1.19.3/modules/markup/orgmode/orgmode.go (about) 1 // Copyright 2017 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package markup 5 6 import ( 7 "bytes" 8 "fmt" 9 "html" 10 "io" 11 "strings" 12 13 "code.gitea.io/gitea/modules/highlight" 14 "code.gitea.io/gitea/modules/log" 15 "code.gitea.io/gitea/modules/markup" 16 "code.gitea.io/gitea/modules/setting" 17 "code.gitea.io/gitea/modules/util" 18 19 "github.com/alecthomas/chroma/v2" 20 "github.com/alecthomas/chroma/v2/lexers" 21 "github.com/niklasfasching/go-org/org" 22 ) 23 24 func init() { 25 markup.RegisterRenderer(Renderer{}) 26 } 27 28 // Renderer implements markup.Renderer for orgmode 29 type Renderer struct{} 30 31 var _ markup.PostProcessRenderer = (*Renderer)(nil) 32 33 // Name implements markup.Renderer 34 func (Renderer) Name() string { 35 return "orgmode" 36 } 37 38 // NeedPostProcess implements markup.PostProcessRenderer 39 func (Renderer) NeedPostProcess() bool { return true } 40 41 // Extensions implements markup.Renderer 42 func (Renderer) Extensions() []string { 43 return []string{".org"} 44 } 45 46 // SanitizerRules implements markup.Renderer 47 func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule { 48 return []setting.MarkupSanitizerRule{} 49 } 50 51 // Render renders orgmode rawbytes to HTML 52 func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { 53 htmlWriter := org.NewHTMLWriter() 54 htmlWriter.HighlightCodeBlock = func(source, lang string, inline bool) string { 55 defer func() { 56 if err := recover(); err != nil { 57 log.Error("Panic in HighlightCodeBlock: %v\n%s", err, log.Stack(2)) 58 panic(err) 59 } 60 }() 61 var w strings.Builder 62 if _, err := w.WriteString(`<pre>`); err != nil { 63 return "" 64 } 65 66 lexer := lexers.Get(lang) 67 if lexer == nil && lang == "" { 68 lexer = lexers.Analyse(source) 69 if lexer == nil { 70 lexer = lexers.Fallback 71 } 72 lang = strings.ToLower(lexer.Config().Name) 73 } 74 75 if lexer == nil { 76 // include language-x class as part of commonmark spec 77 if _, err := w.WriteString(`<code class="chroma language-` + lang + `">`); err != nil { 78 return "" 79 } 80 if _, err := w.WriteString(html.EscapeString(source)); err != nil { 81 return "" 82 } 83 } else { 84 // include language-x class as part of commonmark spec 85 if _, err := w.WriteString(`<code class="chroma language-` + lang + `">`); err != nil { 86 return "" 87 } 88 lexer = chroma.Coalesce(lexer) 89 90 if _, err := w.WriteString(highlight.CodeFromLexer(lexer, source)); err != nil { 91 return "" 92 } 93 } 94 95 if _, err := w.WriteString("</code></pre>"); err != nil { 96 return "" 97 } 98 99 return w.String() 100 } 101 102 w := &Writer{ 103 HTMLWriter: htmlWriter, 104 URLPrefix: ctx.URLPrefix, 105 IsWiki: ctx.IsWiki, 106 } 107 108 htmlWriter.ExtendingWriter = w 109 110 res, err := org.New().Silent().Parse(input, "").Write(w) 111 if err != nil { 112 return fmt.Errorf("orgmode.Render failed: %w", err) 113 } 114 _, err = io.Copy(output, strings.NewReader(res)) 115 return err 116 } 117 118 // RenderString renders orgmode string to HTML string 119 func RenderString(ctx *markup.RenderContext, content string) (string, error) { 120 var buf strings.Builder 121 if err := Render(ctx, strings.NewReader(content), &buf); err != nil { 122 return "", err 123 } 124 return buf.String(), nil 125 } 126 127 // Render renders orgmode string to HTML string 128 func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { 129 return Render(ctx, input, output) 130 } 131 132 // Writer implements org.Writer 133 type Writer struct { 134 *org.HTMLWriter 135 URLPrefix string 136 IsWiki bool 137 } 138 139 var byteMailto = []byte("mailto:") 140 141 // WriteRegularLink renders images, links or videos 142 func (r *Writer) WriteRegularLink(l org.RegularLink) { 143 link := []byte(html.EscapeString(l.URL)) 144 if l.Protocol == "file" { 145 link = link[len("file:"):] 146 } 147 if len(link) > 0 && !markup.IsLink(link) && 148 link[0] != '#' && !bytes.HasPrefix(link, byteMailto) { 149 lnk := string(link) 150 if r.IsWiki { 151 lnk = util.URLJoin("wiki", lnk) 152 } 153 link = []byte(util.URLJoin(r.URLPrefix, lnk)) 154 } 155 156 description := string(link) 157 if l.Description != nil { 158 description = r.WriteNodesAsString(l.Description...) 159 } 160 switch l.Kind() { 161 case "image": 162 imageSrc := getMediaURL(link) 163 fmt.Fprintf(r, `<img src="%s" alt="%s" title="%s" />`, imageSrc, description, description) 164 case "video": 165 videoSrc := getMediaURL(link) 166 fmt.Fprintf(r, `<video src="%s" title="%s">%s</video>`, videoSrc, description, description) 167 default: 168 fmt.Fprintf(r, `<a href="%s" title="%s">%s</a>`, link, description, description) 169 } 170 } 171 172 func getMediaURL(l []byte) string { 173 srcURL := string(l) 174 175 // Check if link is valid 176 if len(srcURL) > 0 && !markup.IsLink(l) { 177 srcURL = strings.Replace(srcURL, "/src/", "/media/", 1) 178 } 179 180 return srcURL 181 }