diff --git a/go.mod b/go.mod index 08dfe1ae5ce..05a5083f22f 100644 --- a/go.mod +++ b/go.mod @@ -99,7 +99,7 @@ require ( github.com/urfave/cli v1.20.0 github.com/xanzy/go-gitlab v0.37.0 github.com/yohcop/openid-go v1.0.0 - github.com/yuin/goldmark v1.2.1 + github.com/yuin/goldmark v1.3.3 github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 go.jolheiser.com/hcaptcha v0.0.4 diff --git a/go.sum b/go.sum index e674b9e231c..be02d44c871 100644 --- a/go.sum +++ b/go.sum @@ -887,6 +887,8 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.3 h1:37BdQwPx8VOSic8eDSWee6QL9mRpZRm9VJp/QugNrW0= +github.com/yuin/goldmark v1.3.3/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 h1:VWSxtAiQNh3zgHJpdpkpVYjTPqRE3P6UZCOPa1nRDio= github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691/go.mod h1:YLF3kDffRfUH/bTxOxHhV6lxwIB3Vfj91rEwNMS9MXo= github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 h1:gZucqLjL1eDzVWrXj4uiWeMbAopJlBR2mKQAsTGdPwo= diff --git a/vendor/github.com/yuin/goldmark/README.md b/vendor/github.com/yuin/goldmark/README.md index 8cf7c5a0ebb..f5a998d2b59 100644 --- a/vendor/github.com/yuin/goldmark/README.md +++ b/vendor/github.com/yuin/goldmark/README.md @@ -1,7 +1,7 @@ goldmark ========================================== -[![http://godoc.org/github.com/yuin/goldmark](https://godoc.org/github.com/yuin/goldmark?status.svg)](http://godoc.org/github.com/yuin/goldmark) +[![https://pkg.go.dev/github.com/yuin/goldmark](https://pkg.go.dev/badge/github.com/yuin/goldmark.svg)](https://pkg.go.dev/github.com/yuin/goldmark) [![https://github.com/yuin/goldmark/actions?query=workflow:test](https://github.com/yuin/goldmark/workflows/test/badge.svg?branch=master&event=push)](https://github.com/yuin/goldmark/actions?query=workflow:test) [![https://coveralls.io/github/yuin/goldmark](https://coveralls.io/repos/github/yuin/goldmark/badge.svg?branch=master)](https://coveralls.io/github/yuin/goldmark) [![https://goreportcard.com/report/github.com/yuin/goldmark](https://goreportcard.com/badge/github.com/yuin/goldmark)](https://goreportcard.com/report/github.com/yuin/goldmark) @@ -173,6 +173,7 @@ Parser and Renderer options - This extension enables Table, Strikethrough, Linkify and TaskList. - This extension does not filter tags defined in [6.11: Disallowed Raw HTML (extension)](https://github.github.com/gfm/#disallowed-raw-html-extension-). If you need to filter HTML tags, see [Security](#security). + - If you need to parse github emojis, you can use [goldmark-emoji](https://github.com/yuin/goldmark-emoji) extension. - `extension.DefinitionList` - [PHP Markdown Extra: Definition lists](https://michelf.ca/projects/php-markdown/extra/#def-list) - `extension.Footnote` @@ -279,13 +280,96 @@ markdown := goldmark.New( []byte("https:"), }), extension.WithLinkifyURLRegexp( - xurls.Strict(), + xurls.Strict, ), ), ), ) ``` +### Footnotes extension + +The Footnote extension implements [PHP Markdown Extra: Footnotes](https://michelf.ca/projects/php-markdown/extra/#footnotes). + +This extension has some options: + +| Functional option | Type | Description | +| ----------------- | ---- | ----------- | +| `extension.WithFootnoteIDPrefix` | `[]byte` | a prefix for the id attributes.| +| `extension.WithFootnoteIDPrefixFunction` | `func(gast.Node) []byte` | a function that determines the id attribute for given Node.| +| `extension.WithFootnoteLinkTitle` | `[]byte` | an optional title attribute for footnote links.| +| `extension.WithFootnoteBacklinkTitle` | `[]byte` | an optional title attribute for footnote backlinks. | +| `extension.WithFootnoteLinkClass` | `[]byte` | a class for footnote links. This defaults to `footnote-ref`. | +| `extension.WithFootnoteBacklinkClass` | `[]byte` | a class for footnote backlinks. This defaults to `footnote-backref`. | +| `extension.WithFootnoteBacklinkHTML` | `[]byte` | a class for footnote backlinks. This defaults to `↩︎`. | + +Some options can have special substitutions. Occurances of “^^” in the string will be replaced by the corresponding footnote number in the HTML output. Occurances of “%%” will be replaced by a number for the reference (footnotes can have multiple references). + +`extension.WithFootnoteIDPrefix` and `extension.WithFootnoteIDPrefixFunction` are useful if you have multiple Markdown documents displayed inside one HTML document to avoid footnote ids to clash each other. + +`extension.WithFootnoteIDPrefix` sets fixed id prefix, so you may write codes like the following: + +```go +for _, path := range files { + source := readAll(path) + prefix := getPrefix(path) + + markdown := goldmark.New( + goldmark.WithExtensions( + NewFootnote( + WithFootnoteIDPrefix([]byte(path)), + ), + ), + ) + var b bytes.Buffer + err := markdown.Convert(source, &b) + if err != nil { + t.Error(err.Error()) + } +} +``` + +`extension.WithFootnoteIDPrefixFunction` determines an id prefix by calling given function, so you may write codes like the following: + +```go +markdown := goldmark.New( + goldmark.WithExtensions( + NewFootnote( + WithFootnoteIDPrefixFunction(func(n gast.Node) []byte { + v, ok := n.OwnerDocument().Meta()["footnote-prefix"] + if ok { + return util.StringToReadOnlyBytes(v.(string)) + } + return nil + }), + ), + ), +) + +for _, path := range files { + source := readAll(path) + var b bytes.Buffer + + doc := markdown.Parser().Parse(text.NewReader(source)) + doc.Meta()["footnote-prefix"] = getPrefix(path) + err := markdown.Renderer().Render(&b, source, doc) +} +``` + +You can use [goldmark-meta](https://github.com/yuin/goldmark-meta) to define a id prefix in the markdown document: + + +```markdown +--- +title: document title +slug: article1 +footnote-prefix: article1 +--- + +# My article + +``` + Security -------------------- By default, goldmark does not render raw HTML or potentially-dangerous URLs. @@ -336,6 +420,8 @@ Extensions extension for the goldmark Markdown parser. - [goldmark-highlighting](https://github.com/yuin/goldmark-highlighting): A syntax-highlighting extension for the goldmark markdown parser. +- [goldmark-emoji](https://github.com/yuin/goldmark-emoji): An emoji + extension for the goldmark Markdown parser. - [goldmark-mathjax](https://github.com/litao91/goldmark-mathjax): Mathjax support for the goldmark markdown parser goldmark internal(for extension developers) diff --git a/vendor/github.com/yuin/goldmark/ast/ast.go b/vendor/github.com/yuin/goldmark/ast/ast.go index 66059e94cc1..3719ebbd8d7 100644 --- a/vendor/github.com/yuin/goldmark/ast/ast.go +++ b/vendor/github.com/yuin/goldmark/ast/ast.go @@ -45,11 +45,6 @@ type Attribute struct { Value interface{} } -var attrNameIDS = []byte("#") -var attrNameID = []byte("id") -var attrNameClassS = []byte(".") -var attrNameClass = []byte("class") - // A Node interface defines basic AST node functionalities. type Node interface { // Type returns a type of this node. @@ -116,6 +111,11 @@ type Node interface { // tail of the children. InsertAfter(self, v1, insertee Node) + // OwnerDocument returns this node's owner document. + // If this node is not a child of the Document node, OwnerDocument + // returns nil. + OwnerDocument() *Document + // Dump dumps an AST tree structure to stdout. // This function completely aimed for debugging. // level is a indent level. Implementer should indent informations with @@ -169,7 +169,7 @@ type Node interface { RemoveAttributes() } -// A BaseNode struct implements the Node interface. +// A BaseNode struct implements the Node interface partialliy. type BaseNode struct { firstChild Node lastChild Node @@ -358,6 +358,22 @@ func (n *BaseNode) InsertBefore(self, v1, insertee Node) { } } +// OwnerDocument implements Node.OwnerDocument +func (n *BaseNode) OwnerDocument() *Document { + d := n.Parent() + for { + p := d.Parent() + if p == nil { + if v, ok := d.(*Document); ok { + return v + } + break + } + d = p + } + return nil +} + // Text implements Node.Text . func (n *BaseNode) Text(source []byte) []byte { var buf bytes.Buffer diff --git a/vendor/github.com/yuin/goldmark/ast/block.go b/vendor/github.com/yuin/goldmark/ast/block.go index f5bca33fe94..fc0b3c2e029 100644 --- a/vendor/github.com/yuin/goldmark/ast/block.go +++ b/vendor/github.com/yuin/goldmark/ast/block.go @@ -7,7 +7,7 @@ import ( textm "github.com/yuin/goldmark/text" ) -// A BaseBlock struct implements the Node interface. +// A BaseBlock struct implements the Node interface partialliy. type BaseBlock struct { BaseNode blankPreviousLines bool @@ -50,6 +50,8 @@ func (b *BaseBlock) SetLines(v *textm.Segments) { // A Document struct is a root node of Markdown text. type Document struct { BaseBlock + + meta map[string]interface{} } // KindDocument is a NodeKind of the Document node. @@ -70,10 +72,29 @@ func (n *Document) Kind() NodeKind { return KindDocument } +// OwnerDocument implements Node.OwnerDocument +func (n *Document) OwnerDocument() *Document { + return n +} + +// Meta returns metadata of this document. +func (n *Document) Meta() map[string]interface{} { + if n.meta == nil { + n.meta = map[string]interface{}{} + } + return n.meta +} + +// SetMeta sets given metadata to this document. +func (n *Document) SetMeta(meta map[string]interface{}) { + n.meta = meta +} + // NewDocument returns a new Document node. func NewDocument() *Document { return &Document{ BaseBlock: BaseBlock{}, + meta: nil, } } diff --git a/vendor/github.com/yuin/goldmark/ast/inline.go b/vendor/github.com/yuin/goldmark/ast/inline.go index 23dcad4bc9a..b221695bd70 100644 --- a/vendor/github.com/yuin/goldmark/ast/inline.go +++ b/vendor/github.com/yuin/goldmark/ast/inline.go @@ -8,7 +8,7 @@ import ( "github.com/yuin/goldmark/util" ) -// A BaseInline struct implements the Node interface. +// A BaseInline struct implements the Node interface partialliy. type BaseInline struct { BaseNode } diff --git a/vendor/github.com/yuin/goldmark/extension/ast/footnote.go b/vendor/github.com/yuin/goldmark/extension/ast/footnote.go index 835f8478b3e..dedbab4f8b0 100644 --- a/vendor/github.com/yuin/goldmark/extension/ast/footnote.go +++ b/vendor/github.com/yuin/goldmark/extension/ast/footnote.go @@ -2,6 +2,7 @@ package ast import ( "fmt" + gast "github.com/yuin/goldmark/ast" ) @@ -9,13 +10,15 @@ import ( // (PHP Markdown Extra) text. type FootnoteLink struct { gast.BaseInline - Index int + Index int + RefCount int } // Dump implements Node.Dump. func (n *FootnoteLink) Dump(source []byte, level int) { m := map[string]string{} m["Index"] = fmt.Sprintf("%v", n.Index) + m["RefCount"] = fmt.Sprintf("%v", n.RefCount) gast.DumpHelper(n, source, level, m, nil) } @@ -30,36 +33,40 @@ func (n *FootnoteLink) Kind() gast.NodeKind { // NewFootnoteLink returns a new FootnoteLink node. func NewFootnoteLink(index int) *FootnoteLink { return &FootnoteLink{ - Index: index, + Index: index, + RefCount: 0, } } -// A FootnoteBackLink struct represents a link to a footnote of Markdown +// A FootnoteBacklink struct represents a link to a footnote of Markdown // (PHP Markdown Extra) text. -type FootnoteBackLink struct { +type FootnoteBacklink struct { gast.BaseInline - Index int + Index int + RefCount int } // Dump implements Node.Dump. -func (n *FootnoteBackLink) Dump(source []byte, level int) { +func (n *FootnoteBacklink) Dump(source []byte, level int) { m := map[string]string{} m["Index"] = fmt.Sprintf("%v", n.Index) + m["RefCount"] = fmt.Sprintf("%v", n.RefCount) gast.DumpHelper(n, source, level, m, nil) } -// KindFootnoteBackLink is a NodeKind of the FootnoteBackLink node. -var KindFootnoteBackLink = gast.NewNodeKind("FootnoteBackLink") +// KindFootnoteBacklink is a NodeKind of the FootnoteBacklink node. +var KindFootnoteBacklink = gast.NewNodeKind("FootnoteBacklink") // Kind implements Node.Kind. -func (n *FootnoteBackLink) Kind() gast.NodeKind { - return KindFootnoteBackLink +func (n *FootnoteBacklink) Kind() gast.NodeKind { + return KindFootnoteBacklink } -// NewFootnoteBackLink returns a new FootnoteBackLink node. -func NewFootnoteBackLink(index int) *FootnoteBackLink { - return &FootnoteBackLink{ - Index: index, +// NewFootnoteBacklink returns a new FootnoteBacklink node. +func NewFootnoteBacklink(index int) *FootnoteBacklink { + return &FootnoteBacklink{ + Index: index, + RefCount: 0, } } diff --git a/vendor/github.com/yuin/goldmark/extension/footnote.go b/vendor/github.com/yuin/goldmark/extension/footnote.go index ede72db8787..62f5ee61c68 100644 --- a/vendor/github.com/yuin/goldmark/extension/footnote.go +++ b/vendor/github.com/yuin/goldmark/extension/footnote.go @@ -2,6 +2,8 @@ package extension import ( "bytes" + "strconv" + "github.com/yuin/goldmark" gast "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/extension/ast" @@ -10,10 +12,10 @@ import ( "github.com/yuin/goldmark/renderer/html" "github.com/yuin/goldmark/text" "github.com/yuin/goldmark/util" - "strconv" ) var footnoteListKey = parser.NewContextKey() +var footnoteLinkListKey = parser.NewContextKey() type footnoteBlockParser struct { } @@ -164,7 +166,20 @@ func (s *footnoteParser) Parse(parent gast.Node, block text.Reader, pc parser.Co return nil } - return ast.NewFootnoteLink(index) + 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 { @@ -180,23 +195,46 @@ func NewFootnoteASTTransformer() parser.ASTTransformer { func (a *footnoteASTTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) { var list *ast.FootnoteList - if tlist := pc.Get(footnoteListKey); tlist != nil { - list = tlist.(*ast.FootnoteList) - } else { + 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 } - pc.Set(footnoteListKey, nil) + + 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 } - index := footnote.(*ast.Footnote).Index + fn := footnote.(*ast.Footnote) + index := fn.Index if index < 0 { list.RemoveChild(list, footnote) } else { - container.AppendChild(container, ast.NewFootnoteBackLink(index)) + backLink := ast.NewFootnoteBacklink(index) + backLink.RefCount = counter[index] + container.AppendChild(container, backLink) } footnote = next } @@ -214,19 +252,250 @@ func (a *footnoteASTTransformer) Transform(node *gast.Document, reader text.Read 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 { - html.Config + FootnoteConfig } // NewFootnoteHTMLRenderer returns a new FootnoteHTMLRenderer. -func NewFootnoteHTMLRenderer(opts ...html.Option) renderer.NodeRenderer { +func NewFootnoteHTMLRenderer(opts ...FootnoteOption) renderer.NodeRenderer { r := &FootnoteHTMLRenderer{ - Config: html.NewConfig(), + FootnoteConfig: NewFootnoteConfig(), } for _, opt := range opts { - opt.SetHTMLOption(&r.Config) + opt.SetFootnoteOption(&r.FootnoteConfig) } return r } @@ -234,7 +503,7 @@ func NewFootnoteHTMLRenderer(opts ...html.Option) renderer.NodeRenderer { // 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.KindFootnoteBacklink, r.renderFootnoteBacklink) reg.Register(ast.KindFootnote, r.renderFootnote) reg.Register(ast.KindFootnoteList, r.renderFootnoteList) } @@ -243,25 +512,45 @@ func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byt if entering { n := node.(*ast.FootnoteLink) is := strconv.Itoa(n.Index) - _, _ = w.WriteString(``) + _, _ = 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(``) } return gast.WalkContinue, nil } -func (r *FootnoteHTMLRenderer) renderFootnoteBackLink(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) { +func (r *FootnoteHTMLRenderer) renderFootnoteBacklink(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) { if entering { - n := node.(*ast.FootnoteBackLink) + n := node.(*ast.FootnoteBacklink) is := strconv.Itoa(n.Index) - _, _ = w.WriteString(` `) - _, _ = w.WriteString("↩︎") + _, _ = 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(``) } return gast.WalkContinue, nil @@ -271,7 +560,9 @@ func (r *FootnoteHTMLRenderer) renderFootnote(w util.BufWriter, source []byte, n n := node.(*ast.Footnote) is := strconv.Itoa(n.Index) if entering { - _, _ = w.WriteString(`
  • \^{}\[\]` + "`" + `]*)?`) +var wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-z]+(?:[/#?][-a-zA-Z0-9@:%_\+.~#!?&/=\(\);,'">\^{}\[\]` + "`" + `]*)?`) -var urlRegexp = regexp.MustCompile(`^(?:http|https|ftp):\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]+(?:(?:/|[#?])[-a-zA-Z0-9@:%_+.~#$!?&//=\(\);,'">\^{}\[\]` + "`" + `]*)?`) +var urlRegexp = regexp.MustCompile(`^(?:http|https|ftp)://[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-z]+(?::\d+)?(?:[/#?][-a-zA-Z0-9@:%_+.~#$!?&/=\(\);,'">\^{}\[\]` + "`" + `]*)?`) // An LinkifyConfig struct is a data structure that holds configuration of the // Linkify extension. @@ -24,10 +24,12 @@ type LinkifyConfig struct { EmailRegexp *regexp.Regexp } -const optLinkifyAllowedProtocols parser.OptionName = "LinkifyAllowedProtocols" -const optLinkifyURLRegexp parser.OptionName = "LinkifyURLRegexp" -const optLinkifyWWWRegexp parser.OptionName = "LinkifyWWWRegexp" -const optLinkifyEmailRegexp parser.OptionName = "LinkifyEmailRegexp" +const ( + optLinkifyAllowedProtocols parser.OptionName = "LinkifyAllowedProtocols" + optLinkifyURLRegexp parser.OptionName = "LinkifyURLRegexp" + optLinkifyWWWRegexp parser.OptionName = "LinkifyWWWRegexp" + optLinkifyEmailRegexp parser.OptionName = "LinkifyEmailRegexp" +) // SetOption implements SetOptioner. func (c *LinkifyConfig) SetOption(name parser.OptionName, value interface{}) { @@ -156,10 +158,12 @@ func (s *linkifyParser) Trigger() []byte { return []byte{' ', '*', '_', '~', '('} } -var protoHTTP = []byte("http:") -var protoHTTPS = []byte("https:") -var protoFTP = []byte("ftp:") -var domainWWW = []byte("www.") +var ( + protoHTTP = []byte("http:") + protoHTTPS = []byte("https:") + protoFTP = []byte("ftp:") + domainWWW = []byte("www.") +) func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node { if pc.IsInLinkLabel() { diff --git a/vendor/github.com/yuin/goldmark/extension/table.go b/vendor/github.com/yuin/goldmark/extension/table.go index f0e994e838b..c637b99f044 100644 --- a/vendor/github.com/yuin/goldmark/extension/table.go +++ b/vendor/github.com/yuin/goldmark/extension/table.go @@ -15,6 +15,14 @@ import ( "github.com/yuin/goldmark/util" ) +var escapedPipeCellListKey = parser.NewContextKey() + +type escapedPipeCell struct { + Cell *ast.TableCell + Pos []int + Transformed bool +} + // TableCellAlignMethod indicates how are table cells aligned in HTML format.indicates how are table cells aligned in HTML format. type TableCellAlignMethod int @@ -148,7 +156,7 @@ func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text. if alignments == nil { continue } - header := b.parseRow(lines.At(i-1), alignments, true, reader) + header := b.parseRow(lines.At(i-1), alignments, true, reader, pc) if header == nil || len(alignments) != header.ChildCount() { return } @@ -156,7 +164,7 @@ func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text. table.Alignments = alignments table.AppendChild(table, ast.NewTableHeader(header)) for j := i + 1; j < lines.Len(); j++ { - table.AppendChild(table, b.parseRow(lines.At(j), alignments, false, reader)) + table.AppendChild(table, b.parseRow(lines.At(j), alignments, false, reader, pc)) } node.Lines().SetSliced(0, i-1) node.Parent().InsertAfter(node.Parent(), node, table) @@ -170,7 +178,7 @@ func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text. } } -func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []ast.Alignment, isHeader bool, reader text.Reader) *ast.TableRow { +func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []ast.Alignment, isHeader bool, reader text.Reader, pc parser.Context) *ast.TableRow { source := reader.Source() line := segment.Value(source) pos := 0 @@ -194,18 +202,39 @@ func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments [] } else { alignment = alignments[i] } - closure := util.FindClosure(line[pos:], byte(0), '|', true, false) - if closure < 0 { - closure = len(line[pos:]) - } + + var escapedCell *escapedPipeCell node := ast.NewTableCell() - seg := text.NewSegment(segment.Start+pos, segment.Start+pos+closure) + node.Alignment = alignment + hasBacktick := false + closure := pos + for ; closure < limit; closure++ { + if line[closure] == '`' { + hasBacktick = true + } + if line[closure] == '|' { + if closure == 0 || line[closure-1] != '\\' { + break + } else if hasBacktick { + if escapedCell == nil { + escapedCell = &escapedPipeCell{node, []int{}, false} + escapedList := pc.ComputeIfAbsent(escapedPipeCellListKey, + func() interface{} { + return []*escapedPipeCell{} + }).([]*escapedPipeCell) + escapedList = append(escapedList, escapedCell) + pc.Set(escapedPipeCellListKey, escapedList) + } + escapedCell.Pos = append(escapedCell.Pos, segment.Start+closure-1) + } + } + } + seg := text.NewSegment(segment.Start+pos, segment.Start+closure) seg = seg.TrimLeftSpace(source) seg = seg.TrimRightSpace(source) node.Lines().Append(seg) - node.Alignment = alignment row.AppendChild(row, node) - pos += closure + 1 + pos = closure + 1 } for ; i < len(alignments); i++ { row.AppendChild(row, ast.NewTableCell()) @@ -243,6 +272,61 @@ func (b *tableParagraphTransformer) parseDelimiter(segment text.Segment, reader return alignments } +type tableASTTransformer struct { +} + +var defaultTableASTTransformer = &tableASTTransformer{} + +// NewTableASTTransformer returns a parser.ASTTransformer for tables. +func NewTableASTTransformer() parser.ASTTransformer { + return defaultTableASTTransformer +} + +func (a *tableASTTransformer) Transform(node *gast.Document, reader text.Reader, pc parser.Context) { + lst := pc.Get(escapedPipeCellListKey) + if lst == nil { + return + } + pc.Set(escapedPipeCellListKey, nil) + for _, v := range lst.([]*escapedPipeCell) { + if v.Transformed { + continue + } + _ = gast.Walk(v.Cell, func(n gast.Node, entering bool) (gast.WalkStatus, error) { + if !entering || n.Kind() != gast.KindCodeSpan { + return gast.WalkContinue, nil + } + + for c := n.FirstChild(); c != nil; { + next := c.NextSibling() + if c.Kind() != gast.KindText { + c = next + continue + } + parent := c.Parent() + ts := &c.(*gast.Text).Segment + n := c + for _, v := range lst.([]*escapedPipeCell) { + for _, pos := range v.Pos { + if ts.Start <= pos && pos < ts.Stop { + segment := n.(*gast.Text).Segment + n1 := gast.NewRawTextSegment(segment.WithStop(pos)) + n2 := gast.NewRawTextSegment(segment.WithStart(pos + 1)) + parent.InsertAfter(parent, n, n1) + parent.InsertAfter(parent, n1, n2) + parent.RemoveChild(parent, n) + n = n2 + v.Transformed = true + } + } + } + c = next + } + return gast.WalkContinue, nil + }) + } +} + // TableHTMLRenderer is a renderer.NodeRenderer implementation that // renders Table nodes. type TableHTMLRenderer struct { @@ -419,7 +503,7 @@ func (r *TableHTMLRenderer) renderTableCell(w util.BufWriter, source []byte, nod cob.AppendByte(';') } style := fmt.Sprintf("text-align:%s", n.Alignment.String()) - cob.Append(util.StringToReadOnlyBytes(style)) + cob.AppendString(style) n.SetAttributeString("style", cob.Bytes()) } } @@ -454,9 +538,14 @@ func NewTable(opts ...TableOption) goldmark.Extender { } func (e *table) Extend(m goldmark.Markdown) { - m.Parser().AddOptions(parser.WithParagraphTransformers( - util.Prioritized(NewTableParagraphTransformer(), 200), - )) + m.Parser().AddOptions( + parser.WithParagraphTransformers( + util.Prioritized(NewTableParagraphTransformer(), 200), + ), + parser.WithASTTransformers( + util.Prioritized(defaultTableASTTransformer, 0), + ), + ) m.Renderer().AddOptions(renderer.WithNodeRenderers( util.Prioritized(NewTableHTMLRenderer(e.options...), 500), )) diff --git a/vendor/github.com/yuin/goldmark/go.mod b/vendor/github.com/yuin/goldmark/go.mod index a10efcad528..f76c1766fcb 100644 --- a/vendor/github.com/yuin/goldmark/go.mod +++ b/vendor/github.com/yuin/goldmark/go.mod @@ -1,3 +1,3 @@ module github.com/yuin/goldmark -go 1.13 +go 1.15 diff --git a/vendor/github.com/yuin/goldmark/parser/code_block.go b/vendor/github.com/yuin/goldmark/parser/code_block.go index d02c21fc713..4b1863929fa 100644 --- a/vendor/github.com/yuin/goldmark/parser/code_block.go +++ b/vendor/github.com/yuin/goldmark/parser/code_block.go @@ -49,6 +49,12 @@ func (b *codeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context } reader.AdvanceAndSetPadding(pos, padding) _, segment = reader.PeekLine() + + // if code block line starts with a tab, keep a tab as it is. + if segment.Padding != 0 { + preserveLeadingTabInCodeBlock(&segment, reader) + } + node.Lines().Append(segment) reader.Advance(segment.Len() - 1) return Continue | NoChildren @@ -77,3 +83,14 @@ func (b *codeBlockParser) CanInterruptParagraph() bool { func (b *codeBlockParser) CanAcceptIndentedLine() bool { return true } + +func preserveLeadingTabInCodeBlock(segment *text.Segment, reader text.Reader) { + offsetWithPadding := reader.LineOffset() + sl, ss := reader.Position() + reader.SetPosition(sl, text.NewSegment(ss.Start-1, ss.Stop)) + if offsetWithPadding == reader.LineOffset() { + segment.Padding = 0 + segment.Start-- + } + reader.SetPosition(sl, ss) +} diff --git a/vendor/github.com/yuin/goldmark/parser/fcode_block.go b/vendor/github.com/yuin/goldmark/parser/fcode_block.go index f5b83eef7b4..4801449194b 100644 --- a/vendor/github.com/yuin/goldmark/parser/fcode_block.go +++ b/vendor/github.com/yuin/goldmark/parser/fcode_block.go @@ -71,6 +71,10 @@ func (b *fencedCodeBlockParser) Open(parent ast.Node, reader text.Reader, pc Con func (b *fencedCodeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context) State { line, segment := reader.PeekLine() fdata := pc.Get(fencedCodeBlockInfoKey).(*fenceData) + // if code block line starts with a tab, keep a tab as it is. + if segment.Padding != 0 { + preserveLeadingTabInCodeBlock(&segment, reader) + } w, pos := util.IndentWidth(line, reader.LineOffset()) if w < 4 { i := pos diff --git a/vendor/github.com/yuin/goldmark/parser/link.go b/vendor/github.com/yuin/goldmark/parser/link.go index e7c6966f3de..c36cce5d900 100644 --- a/vendor/github.com/yuin/goldmark/parser/link.go +++ b/vendor/github.com/yuin/goldmark/parser/link.go @@ -2,7 +2,6 @@ package parser import ( "fmt" - "regexp" "strings" "github.com/yuin/goldmark/ast" @@ -113,8 +112,6 @@ func (s *linkParser) Trigger() []byte { return []byte{'!', '[', ']'} } -var linkDestinationRegexp = regexp.MustCompile(`\s*([^\s].+)`) -var linkTitleRegexp = regexp.MustCompile(`\s+(\)|["'\(].+)`) var linkBottom = NewContextKey() func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.Node { @@ -293,20 +290,17 @@ func (s *linkParser) parseLink(parent ast.Node, last *linkLabelState, block text func parseLinkDestination(block text.Reader) ([]byte, bool) { block.SkipSpaces() line, _ := block.PeekLine() - buf := []byte{} if block.Peek() == '<' { i := 1 for i < len(line) { c := line[i] if c == '\\' && i < len(line)-1 && util.IsPunct(line[i+1]) { - buf = append(buf, '\\', line[i+1]) i += 2 continue } else if c == '>' { block.Advance(i + 1) return line[1:i], true } - buf = append(buf, c) i++ } return nil, false @@ -316,7 +310,6 @@ func parseLinkDestination(block text.Reader) ([]byte, bool) { for i < len(line) { c := line[i] if c == '\\' && i < len(line)-1 && util.IsPunct(line[i+1]) { - buf = append(buf, '\\', line[i+1]) i += 2 continue } else if c == '(' { @@ -329,7 +322,6 @@ func parseLinkDestination(block text.Reader) ([]byte, bool) { } else if util.IsSpace(c) { break } - buf = append(buf, c) i++ } block.Advance(i) diff --git a/vendor/github.com/yuin/goldmark/parser/parser.go b/vendor/github.com/yuin/goldmark/parser/parser.go index def13db6663..e58b5ee936c 100644 --- a/vendor/github.com/yuin/goldmark/parser/parser.go +++ b/vendor/github.com/yuin/goldmark/parser/parser.go @@ -138,6 +138,9 @@ type Context interface { // Get returns a value associated with the given key. Get(ContextKey) interface{} + // ComputeIfAbsent computes a value if a value associated with the given key is absent and returns the value. + ComputeIfAbsent(ContextKey, func() interface{}) interface{} + // Set sets the given value to the context. Set(ContextKey, interface{}) @@ -252,6 +255,15 @@ func (p *parseContext) Get(key ContextKey) interface{} { return p.store[key] } +func (p *parseContext) ComputeIfAbsent(key ContextKey, f func() interface{}) interface{} { + v := p.store[key] + if v == nil { + v = f() + p.store[key] = v + } + return v +} + func (p *parseContext) Set(key ContextKey, value interface{}) { p.store[key] = value } diff --git a/vendor/github.com/yuin/goldmark/parser/raw_html.go b/vendor/github.com/yuin/goldmark/parser/raw_html.go index d7ba414ff21..7fd696cc2ce 100644 --- a/vendor/github.com/yuin/goldmark/parser/raw_html.go +++ b/vendor/github.com/yuin/goldmark/parser/raw_html.go @@ -2,10 +2,11 @@ package parser import ( "bytes" + "regexp" + "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/text" "github.com/yuin/goldmark/util" - "regexp" ) type rawHTMLParser struct { @@ -67,8 +68,6 @@ func (s *rawHTMLParser) parseSingleLineRegexp(reg *regexp.Regexp, block text.Rea return node } -var dummyMatch = [][]byte{} - func (s *rawHTMLParser) parseMultiLineRegexp(reg *regexp.Regexp, block text.Reader, pc Context) ast.Node { sline, ssegment := block.Position() if block.Match(reg) { @@ -102,7 +101,3 @@ func (s *rawHTMLParser) parseMultiLineRegexp(reg *regexp.Regexp, block text.Read } return nil } - -func (s *rawHTMLParser) CloseBlock(parent ast.Node, pc Context) { - // nothing to do -} diff --git a/vendor/github.com/yuin/goldmark/util/util.go b/vendor/github.com/yuin/goldmark/util/util.go index fc1438dc19d..3ec73f54f32 100644 --- a/vendor/github.com/yuin/goldmark/util/util.go +++ b/vendor/github.com/yuin/goldmark/util/util.go @@ -37,6 +37,12 @@ func (b *CopyOnWriteBuffer) Write(value []byte) { b.buffer = append(b.buffer, value...) } +// WriteString writes given string to the buffer. +// WriteString allocate new buffer and clears it at the first time. +func (b *CopyOnWriteBuffer) WriteString(value string) { + b.Write(StringToReadOnlyBytes(value)) +} + // Append appends given bytes to the buffer. // Append copy buffer at the first time. func (b *CopyOnWriteBuffer) Append(value []byte) { @@ -49,6 +55,12 @@ func (b *CopyOnWriteBuffer) Append(value []byte) { b.buffer = append(b.buffer, value...) } +// AppendString appends given string to the buffer. +// AppendString copy buffer at the first time. +func (b *CopyOnWriteBuffer) AppendString(value string) { + b.Append(StringToReadOnlyBytes(value)) +} + // WriteByte writes the given byte to the buffer. // WriteByte allocate new buffer and clears it at the first time. func (b *CopyOnWriteBuffer) WriteByte(c byte) { @@ -804,7 +816,7 @@ func IsPunct(c byte) bool { return punctTable[c] == 1 } -// IsPunct returns true if the given rune is a punctuation, otherwise false. +// IsPunctRune returns true if the given rune is a punctuation, otherwise false. func IsPunctRune(r rune) bool { return int32(r) <= 256 && IsPunct(byte(r)) || unicode.IsPunct(r) } @@ -814,7 +826,7 @@ func IsSpace(c byte) bool { return spaceTable[c] == 1 } -// IsSpace returns true if the given rune is a space, otherwise false. +// IsSpaceRune returns true if the given rune is a space, otherwise false. func IsSpaceRune(r rune) bool { return int32(r) <= 256 && IsSpace(byte(r)) || unicode.IsSpace(r) } diff --git a/vendor/modules.txt b/vendor/modules.txt index 11373e35042..c05df2988bb 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -745,7 +745,7 @@ github.com/xi2/xz # github.com/yohcop/openid-go v1.0.0 ## explicit github.com/yohcop/openid-go -# github.com/yuin/goldmark v1.2.1 +# github.com/yuin/goldmark v1.3.3 ## explicit github.com/yuin/goldmark github.com/yuin/goldmark/ast