1package extension
2
3import (
4 "bytes"
5 "fmt"
6 "strconv"
7
8 "github.com/yuin/goldmark"
9 gast "github.com/yuin/goldmark/ast"
10 "github.com/yuin/goldmark/extension/ast"
11 "github.com/yuin/goldmark/parser"
12 "github.com/yuin/goldmark/renderer"
13 "github.com/yuin/goldmark/renderer/html"
14 "github.com/yuin/goldmark/text"
15 "github.com/yuin/goldmark/util"
16)
17
18var footnoteListKey = parser.NewContextKey()
19var footnoteLinkListKey = parser.NewContextKey()
20
21type footnoteBlockParser struct {
22}
23
24var defaultFootnoteBlockParser = &footnoteBlockParser{}
25
26// NewFootnoteBlockParser returns a new parser.BlockParser that can parse
27// footnotes of the Markdown(PHP Markdown Extra) text.
28func NewFootnoteBlockParser() parser.BlockParser {
29 return defaultFootnoteBlockParser
30}
31
32func (b *footnoteBlockParser) Trigger() []byte {
33 return []byte{'['}
34}
35
36func (b *footnoteBlockParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
37 line, segment := reader.PeekLine()
38 pos := pc.BlockOffset()
39 if pos < 0 || line[pos] != '[' {
40 return nil, parser.NoChildren
41 }
42 pos++
43 if pos > len(line)-1 || line[pos] != '^' {
44 return nil, parser.NoChildren
45 }
46 open := pos + 1
47 var closes int
48 closure := util.FindClosure(line[pos+1:], '[', ']', false, false) //nolint:staticcheck
49 closes = pos + 1 + closure
50 next := closes + 1
51 if closure > -1 {
52 if next >= len(line) || line[next] != ':' {
53 return nil, parser.NoChildren
54 }
55 } else {
56 return nil, parser.NoChildren
57 }
58 padding := segment.Padding
59 label := reader.Value(text.NewSegment(segment.Start+open-padding, segment.Start+closes-padding))
60 if util.IsBlank(label) {
61 return nil, parser.NoChildren
62 }
63 item := ast.NewFootnote(label)
64
65 pos = next + 1 - padding
66 if pos >= len(line) {
67 reader.Advance(pos)
68 return item, parser.NoChildren
69 }
70 reader.AdvanceAndSetPadding(pos, padding)
71 return item, parser.HasChildren
72}
73
74func (b *footnoteBlockParser) Continue(node gast.Node, reader text.Reader, pc parser.Context) parser.State {
75 line, _ := reader.PeekLine()
76 if util.IsBlank(line) {
77 return parser.Continue | parser.HasChildren
78 }
79 childpos, padding := util.IndentPosition(line, reader.LineOffset(), 4)
80 if childpos < 0 {
81 return parser.Close
82 }
83 reader.AdvanceAndSetPadding(childpos, padding)
84 return parser.Continue | parser.HasChildren
85}
86
87func (b *footnoteBlockParser) Close(node gast.Node, reader text.Reader, pc parser.Context) {
88 var list *ast.FootnoteList
89 if tlist := pc.Get(footnoteListKey); tlist != nil {
90 list = tlist.(*ast.FootnoteList)
91 } else {
92 list = ast.NewFootnoteList()
93 pc.Set(footnoteListKey, list)
94 node.Parent().InsertBefore(node.Parent(), node, list)
95 }
96 node.Parent().RemoveChild(node.Parent(), node)
97 list.AppendChild(list, node)
98}
99
100func (b *footnoteBlockParser) CanInterruptParagraph() bool {
101 return true
102}
103
104func (b *footnoteBlockParser) CanAcceptIndentedLine() bool {
105 return false
106}
107
108type footnoteParser struct {
109}
110
111var defaultFootnoteParser = &footnoteParser{}
112
113// NewFootnoteParser returns a new parser.InlineParser that can parse
114// footnote links of the Markdown(PHP Markdown Extra) text.
115func NewFootnoteParser() parser.InlineParser {
116 return defaultFootnoteParser
117}
118
119func (s *footnoteParser) Trigger() []byte {
120 // footnote syntax probably conflict with the image syntax.
121 // So we need trigger this parser with '!'.
122 return []byte{'!', '['}
123}
124
125func (s *footnoteParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
126 line, segment := block.PeekLine()
127 pos := 1
128 if len(line) > 0 && line[0] == '!' {
129 pos++
130 }
131 if pos >= len(line) || line[pos] != '^' {
132 return nil
133 }
134 pos++
135 if pos >= len(line) {
136 return nil
137 }
138 open := pos
139 closure := util.FindClosure(line[pos:], '[', ']', false, false) //nolint:staticcheck
140 if closure < 0 {
141 return nil
142 }
143 closes := pos + closure
144 value := block.Value(text.NewSegment(segment.Start+open, segment.Start+closes))
145 block.Advance(closes + 1)
146
147 var list *ast.FootnoteList
148 if tlist := pc.Get(footnoteListKey); tlist != nil {
149 list = tlist.(*ast.FootnoteList)
150 }
151 if list == nil {
152 return nil
153 }
154 index := 0
155 for def := list.FirstChild(); def != nil; def = def.NextSibling() {
156 d := def.(*ast.Footnote)
157 if bytes.Equal(d.Ref, value) {
158 if d.Index < 0 {
159 list.Count++
160 d.Index = list.Count
161 }
162 index = d.Index
163 break
164 }
165 }
166 if index == 0 {
167 return nil
168 }
169
170 fnlink := ast.NewFootnoteLink(index)
171 var fnlist []*ast.FootnoteLink
172 if tmp := pc.Get(footnoteLinkListKey); tmp != nil {
173 fnlist = tmp.([]*ast.FootnoteLink)
174 } else {
175 fnlist = []*ast.FootnoteLink{}
176 pc.Set(footnoteLinkListKey, fnlist)
177 }
178 pc.Set(footnoteLinkListKey, append(fnlist, fnlink))
179 if line[0] == '!' {
180 parent.AppendChild(parent, gast.NewTextSegment(text.NewSegment(segment.Start, segment.Start+1)))
181 }
182
183 return fnlink
184}
185
186type footnoteASTTransformer struct {
187}
188
189var defaultFootnoteASTTransformer = &footnoteASTTransformer{}
190
191// NewFootnoteASTTransformer returns a new parser.ASTTransformer that
192// insert a footnote list to the last of the document.
193func NewFootnoteASTTransformer() parser.ASTTransformer {
194 return defaultFootnoteASTTransformer
195}
196
197func (a *footnoteASTTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) {
198 var list *ast.FootnoteList
199 var fnlist []*ast.FootnoteLink
200 if tmp := pc.Get(footnoteListKey); tmp != nil {
201 list = tmp.(*ast.FootnoteList)
202 }
203 if tmp := pc.Get(footnoteLinkListKey); tmp != nil {
204 fnlist = tmp.([]*ast.FootnoteLink)
205 }
206
207 pc.Set(footnoteListKey, nil)
208 pc.Set(footnoteLinkListKey, nil)
209
210 if list == nil {
211 return
212 }
213
214 counter := map[int]int{}
215 if fnlist != nil {
216 for _, fnlink := range fnlist {
217 if fnlink.Index >= 0 {
218 counter[fnlink.Index]++
219 }
220 }
221 refCounter := map[int]int{}
222 for _, fnlink := range fnlist {
223 fnlink.RefCount = counter[fnlink.Index]
224 if _, ok := refCounter[fnlink.Index]; !ok {
225 refCounter[fnlink.Index] = 0
226 }
227 fnlink.RefIndex = refCounter[fnlink.Index]
228 refCounter[fnlink.Index]++
229 }
230 }
231 for footnote := list.FirstChild(); footnote != nil; {
232 var container gast.Node = footnote
233 next := footnote.NextSibling()
234 if fc := container.LastChild(); fc != nil && gast.IsParagraph(fc) {
235 container = fc
236 }
237 fn := footnote.(*ast.Footnote)
238 index := fn.Index
239 if index < 0 {
240 list.RemoveChild(list, footnote)
241 } else {
242 refCount := counter[index]
243 backLink := ast.NewFootnoteBacklink(index)
244 backLink.RefCount = refCount
245 backLink.RefIndex = 0
246 container.AppendChild(container, backLink)
247 if refCount > 1 {
248 for i := 1; i < refCount; i++ {
249 backLink := ast.NewFootnoteBacklink(index)
250 backLink.RefCount = refCount
251 backLink.RefIndex = i
252 container.AppendChild(container, backLink)
253 }
254 }
255 }
256 footnote = next
257 }
258 list.SortChildren(func(n1, n2 gast.Node) int {
259 if n1.(*ast.Footnote).Index < n2.(*ast.Footnote).Index {
260 return -1
261 }
262 return 1
263 })
264 if list.Count <= 0 {
265 list.Parent().RemoveChild(list.Parent(), list)
266 return
267 }
268
269 node.AppendChild(node, list)
270}
271
272// FootnoteConfig holds configuration values for the footnote extension.
273//
274// Link* and Backlink* configurations have some variables:
275// Occurrences of “^^” in the string will be replaced by the
276// corresponding footnote number in the HTML output.
277// Occurrences of “%%” will be replaced by a number for the
278// reference (footnotes can have multiple references).
279type FootnoteConfig struct {
280 html.Config
281
282 // IDPrefix is a prefix for the id attributes generated by footnotes.
283 IDPrefix []byte
284
285 // IDPrefix is a function that determines the id attribute for given Node.
286 IDPrefixFunction func(gast.Node) []byte
287
288 // LinkTitle is an optional title attribute for footnote links.
289 LinkTitle []byte
290
291 // BacklinkTitle is an optional title attribute for footnote backlinks.
292 BacklinkTitle []byte
293
294 // LinkClass is a class for footnote links.
295 LinkClass []byte
296
297 // BacklinkClass is a class for footnote backlinks.
298 BacklinkClass []byte
299
300 // BacklinkHTML is an HTML content for footnote backlinks.
301 BacklinkHTML []byte
302}
303
304// FootnoteOption interface is a functional option interface for the extension.
305type FootnoteOption interface {
306 renderer.Option
307 // SetFootnoteOption sets given option to the extension.
308 SetFootnoteOption(*FootnoteConfig)
309}
310
311// NewFootnoteConfig returns a new Config with defaults.
312func NewFootnoteConfig() FootnoteConfig {
313 return FootnoteConfig{
314 Config: html.NewConfig(),
315 LinkTitle: []byte(""),
316 BacklinkTitle: []byte(""),
317 LinkClass: []byte("footnote-ref"),
318 BacklinkClass: []byte("footnote-backref"),
319 BacklinkHTML: []byte("↩︎"),
320 }
321}
322
323// SetOption implements renderer.SetOptioner.
324func (c *FootnoteConfig) SetOption(name renderer.OptionName, value interface{}) {
325 switch name {
326 case optFootnoteIDPrefixFunction:
327 c.IDPrefixFunction = value.(func(gast.Node) []byte)
328 case optFootnoteIDPrefix:
329 c.IDPrefix = value.([]byte)
330 case optFootnoteLinkTitle:
331 c.LinkTitle = value.([]byte)
332 case optFootnoteBacklinkTitle:
333 c.BacklinkTitle = value.([]byte)
334 case optFootnoteLinkClass:
335 c.LinkClass = value.([]byte)
336 case optFootnoteBacklinkClass:
337 c.BacklinkClass = value.([]byte)
338 case optFootnoteBacklinkHTML:
339 c.BacklinkHTML = value.([]byte)
340 default:
341 c.Config.SetOption(name, value)
342 }
343}
344
345type withFootnoteHTMLOptions struct {
346 value []html.Option
347}
348
349func (o *withFootnoteHTMLOptions) SetConfig(c *renderer.Config) {
350 if o.value != nil {
351 for _, v := range o.value {
352 v.(renderer.Option).SetConfig(c)
353 }
354 }
355}
356
357func (o *withFootnoteHTMLOptions) SetFootnoteOption(c *FootnoteConfig) {
358 if o.value != nil {
359 for _, v := range o.value {
360 v.SetHTMLOption(&c.Config)
361 }
362 }
363}
364
365// WithFootnoteHTMLOptions is functional option that wraps goldmark HTMLRenderer options.
366func WithFootnoteHTMLOptions(opts ...html.Option) FootnoteOption {
367 return &withFootnoteHTMLOptions{opts}
368}
369
370const optFootnoteIDPrefix renderer.OptionName = "FootnoteIDPrefix"
371
372type withFootnoteIDPrefix struct {
373 value []byte
374}
375
376func (o *withFootnoteIDPrefix) SetConfig(c *renderer.Config) {
377 c.Options[optFootnoteIDPrefix] = o.value
378}
379
380func (o *withFootnoteIDPrefix) SetFootnoteOption(c *FootnoteConfig) {
381 c.IDPrefix = o.value
382}
383
384// WithFootnoteIDPrefix is a functional option that is a prefix for the id attributes generated by footnotes.
385func WithFootnoteIDPrefix[T []byte | string](a T) FootnoteOption {
386 return &withFootnoteIDPrefix{[]byte(a)}
387}
388
389const optFootnoteIDPrefixFunction renderer.OptionName = "FootnoteIDPrefixFunction"
390
391type withFootnoteIDPrefixFunction struct {
392 value func(gast.Node) []byte
393}
394
395func (o *withFootnoteIDPrefixFunction) SetConfig(c *renderer.Config) {
396 c.Options[optFootnoteIDPrefixFunction] = o.value
397}
398
399func (o *withFootnoteIDPrefixFunction) SetFootnoteOption(c *FootnoteConfig) {
400 c.IDPrefixFunction = o.value
401}
402
403// WithFootnoteIDPrefixFunction is a functional option that is a prefix for the id attributes generated by footnotes.
404func WithFootnoteIDPrefixFunction(a func(gast.Node) []byte) FootnoteOption {
405 return &withFootnoteIDPrefixFunction{a}
406}
407
408const optFootnoteLinkTitle renderer.OptionName = "FootnoteLinkTitle"
409
410type withFootnoteLinkTitle struct {
411 value []byte
412}
413
414func (o *withFootnoteLinkTitle) SetConfig(c *renderer.Config) {
415 c.Options[optFootnoteLinkTitle] = o.value
416}
417
418func (o *withFootnoteLinkTitle) SetFootnoteOption(c *FootnoteConfig) {
419 c.LinkTitle = o.value
420}
421
422// WithFootnoteLinkTitle is a functional option that is an optional title attribute for footnote links.
423func WithFootnoteLinkTitle[T []byte | string](a T) FootnoteOption {
424 return &withFootnoteLinkTitle{[]byte(a)}
425}
426
427const optFootnoteBacklinkTitle renderer.OptionName = "FootnoteBacklinkTitle"
428
429type withFootnoteBacklinkTitle struct {
430 value []byte
431}
432
433func (o *withFootnoteBacklinkTitle) SetConfig(c *renderer.Config) {
434 c.Options[optFootnoteBacklinkTitle] = o.value
435}
436
437func (o *withFootnoteBacklinkTitle) SetFootnoteOption(c *FootnoteConfig) {
438 c.BacklinkTitle = o.value
439}
440
441// WithFootnoteBacklinkTitle is a functional option that is an optional title attribute for footnote backlinks.
442func WithFootnoteBacklinkTitle[T []byte | string](a T) FootnoteOption {
443 return &withFootnoteBacklinkTitle{[]byte(a)}
444}
445
446const optFootnoteLinkClass renderer.OptionName = "FootnoteLinkClass"
447
448type withFootnoteLinkClass struct {
449 value []byte
450}
451
452func (o *withFootnoteLinkClass) SetConfig(c *renderer.Config) {
453 c.Options[optFootnoteLinkClass] = o.value
454}
455
456func (o *withFootnoteLinkClass) SetFootnoteOption(c *FootnoteConfig) {
457 c.LinkClass = o.value
458}
459
460// WithFootnoteLinkClass is a functional option that is a class for footnote links.
461func WithFootnoteLinkClass[T []byte | string](a T) FootnoteOption {
462 return &withFootnoteLinkClass{[]byte(a)}
463}
464
465const optFootnoteBacklinkClass renderer.OptionName = "FootnoteBacklinkClass"
466
467type withFootnoteBacklinkClass struct {
468 value []byte
469}
470
471func (o *withFootnoteBacklinkClass) SetConfig(c *renderer.Config) {
472 c.Options[optFootnoteBacklinkClass] = o.value
473}
474
475func (o *withFootnoteBacklinkClass) SetFootnoteOption(c *FootnoteConfig) {
476 c.BacklinkClass = o.value
477}
478
479// WithFootnoteBacklinkClass is a functional option that is a class for footnote backlinks.
480func WithFootnoteBacklinkClass[T []byte | string](a T) FootnoteOption {
481 return &withFootnoteBacklinkClass{[]byte(a)}
482}
483
484const optFootnoteBacklinkHTML renderer.OptionName = "FootnoteBacklinkHTML"
485
486type withFootnoteBacklinkHTML struct {
487 value []byte
488}
489
490func (o *withFootnoteBacklinkHTML) SetConfig(c *renderer.Config) {
491 c.Options[optFootnoteBacklinkHTML] = o.value
492}
493
494func (o *withFootnoteBacklinkHTML) SetFootnoteOption(c *FootnoteConfig) {
495 c.BacklinkHTML = o.value
496}
497
498// WithFootnoteBacklinkHTML is an HTML content for footnote backlinks.
499func WithFootnoteBacklinkHTML[T []byte | string](a T) FootnoteOption {
500 return &withFootnoteBacklinkHTML{[]byte(a)}
501}
502
503// FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that
504// renders FootnoteLink nodes.
505type FootnoteHTMLRenderer struct {
506 FootnoteConfig
507}
508
509// NewFootnoteHTMLRenderer returns a new FootnoteHTMLRenderer.
510func NewFootnoteHTMLRenderer(opts ...FootnoteOption) renderer.NodeRenderer {
511 r := &FootnoteHTMLRenderer{
512 FootnoteConfig: NewFootnoteConfig(),
513 }
514 for _, opt := range opts {
515 opt.SetFootnoteOption(&r.FootnoteConfig)
516 }
517 return r
518}
519
520// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
521func (r *FootnoteHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
522 reg.Register(ast.KindFootnoteLink, r.renderFootnoteLink)
523 reg.Register(ast.KindFootnoteBacklink, r.renderFootnoteBacklink)
524 reg.Register(ast.KindFootnote, r.renderFootnote)
525 reg.Register(ast.KindFootnoteList, r.renderFootnoteList)
526}
527
528func (r *FootnoteHTMLRenderer) renderFootnoteLink(
529 w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
530 if entering {
531 n := node.(*ast.FootnoteLink)
532 is := strconv.Itoa(n.Index)
533 _, _ = w.WriteString(`<sup id="`)
534 _, _ = w.Write(r.idPrefix(node))
535 _, _ = w.WriteString(`fnref`)
536 if n.RefIndex > 0 {
537 _, _ = w.WriteString(fmt.Sprintf("%v", n.RefIndex))
538 }
539 _ = w.WriteByte(':')
540 _, _ = w.WriteString(is)
541 _, _ = w.WriteString(`"><a href="#`)
542 _, _ = w.Write(r.idPrefix(node))
543 _, _ = w.WriteString(`fn:`)
544 _, _ = w.WriteString(is)
545 _, _ = w.WriteString(`" class="`)
546 _, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.LinkClass,
547 n.Index, n.RefCount))
548 if len(r.FootnoteConfig.LinkTitle) > 0 {
549 _, _ = w.WriteString(`" title="`)
550 _, _ = w.Write(util.EscapeHTML(applyFootnoteTemplate(r.FootnoteConfig.LinkTitle, n.Index, n.RefCount)))
551 }
552 _, _ = w.WriteString(`" role="doc-noteref">`)
553
554 _, _ = w.WriteString(is)
555 _, _ = w.WriteString(`</a></sup>`)
556 }
557 return gast.WalkContinue, nil
558}
559
560func (r *FootnoteHTMLRenderer) renderFootnoteBacklink(
561 w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
562 if entering {
563 n := node.(*ast.FootnoteBacklink)
564 is := strconv.Itoa(n.Index)
565 _, _ = w.WriteString(` <a href="#`)
566 _, _ = w.Write(r.idPrefix(node))
567 _, _ = w.WriteString(`fnref`)
568 if n.RefIndex > 0 {
569 _, _ = w.WriteString(fmt.Sprintf("%v", n.RefIndex))
570 }
571 _ = w.WriteByte(':')
572 _, _ = w.WriteString(is)
573 _, _ = w.WriteString(`" class="`)
574 _, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.BacklinkClass, n.Index, n.RefCount))
575 if len(r.FootnoteConfig.BacklinkTitle) > 0 {
576 _, _ = w.WriteString(`" title="`)
577 _, _ = w.Write(util.EscapeHTML(applyFootnoteTemplate(r.FootnoteConfig.BacklinkTitle, n.Index, n.RefCount)))
578 }
579 _, _ = w.WriteString(`" role="doc-backlink">`)
580 _, _ = w.Write(applyFootnoteTemplate(r.FootnoteConfig.BacklinkHTML, n.Index, n.RefCount))
581 _, _ = w.WriteString(`</a>`)
582 }
583 return gast.WalkContinue, nil
584}
585
586func (r *FootnoteHTMLRenderer) renderFootnote(
587 w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
588 n := node.(*ast.Footnote)
589 is := strconv.Itoa(n.Index)
590 if entering {
591 _, _ = w.WriteString(`<li id="`)
592 _, _ = w.Write(r.idPrefix(node))
593 _, _ = w.WriteString(`fn:`)
594 _, _ = w.WriteString(is)
595 _, _ = w.WriteString(`"`)
596 if node.Attributes() != nil {
597 html.RenderAttributes(w, node, html.ListItemAttributeFilter)
598 }
599 _, _ = w.WriteString(">\n")
600 } else {
601 _, _ = w.WriteString("</li>\n")
602 }
603 return gast.WalkContinue, nil
604}
605
606func (r *FootnoteHTMLRenderer) renderFootnoteList(
607 w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
608 if entering {
609 _, _ = w.WriteString(`<div class="footnotes" role="doc-endnotes"`)
610 if node.Attributes() != nil {
611 html.RenderAttributes(w, node, html.GlobalAttributeFilter)
612 }
613 _ = w.WriteByte('>')
614 if r.Config.XHTML {
615 _, _ = w.WriteString("\n<hr />\n")
616 } else {
617 _, _ = w.WriteString("\n<hr>\n")
618 }
619 _, _ = w.WriteString("<ol>\n")
620 } else {
621 _, _ = w.WriteString("</ol>\n")
622 _, _ = w.WriteString("</div>\n")
623 }
624 return gast.WalkContinue, nil
625}
626
627func (r *FootnoteHTMLRenderer) idPrefix(node gast.Node) []byte {
628 if r.FootnoteConfig.IDPrefix != nil {
629 return r.FootnoteConfig.IDPrefix
630 }
631 if r.FootnoteConfig.IDPrefixFunction != nil {
632 return r.FootnoteConfig.IDPrefixFunction(node)
633 }
634 return []byte("")
635}
636
637func applyFootnoteTemplate(b []byte, index, refCount int) []byte {
638 fast := true
639 for i, c := range b {
640 if i != 0 {
641 if b[i-1] == '^' && c == '^' {
642 fast = false
643 break
644 }
645 if b[i-1] == '%' && c == '%' {
646 fast = false
647 break
648 }
649 }
650 }
651 if fast {
652 return b
653 }
654 is := []byte(strconv.Itoa(index))
655 rs := []byte(strconv.Itoa(refCount))
656 ret := bytes.Replace(b, []byte("^^"), is, -1)
657 return bytes.Replace(ret, []byte("%%"), rs, -1)
658}
659
660type footnote struct {
661 options []FootnoteOption
662}
663
664// Footnote is an extension that allow you to use PHP Markdown Extra Footnotes.
665var Footnote = &footnote{
666 options: []FootnoteOption{},
667}
668
669// NewFootnote returns a new extension with given options.
670func NewFootnote(opts ...FootnoteOption) goldmark.Extender {
671 return &footnote{
672 options: opts,
673 }
674}
675
676func (e *footnote) Extend(m goldmark.Markdown) {
677 m.Parser().AddOptions(
678 parser.WithBlockParsers(
679 util.Prioritized(NewFootnoteBlockParser(), 999),
680 ),
681 parser.WithInlineParsers(
682 util.Prioritized(NewFootnoteParser(), 101),
683 ),
684 parser.WithASTTransformers(
685 util.Prioritized(NewFootnoteASTTransformer(), 999),
686 ),
687 )
688 m.Renderer().AddOptions(renderer.WithNodeRenderers(
689 util.Prioritized(NewFootnoteHTMLRenderer(e.options...), 500),
690 ))
691}