123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- // Copyright 2014 The Gogs Authors. All rights reserved.
- // Copyright 2018 The Gitea Authors. All rights reserved.
- // Use of this source code is governed by a MIT-style
- // license that can be found in the LICENSE file.
-
- package markdown
-
- import (
- "bytes"
- "io"
- "strings"
-
- "code.gitea.io/gitea/modules/markup"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/util"
-
- "github.com/russross/blackfriday/v2"
- )
-
- // Renderer is a extended version of underlying render object.
- type Renderer struct {
- blackfriday.Renderer
- URLPrefix string
- IsWiki bool
- }
-
- var byteMailto = []byte("mailto:")
-
- var htmlEscaper = [256][]byte{
- '&': []byte("&"),
- '<': []byte("<"),
- '>': []byte(">"),
- '"': []byte("""),
- }
-
- func escapeHTML(w io.Writer, s []byte) {
- var start, end int
- for end < len(s) {
- escSeq := htmlEscaper[s[end]]
- if escSeq != nil {
- _, _ = w.Write(s[start:end])
- _, _ = w.Write(escSeq)
- start = end + 1
- }
- end++
- }
- if start < len(s) && end <= len(s) {
- _, _ = w.Write(s[start:end])
- }
- }
-
- // RenderNode is a default renderer of a single node of a syntax tree. For
- // block nodes it will be called twice: first time with entering=true, second
- // time with entering=false, so that it could know when it's working on an open
- // tag and when on close. It writes the result to w.
- //
- // The return value is a way to tell the calling walker to adjust its walk
- // pattern: e.g. it can terminate the traversal by returning Terminate. Or it
- // can ask the walker to skip a subtree of this node by returning SkipChildren.
- // The typical behavior is to return GoToNext, which asks for the usual
- // traversal to the next node.
- func (r *Renderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
- switch node.Type {
- case blackfriday.Image:
- prefix := r.URLPrefix
- if r.IsWiki {
- prefix = util.URLJoin(prefix, "wiki", "raw")
- }
- prefix = strings.Replace(prefix, "/src/", "/media/", 1)
- link := node.LinkData.Destination
- if len(link) > 0 && !markup.IsLink(link) {
- lnk := string(link)
- lnk = util.URLJoin(prefix, lnk)
- lnk = strings.Replace(lnk, " ", "+", -1)
- link = []byte(lnk)
- }
- node.LinkData.Destination = link
- // Render link around image only if parent is not link already
- if node.Parent != nil && node.Parent.Type != blackfriday.Link {
- if entering {
- _, _ = w.Write([]byte(`<a href="`))
- escapeHTML(w, link)
- _, _ = w.Write([]byte(`">`))
- return r.Renderer.RenderNode(w, node, entering)
- }
- s := r.Renderer.RenderNode(w, node, entering)
- _, _ = w.Write([]byte(`</a>`))
- return s
- }
- return r.Renderer.RenderNode(w, node, entering)
- case blackfriday.Link:
- // special case: this is not a link, a hash link or a mailto:, so it's a
- // relative URL
- link := node.LinkData.Destination
- if len(link) > 0 && !markup.IsLink(link) &&
- link[0] != '#' && !bytes.HasPrefix(link, byteMailto) &&
- node.LinkData.Footnote == nil {
- lnk := string(link)
- if r.IsWiki {
- lnk = util.URLJoin("wiki", lnk)
- }
- link = []byte(util.URLJoin(r.URLPrefix, lnk))
- }
- node.LinkData.Destination = link
- return r.Renderer.RenderNode(w, node, entering)
- case blackfriday.Text:
- isListItem := false
- for n := node.Parent; n != nil; n = n.Parent {
- if n.Type == blackfriday.Item {
- isListItem = true
- break
- }
- }
- if isListItem {
- text := node.Literal
- switch {
- case bytes.HasPrefix(text, []byte("[ ] ")):
- _, _ = w.Write([]byte(`<span class="ui fitted disabled checkbox"><input type="checkbox" disabled="disabled" /><label /></span>`))
- text = text[3:]
- case bytes.HasPrefix(text, []byte("[x] ")):
- _, _ = w.Write([]byte(`<span class="ui checked fitted disabled checkbox"><input type="checkbox" checked="" disabled="disabled" /><label /></span>`))
- text = text[3:]
- }
- node.Literal = text
- }
- }
- return r.Renderer.RenderNode(w, node, entering)
- }
-
- const (
- blackfridayExtensions = 0 |
- blackfriday.NoIntraEmphasis |
- blackfriday.Tables |
- blackfriday.FencedCode |
- blackfriday.Strikethrough |
- blackfriday.NoEmptyLineBeforeBlock |
- blackfriday.DefinitionLists |
- blackfriday.Footnotes |
- blackfriday.HeadingIDs |
- blackfriday.AutoHeadingIDs
- blackfridayHTMLFlags = 0 |
- blackfriday.Smartypants
- )
-
- // RenderRaw renders Markdown to HTML without handling special links.
- func RenderRaw(body []byte, urlPrefix string, wikiMarkdown bool) []byte {
- renderer := &Renderer{
- Renderer: blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{
- Flags: blackfridayHTMLFlags,
- }),
- URLPrefix: urlPrefix,
- IsWiki: wikiMarkdown,
- }
-
- exts := blackfridayExtensions
- if setting.Markdown.EnableHardLineBreak {
- exts |= blackfriday.HardLineBreak
- }
-
- // Need to normalize EOL to UNIX LF to have consistent results in rendering
- body = blackfriday.Run(util.NormalizeEOL(body), blackfriday.WithRenderer(renderer), blackfriday.WithExtensions(exts))
- return markup.SanitizeBytes(body)
- }
-
- var (
- // MarkupName describes markup's name
- MarkupName = "markdown"
- )
-
- func init() {
- markup.RegisterParser(Parser{})
- }
-
- // Parser implements markup.Parser
- type Parser struct {
- }
-
- // Name implements markup.Parser
- func (Parser) Name() string {
- return MarkupName
- }
-
- // Extensions implements markup.Parser
- func (Parser) Extensions() []string {
- return setting.Markdown.FileExtensions
- }
-
- // Render implements markup.Parser
- func (Parser) Render(rawBytes []byte, urlPrefix string, metas map[string]string, isWiki bool) []byte {
- return RenderRaw(rawBytes, urlPrefix, isWiki)
- }
-
- // Render renders Markdown to HTML with all specific handling stuff.
- func Render(rawBytes []byte, urlPrefix string, metas map[string]string) []byte {
- return markup.Render("a.md", rawBytes, urlPrefix, metas)
- }
-
- // RenderString renders Markdown to HTML with special links and returns string type.
- func RenderString(raw, urlPrefix string, metas map[string]string) string {
- return markup.RenderString("a.md", raw, urlPrefix, metas)
- }
-
- // RenderWiki renders markdown wiki page to HTML and return HTML string
- func RenderWiki(rawBytes []byte, urlPrefix string, metas map[string]string) string {
- return markup.RenderWiki("a.md", rawBytes, urlPrefix, metas)
- }
-
- // IsMarkdownFile reports whether name looks like a Markdown file
- // based on its extension.
- func IsMarkdownFile(name string) bool {
- return markup.IsMarkupFile(name, MarkupName)
- }
|