enable: false
superscript:
enable: false
- footnote: true
+ footnote:
+ enable: true
+ enableAutoIDPrefix: false
linkify: true
linkifyProtocol: https
passthrough:
return converter.NopConverter, fmt.Errorf("no content renderer found for markup %q, page: %s", markup, ps.getPageInfoForError())
}
- var id string
var filename string
var path string
if p.f != nil {
- id = p.f.UniqueID()
filename = p.f.Filename()
path = p.f.Path()
} else {
converter.DocumentContext{
Document: doc,
DocumentLookup: documentLookup,
- DocumentID: id,
+ DocumentID: hashing.XxHashFromStringHexEncoded(p.Path()),
DocumentName: path,
Filename: filename,
},
extensions = append(extensions, extension.DefinitionList)
}
- if cfg.Extensions.Footnote {
- extensions = append(extensions, extension.Footnote)
+ if cfg.Extensions.Footnote.Enable {
+ if cfg.Extensions.Footnote.EnableAutoIDPrefix {
+ extensions = append(extensions, extension.NewFootnote(extension.WithFootnoteIDPrefixFunction(func(n ast.Node) []byte {
+ documentID := n.OwnerDocument().Meta()["documentID"].(string)
+ return []byte("h" + documentID)
+ })))
+ } else {
+ extensions = append(extensions, extension.Footnote)
+ }
}
if cfg.Extensions.CJK.Enable {
reader,
parser.WithContext(pctx),
)
+ doc.OwnerDocument().AddMeta("documentID", c.ctx.DocumentID)
return parserResult{
doc: doc,
RightAngleQuote: "»",
Apostrophe: "’",
},
- Footnote: true,
+ Footnote: Footnote{
+ Enable: true,
+ EnableAutoIDPrefix: false,
+ },
DefinitionList: true,
Table: true,
Strikethrough: true,
type Extensions struct {
Typographer Typographer
- Footnote bool
+ Footnote Footnote
DefinitionList bool
Extras Extras
Passthrough Passthrough
CJK CJK
}
+// Footnote holds footnote configuration.
+type Footnote struct {
+ Enable bool
+ EnableAutoIDPrefix bool
+}
+
// Typographer holds typographer configuration.
type Typographer struct {
// Whether to disable typographer.
---
# HTML comments
-## Simple
+## Simple
<!-- This is a comment -->
<!-- This is a comment indented -->
<img border="0" src="pic_trulli.jpg" alt="Trulli">
-->
-## XSS
+## XSS
<!-- --><script>alert("I just escaped the HTML comment")</script><!-- -->
This is a <!-- hidden --> word.
-This is a <!--
+This is a <!--
hidden --> word.
-This is a <!--
+This is a <!--
hidden
--> word.
)
b.AssertLogContains("! WARN")
}
+
+func TestFootnoteExtension(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+disableKinds = ['home','rss','section','sitemap','taxonomy','term']
+[markup.goldmark.extensions.footnote]
+enable = false
+enableAutoIDPrefix = false
+-- layouts/all.html --
+{{ .Content }}
+-- content/p1.md --
+---
+title: p1
+---
+foo[^1] and bar[^2]
+
+[^1]: footnote one
+[^2]: footnote two
+-- content/p2.md --
+---
+title: p2
+---
+foo[^1] and bar[^2]
+
+[^1]: footnote one
+[^2]: footnote two
+-- content/_content.gotmpl --
+{{ range slice 3 4 }}
+ {{ $page := dict
+ "content" (dict "mediaType" "text/markdown" "value" "foo[^1] and bar[^2]\n\n[^1]: footnote one\n[^2]: footnote two")
+ "path" (printf "p%d" .)
+ "title" (printf "p%d" .)
+ }}
+ {{ $.AddPage $page }}
+{{ end }}
+`
+
+ want := "<p>foo[^1] and bar[^2]</p>\n<p>[^1]: footnote one\n[^2]: footnote two</p>"
+ b := hugolib.Test(t, files)
+ b.AssertFileContent("public/p1/index.html", want)
+ b.AssertFileContent("public/p2/index.html", want)
+ b.AssertFileContent("public/p3/index.html", want)
+ b.AssertFileContent("public/p4/index.html", want)
+
+ files = strings.ReplaceAll(files, "enable = false", "enable = true")
+ want = "<p>foo<sup id=\"fnref:1\"><a href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\">1</a></sup> and bar<sup id=\"fnref:2\"><a href=\"#fn:2\" class=\"footnote-ref\" role=\"doc-noteref\">2</a></sup></p>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<p>footnote one <a href=\"#fnref:1\" class=\"footnote-backref\" role=\"doc-backlink\">↩︎</a></p>\n</li>\n<li id=\"fn:2\">\n<p>footnote two <a href=\"#fnref:2\" class=\"footnote-backref\" role=\"doc-backlink\">↩︎</a></p>\n</li>\n</ol>\n</div>"
+ b = hugolib.Test(t, files)
+ b.AssertFileContent("public/p1/index.html", want)
+ b.AssertFileContent("public/p2/index.html", want)
+ b.AssertFileContent("public/p3/index.html", want)
+ b.AssertFileContent("public/p4/index.html", want)
+
+ files = strings.ReplaceAll(files, "enableAutoIDPrefix = false", "enableAutoIDPrefix = true")
+ b = hugolib.Test(t, files)
+ b.AssertFileContent("public/p1/index.html",
+ "<p>foo<sup id=\"hb5cdcabc9e678612fnref:1\"><a href=\"#hb5cdcabc9e678612fn:1\" class=\"footnote-ref\" role=\"doc-noteref\">1</a></sup> and bar<sup id=\"hb5cdcabc9e678612fnref:2\"><a href=\"#hb5cdcabc9e678612fn:2\" class=\"footnote-ref\" role=\"doc-noteref\">2</a></sup></p>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n<hr>\n<ol>\n<li id=\"hb5cdcabc9e678612fn:1\">\n<p>footnote one <a href=\"#hb5cdcabc9e678612fnref:1\" class=\"footnote-backref\" role=\"doc-backlink\">↩︎</a></p>\n</li>\n<li id=\"hb5cdcabc9e678612fn:2\">\n<p>footnote two <a href=\"#hb5cdcabc9e678612fnref:2\" class=\"footnote-backref\" role=\"doc-backlink\">↩︎</a></p>\n</li>\n</ol>\n</div>",
+ )
+ b.AssertFileContent("public/p2/index.html",
+ "<p>foo<sup id=\"h58e8265a0c07b195fnref:1\"><a href=\"#h58e8265a0c07b195fn:1\" class=\"footnote-ref\" role=\"doc-noteref\">1</a></sup> and bar<sup id=\"h58e8265a0c07b195fnref:2\"><a href=\"#h58e8265a0c07b195fn:2\" class=\"footnote-ref\" role=\"doc-noteref\">2</a></sup></p>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n<hr>\n<ol>\n<li id=\"h58e8265a0c07b195fn:1\">\n<p>footnote one <a href=\"#h58e8265a0c07b195fnref:1\" class=\"footnote-backref\" role=\"doc-backlink\">↩︎</a></p>\n</li>\n<li id=\"h58e8265a0c07b195fn:2\">\n<p>footnote two <a href=\"#h58e8265a0c07b195fnref:2\" class=\"footnote-backref\" role=\"doc-backlink\">↩︎</a></p>\n</li>\n</ol>\n</div>",
+ )
+ b.AssertFileContent("public/p3/index.html",
+ "<p>foo<sup id=\"h0aab769290d7e233fnref:1\"><a href=\"#h0aab769290d7e233fn:1\" class=\"footnote-ref\" role=\"doc-noteref\">1</a></sup> and bar<sup id=\"h0aab769290d7e233fnref:2\"><a href=\"#h0aab769290d7e233fn:2\" class=\"footnote-ref\" role=\"doc-noteref\">2</a></sup></p>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n<hr>\n<ol>\n<li id=\"h0aab769290d7e233fn:1\">\n<p>footnote one <a href=\"#h0aab769290d7e233fnref:1\" class=\"footnote-backref\" role=\"doc-backlink\">↩︎</a></p>\n</li>\n<li id=\"h0aab769290d7e233fn:2\">\n<p>footnote two <a href=\"#h0aab769290d7e233fnref:2\" class=\"footnote-backref\" role=\"doc-backlink\">↩︎</a></p>\n</li>\n</ol>\n</div>",
+ )
+ b.AssertFileContent("public/p4/index.html",
+ "<p>foo<sup id=\"ha35b794ad6e8626cfnref:1\"><a href=\"#ha35b794ad6e8626cfn:1\" class=\"footnote-ref\" role=\"doc-noteref\">1</a></sup> and bar<sup id=\"ha35b794ad6e8626cfnref:2\"><a href=\"#ha35b794ad6e8626cfn:2\" class=\"footnote-ref\" role=\"doc-noteref\">2</a></sup></p>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n<hr>\n<ol>\n<li id=\"ha35b794ad6e8626cfn:1\">\n<p>footnote one <a href=\"#ha35b794ad6e8626cfnref:1\" class=\"footnote-backref\" role=\"doc-backlink\">↩︎</a></p>\n</li>\n<li id=\"ha35b794ad6e8626cfn:2\">\n<p>footnote two <a href=\"#ha35b794ad6e8626cfnref:2\" class=\"footnote-backref\" role=\"doc-backlink\">↩︎</a></p>\n</li>\n</ol>\n</div>",
+ )
+}
}
}
- // Changed from a bool in 0.112.0.
+ // Handle changes to the Goldmark configuration.
v, err = maps.GetNestedParam("goldmark.extensions", ".", m)
if err == nil {
vm := maps.ToStringMap(v)
- const typographerKey = "typographer"
- if vv, found := vm[typographerKey]; found {
- if vvb, ok := vv.(bool); ok {
- if !vvb {
- vm[typographerKey] = goldmark_config.Typographer{
- Disable: true,
- }
- } else {
- delete(vm, typographerKey)
- }
+
+ // We changed the typographer extension config from a bool to a struct in 0.112.0.
+ migrateGoldmarkConfig(vm, "typographer", goldmark_config.Typographer{Disable: true})
+
+ // We changed the footnote extension config from a bool to a struct in 0.151.0.
+ migrateGoldmarkConfig(vm, "footnote", goldmark_config.Footnote{Enable: false})
+ }
+}
+
+func migrateGoldmarkConfig(vm map[string]any, key string, falseVal any) {
+ if vv, found := vm[key]; found {
+ if vvb, ok := vv.(bool); ok {
+ if !vvb {
+ vm[key] = falseVal
+ } else {
+ delete(vm, key)
}
}
}
c.Assert(conf.AsciidocExt.Extensions[0], qt.Equals, "asciidoctor-html5s")
})
- c.Run("Decode legacy typographer", func(c *qt.C) {
+ // We changed the typographer extension config from a bool to a struct in 0.112.0.
+ // We changed the footnote extension config from a bool to a struct in 0.151.0.
+ c.Run("Decode legacy Goldmark configs", func(c *qt.C) {
c.Parallel()
v := config.New()
- // typographer was changed from a bool to a struct in 0.112.0.
v.Set("markup", map[string]any{
"goldmark": map[string]any{
"extensions": map[string]any{
+ "footnote": false,
"typographer": false,
},
},
conf, err := Decode(v)
c.Assert(err, qt.IsNil)
+ c.Assert(conf.Goldmark.Extensions.Footnote.Enable, qt.Equals, false)
c.Assert(conf.Goldmark.Extensions.Typographer.Disable, qt.Equals, true)
v.Set("markup", map[string]any{
"goldmark": map[string]any{
"extensions": map[string]any{
+ "footnote": true,
"typographer": true,
},
},
conf, err = Decode(v)
c.Assert(err, qt.IsNil)
+ c.Assert(conf.Goldmark.Extensions.Footnote.Enable, qt.Equals, true)
c.Assert(conf.Goldmark.Extensions.Typographer.Disable, qt.Equals, false)
c.Assert(conf.Goldmark.Extensions.Typographer.Ellipsis, qt.Equals, "…")
})