123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181 |
- // 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))
- }
- if len(link) > 0 && link[0] == '#' {
- link = []byte("#user-content-" + string(link)[1:])
- }
- 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
- }
|