mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 11:28:24 +00:00 
			
		
		
		
	* update github.com/blevesearch/bleve v2.0.2 -> v2.0.3 * github.com/denisenkom/go-mssqldb v0.9.0 -> v0.10.0 * github.com/editorconfig/editorconfig-core-go v2.4.1 -> v2.4.2 * github.com/go-chi/cors v1.1.1 -> v1.2.0 * github.com/go-git/go-billy v5.0.0 -> v5.1.0 * github.com/go-git/go-git v5.2.0 -> v5.3.0 * github.com/go-ldap/ldap v3.2.4 -> v3.3.0 * github.com/go-redis/redis v8.6.0 -> v8.8.2 * github.com/go-sql-driver/mysql v1.5.0 -> v1.6.0 * github.com/go-swagger/go-swagger v0.26.1 -> v0.27.0 * github.com/lib/pq v1.9.0 -> v1.10.1 * github.com/mattn/go-sqlite3 v1.14.6 -> v1.14.7 * github.com/go-testfixtures/testfixtures v3.5.0 -> v3.6.0 * github.com/issue9/identicon v1.0.1 -> v1.2.0 * github.com/klauspost/compress v1.11.8 -> v1.12.1 * github.com/mgechev/revive v1.0.3 -> v1.0.6 * github.com/microcosm-cc/bluemonday v1.0.7 -> v1.0.8 * github.com/niklasfasching/go-org v1.4.0 -> v1.5.0 * github.com/olivere/elastic v7.0.22 -> v7.0.24 * github.com/pelletier/go-toml v1.8.1 -> v1.9.0 * github.com/prometheus/client_golang v1.9.0 -> v1.10.0 * github.com/xanzy/go-gitlab v0.44.0 -> v0.48.0 * github.com/yuin/goldmark v1.3.3 -> v1.3.5 * github.com/6543/go-version v1.2.4 -> v1.3.1 * do github.com/lib/pq v1.10.0 -> v1.10.1 again ...
		
			
				
	
	
		
			671 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
			
		
		
	
	
			671 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
| package extension
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"strconv"
 | |
| 
 | |
| 	"github.com/yuin/goldmark"
 | |
| 	gast "github.com/yuin/goldmark/ast"
 | |
| 	"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 footnoteListKey = parser.NewContextKey()
 | |
| var footnoteLinkListKey = parser.NewContextKey()
 | |
| 
 | |
| type footnoteBlockParser struct {
 | |
| }
 | |
| 
 | |
| var defaultFootnoteBlockParser = &footnoteBlockParser{}
 | |
| 
 | |
| // NewFootnoteBlockParser returns a new parser.BlockParser that can parse
 | |
| // footnotes of the Markdown(PHP Markdown Extra) text.
 | |
| func NewFootnoteBlockParser() parser.BlockParser {
 | |
| 	return defaultFootnoteBlockParser
 | |
| }
 | |
| 
 | |
| func (b *footnoteBlockParser) Trigger() []byte {
 | |
| 	return []byte{'['}
 | |
| }
 | |
| 
 | |
| func (b *footnoteBlockParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
 | |
| 	line, segment := reader.PeekLine()
 | |
| 	pos := pc.BlockOffset()
 | |
| 	if pos < 0 || line[pos] != '[' {
 | |
| 		return nil, parser.NoChildren
 | |
| 	}
 | |
| 	pos++
 | |
| 	if pos > len(line)-1 || line[pos] != '^' {
 | |
| 		return nil, parser.NoChildren
 | |
| 	}
 | |
| 	open := pos + 1
 | |
| 	closes := 0
 | |
| 	closure := util.FindClosure(line[pos+1:], '[', ']', false, false)
 | |
| 	closes = pos + 1 + closure
 | |
| 	next := closes + 1
 | |
| 	if closure > -1 {
 | |
| 		if next >= len(line) || line[next] != ':' {
 | |
| 			return nil, parser.NoChildren
 | |
| 		}
 | |
| 	} else {
 | |
| 		return nil, parser.NoChildren
 | |
| 	}
 | |
| 	padding := segment.Padding
 | |
| 	label := reader.Value(text.NewSegment(segment.Start+open-padding, segment.Start+closes-padding))
 | |
| 	if util.IsBlank(label) {
 | |
| 		return nil, parser.NoChildren
 | |
| 	}
 | |
| 	item := ast.NewFootnote(label)
 | |
| 
 | |
| 	pos = next + 1 - padding
 | |
| 	if pos >= len(line) {
 | |
| 		reader.Advance(pos)
 | |
| 		return item, parser.NoChildren
 | |
| 	}
 | |
| 	reader.AdvanceAndSetPadding(pos, padding)
 | |
| 	return item, parser.HasChildren
 | |
| }
 | |
| 
 | |
| func (b *footnoteBlockParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State {
 | |
| 	line, _ := reader.PeekLine()
 | |
| 	if util.IsBlank(line) {
 | |
| 		return parser.Continue | parser.HasChildren
 | |
| 	}
 | |
| 	childpos, padding := util.IndentPosition(line, reader.LineOffset(), 4)
 | |
| 	if childpos < 0 {
 | |
| 		return parser.Close
 | |
| 	}
 | |
| 	reader.AdvanceAndSetPadding(childpos, padding)
 | |
| 	return parser.Continue | parser.HasChildren
 | |
| }
 | |
| 
 | |
| func (b *footnoteBlockParser) Close(node gast.Node, reader text.Reader, pc parser.Context) {
 | |
| 	var list *ast.FootnoteList
 | |
| 	if tlist := pc.Get(footnoteListKey); tlist != nil {
 | |
| 		list = tlist.(*ast.FootnoteList)
 | |
| 	} else {
 | |
| 		list = ast.NewFootnoteList()
 | |
| 		pc.Set(footnoteListKey, list)
 | |
| 		node.Parent().InsertBefore(node.Parent(), node, list)
 | |
| 	}
 | |
| 	node.Parent().RemoveChild(node.Parent(), node)
 | |
| 	list.AppendChild(list, node)
 | |
| }
 | |
| 
 | |
| func (b *footnoteBlockParser) CanInterruptParagraph() bool {
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func (b *footnoteBlockParser) CanAcceptIndentedLine() bool {
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| type footnoteParser struct {
 | |
| }
 | |
| 
 | |
| var defaultFootnoteParser = &footnoteParser{}
 | |
| 
 | |
| // NewFootnoteParser returns a new parser.InlineParser that can parse
 | |
| // footnote links of the Markdown(PHP Markdown Extra) text.
 | |
| func NewFootnoteParser() parser.InlineParser {
 | |
| 	return defaultFootnoteParser
 | |
| }
 | |
| 
 | |
| func (s *footnoteParser) Trigger() []byte {
 | |
| 	// footnote syntax probably conflict with the image syntax.
 | |
| 	// So we need trigger this parser with '!'.
 | |
| 	return []byte{'!', '['}
 | |
| }
 | |
| 
 | |
| func (s *footnoteParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
 | |
| 	line, segment := block.PeekLine()
 | |
| 	pos := 1
 | |
| 	if len(line) > 0 && line[0] == '!' {
 | |
| 		pos++
 | |
| 	}
 | |
| 	if pos >= len(line) || line[pos] != '^' {
 | |
| 		return nil
 | |
| 	}
 | |
| 	pos++
 | |
| 	if pos >= len(line) {
 | |
| 		return nil
 | |
| 	}
 | |
| 	open := pos
 | |
| 	closure := util.FindClosure(line[pos:], '[', ']', false, false)
 | |
| 	if closure < 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 	closes := pos + closure
 | |
| 	value := block.Value(text.NewSegment(segment.Start+open, segment.Start+closes))
 | |
| 	block.Advance(closes + 1)
 | |
| 
 | |
| 	var list *ast.FootnoteList
 | |
| 	if tlist := pc.Get(footnoteListKey); tlist != nil {
 | |
| 		list = tlist.(*ast.FootnoteList)
 | |
| 	}
 | |
| 	if list == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 	index := 0
 | |
| 	for def := list.FirstChild(); def != nil; def = def.NextSibling() {
 | |
| 		d := def.(*ast.Footnote)
 | |
| 		if bytes.Equal(d.Ref, value) {
 | |
| 			if d.Index < 0 {
 | |
| 				list.Count += 1
 | |
| 				d.Index = list.Count
 | |
| 			}
 | |
| 			index = d.Index
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 	if index == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	fnlink := ast.NewFootnoteLink(index)
 | |
| 	var fnlist []*ast.FootnoteLink
 | |
| 	if tmp := pc.Get(footnoteLinkListKey); tmp != nil {
 | |
| 		fnlist = tmp.([]*ast.FootnoteLink)
 | |
| 	} else {
 | |
| 		fnlist = []*ast.FootnoteLink{}
 | |
| 		pc.Set(footnoteLinkListKey, fnlist)
 | |
| 	}
 | |
| 	pc.Set(footnoteLinkListKey, append(fnlist, fnlink))
 | |
| 	if line[0] == '!' {
 | |
| 		parent.AppendChild(parent, gast.NewTextSegment(text.NewSegment(segment.Start, segment.Start+1)))
 | |
| 	}
 | |
| 
 | |
| 	return fnlink
 | |
| }
 | |
| 
 | |
| type footnoteASTTransformer struct {
 | |
| }
 | |
| 
 | |
| var defaultFootnoteASTTransformer = &footnoteASTTransformer{}
 | |
| 
 | |
| // NewFootnoteASTTransformer returns a new parser.ASTTransformer that
 | |
| // insert a footnote list to the last of the document.
 | |
| func NewFootnoteASTTransformer() parser.ASTTransformer {
 | |
| 	return defaultFootnoteASTTransformer
 | |
| }
 | |
| 
 | |
| func (a *footnoteASTTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) {
 | |
| 	var list *ast.FootnoteList
 | |
| 	var fnlist []*ast.FootnoteLink
 | |
| 	if tmp := pc.Get(footnoteListKey); tmp != nil {
 | |
| 		list = tmp.(*ast.FootnoteList)
 | |
| 	}
 | |
| 	if tmp := pc.Get(footnoteLinkListKey); tmp != nil {
 | |
| 		fnlist = tmp.([]*ast.FootnoteLink)
 | |
| 	}
 | |
| 
 | |
| 	pc.Set(footnoteListKey, nil)
 | |
| 	pc.Set(footnoteLinkListKey, nil)
 | |
| 
 | |
| 	if list == nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	counter := map[int]int{}
 | |
| 	if fnlist != nil {
 | |
| 		for _, fnlink := range fnlist {
 | |
| 			if fnlink.Index >= 0 {
 | |
| 				counter[fnlink.Index]++
 | |
| 			}
 | |
| 		}
 | |
| 		for _, fnlink := range fnlist {
 | |
| 			fnlink.RefCount = counter[fnlink.Index]
 | |
| 		}
 | |
| 	}
 | |
| 	for footnote := list.FirstChild(); footnote != nil; {
 | |
| 		var container gast.Node = footnote
 | |
| 		next := footnote.NextSibling()
 | |
| 		if fc := container.LastChild(); fc != nil && gast.IsParagraph(fc) {
 | |
| 			container = fc
 | |
| 		}
 | |
| 		fn := footnote.(*ast.Footnote)
 | |
| 		index := fn.Index
 | |
| 		if index < 0 {
 | |
| 			list.RemoveChild(list, footnote)
 | |
| 		} else {
 | |
| 			backLink := ast.NewFootnoteBacklink(index)
 | |
| 			backLink.RefCount = counter[index]
 | |
| 			container.AppendChild(container, backLink)
 | |
| 		}
 | |
| 		footnote = next
 | |
| 	}
 | |
| 	list.SortChildren(func(n1, n2 gast.Node) int {
 | |
| 		if n1.(*ast.Footnote).Index < n2.(*ast.Footnote).Index {
 | |
| 			return -1
 | |
| 		}
 | |
| 		return 1
 | |
| 	})
 | |
| 	if list.Count <= 0 {
 | |
| 		list.Parent().RemoveChild(list.Parent(), list)
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	node.AppendChild(node, list)
 | |
| }
 | |
| 
 | |
| // FootnoteConfig holds configuration values for the footnote extension.
 | |
| //
 | |
| // Link* and Backlink* configurations have some variables:
 | |
| // Occurrances of “^^” in the string will be replaced by the
 | |
| // corresponding footnote number in the HTML output.
 | |
| // Occurrances of “%%” will be replaced by a number for the
 | |
| // reference (footnotes can have multiple references).
 | |
| type FootnoteConfig struct {
 | |
| 	html.Config
 | |
| 
 | |
| 	// IDPrefix is a prefix for the id attributes generated by footnotes.
 | |
| 	IDPrefix []byte
 | |
| 
 | |
| 	// IDPrefix is a function that determines the id attribute for given Node.
 | |
| 	IDPrefixFunction func(gast.Node) []byte
 | |
| 
 | |
| 	// LinkTitle is an optional title attribute for footnote links.
 | |
| 	LinkTitle []byte
 | |
| 
 | |
| 	// BacklinkTitle is an optional title attribute for footnote backlinks.
 | |
| 	BacklinkTitle []byte
 | |
| 
 | |
| 	// LinkClass is a class for footnote links.
 | |
| 	LinkClass []byte
 | |
| 
 | |
| 	// BacklinkClass is a class for footnote backlinks.
 | |
| 	BacklinkClass []byte
 | |
| 
 | |
| 	// BacklinkHTML is an HTML content for footnote backlinks.
 | |
| 	BacklinkHTML []byte
 | |
| }
 | |
| 
 | |
| // FootnoteOption interface is a functional option interface for the extension.
 | |
| type FootnoteOption interface {
 | |
| 	renderer.Option
 | |
| 	// SetFootnoteOption sets given option to the extension.
 | |
| 	SetFootnoteOption(*FootnoteConfig)
 | |
| }
 | |
| 
 | |
| // NewFootnoteConfig returns a new Config with defaults.
 | |
| func NewFootnoteConfig() FootnoteConfig {
 | |
| 	return FootnoteConfig{
 | |
| 		Config:        html.NewConfig(),
 | |
| 		LinkTitle:     []byte(""),
 | |
| 		BacklinkTitle: []byte(""),
 | |
| 		LinkClass:     []byte("footnote-ref"),
 | |
| 		BacklinkClass: []byte("footnote-backref"),
 | |
| 		BacklinkHTML:  []byte("↩︎"),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // SetOption implements renderer.SetOptioner.
 | |
| func (c *FootnoteConfig) SetOption(name renderer.OptionName, value interface{}) {
 | |
| 	switch name {
 | |
| 	case optFootnoteIDPrefixFunction:
 | |
| 		c.IDPrefixFunction = value.(func(gast.Node) []byte)
 | |
| 	case optFootnoteIDPrefix:
 | |
| 		c.IDPrefix = value.([]byte)
 | |
| 	case optFootnoteLinkTitle:
 | |
| 		c.LinkTitle = value.([]byte)
 | |
| 	case optFootnoteBacklinkTitle:
 | |
| 		c.BacklinkTitle = value.([]byte)
 | |
| 	case optFootnoteLinkClass:
 | |
| 		c.LinkClass = value.([]byte)
 | |
| 	case optFootnoteBacklinkClass:
 | |
| 		c.BacklinkClass = value.([]byte)
 | |
| 	case optFootnoteBacklinkHTML:
 | |
| 		c.BacklinkHTML = value.([]byte)
 | |
| 	default:
 | |
| 		c.Config.SetOption(name, value)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type withFootnoteHTMLOptions struct {
 | |
| 	value []html.Option
 | |
| }
 | |
| 
 | |
| func (o *withFootnoteHTMLOptions) SetConfig(c *renderer.Config) {
 | |
| 	if o.value != nil {
 | |
| 		for _, v := range o.value {
 | |
| 			v.(renderer.Option).SetConfig(c)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (o *withFootnoteHTMLOptions) SetFootnoteOption(c *FootnoteConfig) {
 | |
| 	if o.value != nil {
 | |
| 		for _, v := range o.value {
 | |
| 			v.SetHTMLOption(&c.Config)
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // WithFootnoteHTMLOptions is functional option that wraps goldmark HTMLRenderer options.
 | |
| func WithFootnoteHTMLOptions(opts ...html.Option) FootnoteOption {
 | |
| 	return &withFootnoteHTMLOptions{opts}
 | |
| }
 | |
| 
 | |
| const optFootnoteIDPrefix renderer.OptionName = "FootnoteIDPrefix"
 | |
| 
 | |
| type withFootnoteIDPrefix struct {
 | |
| 	value []byte
 | |
| }
 | |
| 
 | |
| func (o *withFootnoteIDPrefix) SetConfig(c *renderer.Config) {
 | |
| 	c.Options[optFootnoteIDPrefix] = o.value
 | |
| }
 | |
| 
 | |
| func (o *withFootnoteIDPrefix) SetFootnoteOption(c *FootnoteConfig) {
 | |
| 	c.IDPrefix = o.value
 | |
| }
 | |
| 
 | |
| // WithFootnoteIDPrefix is a functional option that is a prefix for the id attributes generated by footnotes.
 | |
| func WithFootnoteIDPrefix(a []byte) FootnoteOption {
 | |
| 	return &withFootnoteIDPrefix{a}
 | |
| }
 | |
| 
 | |
| const optFootnoteIDPrefixFunction renderer.OptionName = "FootnoteIDPrefixFunction"
 | |
| 
 | |
| type withFootnoteIDPrefixFunction struct {
 | |
| 	value func(gast.Node) []byte
 | |
| }
 | |
| 
 | |
| func (o *withFootnoteIDPrefixFunction) SetConfig(c *renderer.Config) {
 | |
| 	c.Options[optFootnoteIDPrefixFunction] = o.value
 | |
| }
 | |
| 
 | |
| func (o *withFootnoteIDPrefixFunction) SetFootnoteOption(c *FootnoteConfig) {
 | |
| 	c.IDPrefixFunction = o.value
 | |
| }
 | |
| 
 | |
| // WithFootnoteIDPrefixFunction is a functional option that is a prefix for the id attributes generated by footnotes.
 | |
| func WithFootnoteIDPrefixFunction(a func(gast.Node) []byte) FootnoteOption {
 | |
| 	return &withFootnoteIDPrefixFunction{a}
 | |
| }
 | |
| 
 | |
| const optFootnoteLinkTitle renderer.OptionName = "FootnoteLinkTitle"
 | |
| 
 | |
| type withFootnoteLinkTitle struct {
 | |
| 	value []byte
 | |
| }
 | |
| 
 | |
| func (o *withFootnoteLinkTitle) SetConfig(c *renderer.Config) {
 | |
| 	c.Options[optFootnoteLinkTitle] = o.value
 | |
| }
 | |
| 
 | |
| func (o *withFootnoteLinkTitle) SetFootnoteOption(c *FootnoteConfig) {
 | |
| 	c.LinkTitle = o.value
 | |
| }
 | |
| 
 | |
| // WithFootnoteLinkTitle is a functional option that is an optional title attribute for footnote links.
 | |
| func WithFootnoteLinkTitle(a []byte) FootnoteOption {
 | |
| 	return &withFootnoteLinkTitle{a}
 | |
| }
 | |
| 
 | |
| const optFootnoteBacklinkTitle renderer.OptionName = "FootnoteBacklinkTitle"
 | |
| 
 | |
| type withFootnoteBacklinkTitle struct {
 | |
| 	value []byte
 | |
| }
 | |
| 
 | |
| func (o *withFootnoteBacklinkTitle) SetConfig(c *renderer.Config) {
 | |
| 	c.Options[optFootnoteBacklinkTitle] = o.value
 | |
| }
 | |
| 
 | |
| func (o *withFootnoteBacklinkTitle) SetFootnoteOption(c *FootnoteConfig) {
 | |
| 	c.BacklinkTitle = o.value
 | |
| }
 | |
| 
 | |
| // WithFootnoteBacklinkTitle is a functional option that is an optional title attribute for footnote backlinks.
 | |
| func WithFootnoteBacklinkTitle(a []byte) FootnoteOption {
 | |
| 	return &withFootnoteBacklinkTitle{a}
 | |
| }
 | |
| 
 | |
| const optFootnoteLinkClass renderer.OptionName = "FootnoteLinkClass"
 | |
| 
 | |
| type withFootnoteLinkClass struct {
 | |
| 	value []byte
 | |
| }
 | |
| 
 | |
| func (o *withFootnoteLinkClass) SetConfig(c *renderer.Config) {
 | |
| 	c.Options[optFootnoteLinkClass] = o.value
 | |
| }
 | |
| 
 | |
| func (o *withFootnoteLinkClass) SetFootnoteOption(c *FootnoteConfig) {
 | |
| 	c.LinkClass = o.value
 | |
| }
 | |
| 
 | |
| // WithFootnoteLinkClass is a functional option that is a class for footnote links.
 | |
| func WithFootnoteLinkClass(a []byte) FootnoteOption {
 | |
| 	return &withFootnoteLinkClass{a}
 | |
| }
 | |
| 
 | |
| const optFootnoteBacklinkClass renderer.OptionName = "FootnoteBacklinkClass"
 | |
| 
 | |
| type withFootnoteBacklinkClass struct {
 | |
| 	value []byte
 | |
| }
 | |
| 
 | |
| func (o *withFootnoteBacklinkClass) SetConfig(c *renderer.Config) {
 | |
| 	c.Options[optFootnoteBacklinkClass] = o.value
 | |
| }
 | |
| 
 | |
| func (o *withFootnoteBacklinkClass) SetFootnoteOption(c *FootnoteConfig) {
 | |
| 	c.BacklinkClass = o.value
 | |
| }
 | |
| 
 | |
| // WithFootnoteBacklinkClass is a functional option that is a class for footnote backlinks.
 | |
| func WithFootnoteBacklinkClass(a []byte) FootnoteOption {
 | |
| 	return &withFootnoteBacklinkClass{a}
 | |
| }
 | |
| 
 | |
| const optFootnoteBacklinkHTML renderer.OptionName = "FootnoteBacklinkHTML"
 | |
| 
 | |
| type withFootnoteBacklinkHTML struct {
 | |
| 	value []byte
 | |
| }
 | |
| 
 | |
| func (o *withFootnoteBacklinkHTML) SetConfig(c *renderer.Config) {
 | |
| 	c.Options[optFootnoteBacklinkHTML] = o.value
 | |
| }
 | |
| 
 | |
| func (o *withFootnoteBacklinkHTML) SetFootnoteOption(c *FootnoteConfig) {
 | |
| 	c.BacklinkHTML = o.value
 | |
| }
 | |
| 
 | |
| // WithFootnoteBacklinkHTML is an HTML content for footnote backlinks.
 | |
| func WithFootnoteBacklinkHTML(a []byte) FootnoteOption {
 | |
| 	return &withFootnoteBacklinkHTML{a}
 | |
| }
 | |
| 
 | |
| // FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that
 | |
| // renders FootnoteLink nodes.
 | |
| type FootnoteHTMLRenderer struct {
 | |
| 	FootnoteConfig
 | |
| }
 | |
| 
 | |
| // NewFootnoteHTMLRenderer returns a new FootnoteHTMLRenderer.
 | |
| func NewFootnoteHTMLRenderer(opts ...FootnoteOption) renderer.NodeRenderer {
 | |
| 	r := &FootnoteHTMLRenderer{
 | |
| 		FootnoteConfig: NewFootnoteConfig(),
 | |
| 	}
 | |
| 	for _, opt := range opts {
 | |
| 		opt.SetFootnoteOption(&r.FootnoteConfig)
 | |
| 	}
 | |
| 	return r
 | |
| }
 | |
| 
 | |
| // RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
 | |
| func (r *FootnoteHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
 | |
| 	reg.Register(ast.KindFootnoteLink, r.renderFootnoteLink)
 | |
| 	reg.Register(ast.KindFootnoteBacklink, r.renderFootnoteBacklink)
 | |
| 	reg.Register(ast.KindFootnote, r.renderFootnote)
 | |
| 	reg.Register(ast.KindFootnoteList, r.renderFootnoteList)
 | |
| }
 | |
| 
 | |
| func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
 | |
| 	if entering {
 | |
| 		n := node.(*ast.FootnoteLink)
 | |
| 		is := strconv.Itoa(n.Index)
 | |
| 		_, _ = w.WriteString(`<sup id="`)
 | |
| 		_, _ = w.Write(r.idPrefix(node))
 | |
| 		_, _ = w.WriteString(`fnref:`)
 | |
| 		_, _ = w.WriteString(is)
 | |
| 		_, _ = w.WriteString(`"><a href="#`)
 | |
| 		_, _ = w.Write(r.idPrefix(node))
 | |
| 		_, _ = w.WriteString(`fn:`)
 | |
| 		_, _ = w.WriteString(is)
 | |
| 		_, _ = w.WriteString(`" class="`)
 | |
| 		_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.LinkClass,
 | |
| 			n.Index, n.RefCount))
 | |
| 		if len(r.FootnoteConfig.LinkTitle) > 0 {
 | |
| 			_, _ = w.WriteString(`" title="`)
 | |
| 			_, _ = w.Write(util.EscapeHTML(applyFootnoteTemplate(r.FootnoteConfig.LinkTitle, n.Index, n.RefCount)))
 | |
| 		}
 | |
| 		_, _ = w.WriteString(`" role="doc-noteref">`)
 | |
| 
 | |
| 		_, _ = w.WriteString(is)
 | |
| 		_, _ = w.WriteString(`</a></sup>`)
 | |
| 	}
 | |
| 	return gast.WalkContinue, nil
 | |
| }
 | |
| 
 | |
| func (r *FootnoteHTMLRenderer) renderFootnoteBacklink(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
 | |
| 	if entering {
 | |
| 		n := node.(*ast.FootnoteBacklink)
 | |
| 		is := strconv.Itoa(n.Index)
 | |
| 		_, _ = w.WriteString(` <a href="#`)
 | |
| 		_, _ = w.Write(r.idPrefix(node))
 | |
| 		_, _ = w.WriteString(`fnref:`)
 | |
| 		_, _ = w.WriteString(is)
 | |
| 		_, _ = w.WriteString(`" class="`)
 | |
| 		_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.BacklinkClass, n.Index, n.RefCount))
 | |
| 		if len(r.FootnoteConfig.BacklinkTitle) > 0 {
 | |
| 			_, _ = w.WriteString(`" title="`)
 | |
| 			_, _ = w.Write(util.EscapeHTML(applyFootnoteTemplate(r.FootnoteConfig.BacklinkTitle, n.Index, n.RefCount)))
 | |
| 		}
 | |
| 		_, _ = w.WriteString(`" role="doc-backlink">`)
 | |
| 		_, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.BacklinkHTML, n.Index, n.RefCount))
 | |
| 		_, _ = w.WriteString(`</a>`)
 | |
| 	}
 | |
| 	return gast.WalkContinue, nil
 | |
| }
 | |
| 
 | |
| func (r *FootnoteHTMLRenderer) renderFootnote(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
 | |
| 	n := node.(*ast.Footnote)
 | |
| 	is := strconv.Itoa(n.Index)
 | |
| 	if entering {
 | |
| 		_, _ = w.WriteString(`<li id="`)
 | |
| 		_, _ = w.Write(r.idPrefix(node))
 | |
| 		_, _ = w.WriteString(`fn:`)
 | |
| 		_, _ = w.WriteString(is)
 | |
| 		_, _ = w.WriteString(`" role="doc-endnote"`)
 | |
| 		if node.Attributes() != nil {
 | |
| 			html.RenderAttributes(w, node, html.ListItemAttributeFilter)
 | |
| 		}
 | |
| 		_, _ = w.WriteString(">\n")
 | |
| 	} else {
 | |
| 		_, _ = w.WriteString("</li>\n")
 | |
| 	}
 | |
| 	return gast.WalkContinue, nil
 | |
| }
 | |
| 
 | |
| func (r *FootnoteHTMLRenderer) renderFootnoteList(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
 | |
| 	tag := "section"
 | |
| 	if r.Config.XHTML {
 | |
| 		tag = "div"
 | |
| 	}
 | |
| 	if entering {
 | |
| 		_, _ = w.WriteString("<")
 | |
| 		_, _ = w.WriteString(tag)
 | |
| 		_, _ = w.WriteString(` class="footnotes" role="doc-endnotes"`)
 | |
| 		if node.Attributes() != nil {
 | |
| 			html.RenderAttributes(w, node, html.GlobalAttributeFilter)
 | |
| 		}
 | |
| 		_ = w.WriteByte('>')
 | |
| 		if r.Config.XHTML {
 | |
| 			_, _ = w.WriteString("\n<hr />\n")
 | |
| 		} else {
 | |
| 			_, _ = w.WriteString("\n<hr>\n")
 | |
| 		}
 | |
| 		_, _ = w.WriteString("<ol>\n")
 | |
| 	} else {
 | |
| 		_, _ = w.WriteString("</ol>\n")
 | |
| 		_, _ = w.WriteString("</")
 | |
| 		_, _ = w.WriteString(tag)
 | |
| 		_, _ = w.WriteString(">\n")
 | |
| 	}
 | |
| 	return gast.WalkContinue, nil
 | |
| }
 | |
| 
 | |
| func (r *FootnoteHTMLRenderer) idPrefix(node gast.Node) []byte {
 | |
| 	if r.FootnoteConfig.IDPrefix != nil {
 | |
| 		return r.FootnoteConfig.IDPrefix
 | |
| 	}
 | |
| 	if r.FootnoteConfig.IDPrefixFunction != nil {
 | |
| 		return r.FootnoteConfig.IDPrefixFunction(node)
 | |
| 	}
 | |
| 	return []byte("")
 | |
| }
 | |
| 
 | |
| func applyFootnoteTemplate(b []byte, index, refCount int) []byte {
 | |
| 	fast := true
 | |
| 	for i, c := range b {
 | |
| 		if i != 0 {
 | |
| 			if b[i-1] == '^' && c == '^' {
 | |
| 				fast = false
 | |
| 				break
 | |
| 			}
 | |
| 			if b[i-1] == '%' && c == '%' {
 | |
| 				fast = false
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 	if fast {
 | |
| 		return b
 | |
| 	}
 | |
| 	is := []byte(strconv.Itoa(index))
 | |
| 	rs := []byte(strconv.Itoa(refCount))
 | |
| 	ret := bytes.Replace(b, []byte("^^"), is, -1)
 | |
| 	return bytes.Replace(ret, []byte("%%"), rs, -1)
 | |
| }
 | |
| 
 | |
| type footnote struct {
 | |
| 	options []FootnoteOption
 | |
| }
 | |
| 
 | |
| // Footnote is an extension that allow you to use PHP Markdown Extra Footnotes.
 | |
| var Footnote = &footnote{
 | |
| 	options: []FootnoteOption{},
 | |
| }
 | |
| 
 | |
| // NewFootnote returns a new extension with given options.
 | |
| func NewFootnote(opts ...FootnoteOption) goldmark.Extender {
 | |
| 	return &footnote{
 | |
| 		options: opts,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (e *footnote) Extend(m goldmark.Markdown) {
 | |
| 	m.Parser().AddOptions(
 | |
| 		parser.WithBlockParsers(
 | |
| 			util.Prioritized(NewFootnoteBlockParser(), 999),
 | |
| 		),
 | |
| 		parser.WithInlineParsers(
 | |
| 			util.Prioritized(NewFootnoteParser(), 101),
 | |
| 		),
 | |
| 		parser.WithASTTransformers(
 | |
| 			util.Prioritized(NewFootnoteASTTransformer(), 999),
 | |
| 		),
 | |
| 	)
 | |
| 	m.Renderer().AddOptions(renderer.WithNodeRenderers(
 | |
| 		util.Prioritized(NewFootnoteHTMLRenderer(e.options...), 500),
 | |
| 	))
 | |
| }
 |