summaryrefslogtreecommitdiffstats
path: root/modules/markup/markdown/goldmark.go
diff options
context:
space:
mode:
authorzeripath <art27@cantab.net>2019-12-31 01:53:28 +0000
committerLauris BH <lauris@nix.lv>2019-12-31 03:53:28 +0200
commit27757714d0420192e6139d1e4206446dcefe6531 (patch)
treed237deeb48315f1465dbd915b65f95973c4cbbe9 /modules/markup/markdown/goldmark.go
parent0c07f1de5b8cf7b9f9f607aae76a53c99aeb2c04 (diff)
downloadgitea-27757714d0420192e6139d1e4206446dcefe6531.tar.gz
gitea-27757714d0420192e6139d1e4206446dcefe6531.zip
Change markdown rendering from blackfriday to goldmark (#9533)
* Move to goldmark Markdown rendering moved from blackfriday to the goldmark. Multiple subtle changes required to the goldmark extensions to keep current rendering and defaults. Can go further with goldmark linkify and have this work within markdown rendering making the link processor unnecessary. Need to think about how to go about allowing extensions - at present it seems that these would be hard to do without recompilation. * linter fixes Co-authored-by: Lauris BH <lauris@nix.lv>
Diffstat (limited to 'modules/markup/markdown/goldmark.go')
-rw-r--r--modules/markup/markdown/goldmark.go178
1 files changed, 178 insertions, 0 deletions
diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go
new file mode 100644
index 0000000000..2a2a9dce6a
--- /dev/null
+++ b/modules/markup/markdown/goldmark.go
@@ -0,0 +1,178 @@
+// Copyright 2019 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"
+ "fmt"
+ "strings"
+
+ "code.gitea.io/gitea/modules/markup"
+ "code.gitea.io/gitea/modules/markup/common"
+ giteautil "code.gitea.io/gitea/modules/util"
+
+ "github.com/yuin/goldmark/ast"
+ east "github.com/yuin/goldmark/extension/ast"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/renderer"
+ "github.com/yuin/goldmark/renderer/html"
+ "github.com/yuin/goldmark/text"
+ "github.com/yuin/goldmark/util"
+)
+
+var byteMailto = []byte("mailto:")
+
+// GiteaASTTransformer is a default transformer of the goldmark tree.
+type GiteaASTTransformer struct{}
+
+// Transform transforms the given AST tree.
+func (g *GiteaASTTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
+ _ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
+ if !entering {
+ return ast.WalkContinue, nil
+ }
+
+ switch v := n.(type) {
+ case *ast.Image:
+ // Images need two things:
+ //
+ // 1. Their src needs to munged to be a real value
+ // 2. If they're not wrapped with a link they need a link wrapper
+
+ // Check if the destination is a real link
+ link := v.Destination
+ if len(link) > 0 && !markup.IsLink(link) {
+ prefix := pc.Get(urlPrefixKey).(string)
+ if pc.Get(isWikiKey).(bool) {
+ prefix = giteautil.URLJoin(prefix, "wiki", "raw")
+ }
+ prefix = strings.Replace(prefix, "/src/", "/media/", 1)
+
+ lnk := string(link)
+ lnk = giteautil.URLJoin(prefix, lnk)
+ lnk = strings.Replace(lnk, " ", "+", -1)
+ link = []byte(lnk)
+ }
+ v.Destination = link
+
+ parent := n.Parent()
+ // Create a link around image only if parent is not already a link
+ if _, ok := parent.(*ast.Link); !ok && parent != nil {
+ wrap := ast.NewLink()
+ wrap.Destination = link
+ wrap.Title = v.Title
+ parent.ReplaceChild(parent, n, wrap)
+ wrap.AppendChild(wrap, n)
+ }
+ case *ast.Link:
+ // Links need their href to munged to be a real value
+ link := v.Destination
+ if len(link) > 0 && !markup.IsLink(link) &&
+ link[0] != '#' && !bytes.HasPrefix(link, byteMailto) {
+ // special case: this is not a link, a hash link or a mailto:, so it's a
+ // relative URL
+ lnk := string(link)
+ if pc.Get(isWikiKey).(bool) {
+ lnk = giteautil.URLJoin("wiki", lnk)
+ }
+ link = []byte(giteautil.URLJoin(pc.Get(urlPrefixKey).(string), lnk))
+ }
+ v.Destination = link
+ }
+ return ast.WalkContinue, nil
+ })
+}
+
+type prefixedIDs struct {
+ values map[string]bool
+}
+
+// Generate generates a new element id.
+func (p *prefixedIDs) Generate(value []byte, kind ast.NodeKind) []byte {
+ dft := []byte("id")
+ if kind == ast.KindHeading {
+ dft = []byte("heading")
+ }
+ return p.GenerateWithDefault(value, dft)
+}
+
+// Generate generates a new element id.
+func (p *prefixedIDs) GenerateWithDefault(value []byte, dft []byte) []byte {
+ result := common.CleanValue(value)
+ if len(result) == 0 {
+ result = dft
+ }
+ if !bytes.HasPrefix(result, []byte("user-content-")) {
+ result = append([]byte("user-content-"), result...)
+ }
+ if _, ok := p.values[util.BytesToReadOnlyString(result)]; !ok {
+ p.values[util.BytesToReadOnlyString(result)] = true
+ return result
+ }
+ for i := 1; ; i++ {
+ newResult := fmt.Sprintf("%s-%d", result, i)
+ if _, ok := p.values[newResult]; !ok {
+ p.values[newResult] = true
+ return []byte(newResult)
+ }
+ }
+}
+
+// Put puts a given element id to the used ids table.
+func (p *prefixedIDs) Put(value []byte) {
+ p.values[util.BytesToReadOnlyString(value)] = true
+}
+
+func newPrefixedIDs() *prefixedIDs {
+ return &prefixedIDs{
+ values: map[string]bool{},
+ }
+}
+
+// NewTaskCheckBoxHTMLRenderer creates a TaskCheckBoxHTMLRenderer to render tasklists
+// in the gitea form.
+func NewTaskCheckBoxHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
+ r := &TaskCheckBoxHTMLRenderer{
+ Config: html.NewConfig(),
+ }
+ for _, opt := range opts {
+ opt.SetHTMLOption(&r.Config)
+ }
+ return r
+}
+
+// TaskCheckBoxHTMLRenderer is a renderer.NodeRenderer implementation that
+// renders checkboxes in list items.
+// Overrides the default goldmark one to present the gitea format
+type TaskCheckBoxHTMLRenderer struct {
+ html.Config
+}
+
+// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
+func (r *TaskCheckBoxHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
+ reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox)
+}
+
+func (r *TaskCheckBoxHTMLRenderer) renderTaskCheckBox(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
+ if !entering {
+ return ast.WalkContinue, nil
+ }
+ n := node.(*east.TaskCheckBox)
+
+ end := ">"
+ if r.XHTML {
+ end = " />"
+ }
+ var err error
+ if n.IsChecked {
+ _, err = w.WriteString(`<span class="ui fitted disabled checkbox"><input type="checkbox" disabled="disabled"` + end + `<label` + end + `</span>`)
+ } else {
+ _, err = w.WriteString(`<span class="ui checked fitted disabled checkbox"><input type="checkbox" checked="" disabled="disabled"` + end + `<label` + end + `</span>`)
+ }
+ if err != nil {
+ return ast.WalkStop, err
+ }
+ return ast.WalkContinue, nil
+}