Skip to content

Commit

Permalink
Closes #161
Browse files Browse the repository at this point in the history
- Implement footnote configurations defined in original markdown extra.
- Add OwnerDocument() method to ast.Node
- Add Meta() method to *ast.Document
  • Loading branch information
yuin committed Dec 13, 2020
1 parent da9729d commit 9e0189d
Show file tree
Hide file tree
Showing 8 changed files with 632 additions and 46 deletions.
8 changes: 4 additions & 4 deletions .github/ISSUE_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
goldmark has [https://github.com/yuin/goldmark/discussions](Discussions) in github.
You should post only issues here. Feature requests and questions should be posted at discussions.


- [ ] goldmark is fully compliant with the CommonMark. Before submitting issue, you **must** read [CommonMark spec](https://spec.commonmark.org/0.29/) and confirm your output is different from [CommonMark online demo](https://spec.commonmark.org/dingus/).
- [ ] **Extensions(Autolink without `<` `>`, Table, etc) are not part of CommonMark spec.** You should confirm your output is different from other official renderers correspond with an extension.
- [ ] **goldmark is not dedicated for Hugo**. If you are Hugo user and your issue was raised by your experience in Hugo, **you should consider create issue at Hugo repository at first** .
- [ ] Before you make a feature request, **you should consider implement the new feature as an extension by yourself** . To keep goldmark itself simple, most new features should be implemented as an extension.

Please answer the following before submitting your issue:

Expand All @@ -12,6 +15,3 @@ Please answer the following before submitting your issue:
5. What did you expect to see? :
6. What did you see instead? :
7. Did you confirm your output is different from [CommonMark online demo](https://spec.commonmark.org/dingus/) or other official renderer correspond with an extension?:
8. (Feature request only): Why you can not implement it as an extension?:
- You should avoid saying like "I'm not familiar with this project" "I'm not a Go programmer" as far as possible. This is an open source project and a library for Go programmers. I encourage you to strive to read source codes.
- I absolutely welcome questions that are difficult even if you read the source codes.
83 changes: 83 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,89 @@ markdown := goldmark.New(
)
```

### 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 `&#x21a9;&#xfe0e;`. |

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.
Expand Down
21 changes: 21 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,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
Expand Down Expand Up @@ -358,6 +363,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
Expand Down
21 changes: 21 additions & 0 deletions ast/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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,
}
}

Expand Down
35 changes: 21 additions & 14 deletions extension/ast/footnote.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,23 @@ package ast

import (
"fmt"

gast "github.com/yuin/goldmark/ast"
)

// A FootnoteLink struct represents a link to a footnote of Markdown
// (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)
}

Expand All @@ -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,
}
}

Expand Down
Loading

0 comments on commit 9e0189d

Please sign in to comment.