diff --git a/docs/data/docs.yaml b/docs/data/docs.yaml
index d4ca1449e..e359ff6c9 100644
--- a/docs/data/docs.yaml
+++ b/docs/data/docs.yaml
@@ -1238,7 +1238,9 @@ config:
enable: false
superscript:
enable: false
- footnote: true
+ footnote:
+ enable: true
+ enableAutoIDPrefix: false
linkify: true
linkifyProtocol: https
passthrough:
diff --git a/hugolib/page__meta.go b/hugolib/page__meta.go
index 5a27d1913..f04e0bd56 100644
--- a/hugolib/page__meta.go
+++ b/hugolib/page__meta.go
@@ -794,11 +794,9 @@ func (p *pageMeta) newContentConverter(ps *pageState, markup string) (converter.
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 {
@@ -822,7 +820,7 @@ func (p *pageMeta) newContentConverter(ps *pageState, markup string) (converter.
converter.DocumentContext{
Document: doc,
DocumentLookup: documentLookup,
- DocumentID: id,
+ DocumentID: hashing.XxHashFromStringHexEncoded(p.Path()),
DocumentName: path,
Filename: filename,
},
diff --git a/markup/goldmark/convert.go b/markup/goldmark/convert.go
index fb374e6ae..7b23c8986 100644
--- a/markup/goldmark/convert.go
+++ b/markup/goldmark/convert.go
@@ -176,8 +176,15 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
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 {
@@ -262,6 +269,7 @@ func (c *goldmarkConverter) Parse(ctx converter.RenderContext) (converter.Result
reader,
parser.WithContext(pctx),
)
+ doc.OwnerDocument().AddMeta("documentID", c.ctx.DocumentID)
return parserResult{
doc: doc,
diff --git a/markup/goldmark/goldmark_config/config.go b/markup/goldmark/goldmark_config/config.go
index 168a3b8fa..fe808f5d4 100644
--- a/markup/goldmark/goldmark_config/config.go
+++ b/markup/goldmark/goldmark_config/config.go
@@ -40,7 +40,10 @@ var Default = Config{
RightAngleQuote: "»",
Apostrophe: "’",
},
- Footnote: true,
+ Footnote: Footnote{
+ Enable: true,
+ EnableAutoIDPrefix: false,
+ },
DefinitionList: true,
Table: true,
Strikethrough: true,
@@ -152,7 +155,7 @@ type LinkRenderHook struct {
type Extensions struct {
Typographer Typographer
- Footnote bool
+ Footnote Footnote
DefinitionList bool
Extras Extras
Passthrough Passthrough
@@ -166,6 +169,12 @@ type Extensions struct {
CJK CJK
}
+// Footnote holds footnote configuration.
+type Footnote struct {
+ Enable bool
+ EnableAutoIDPrefix bool
+}
+
// Typographer holds typographer configuration.
type Typographer struct {
// Whether to disable typographer.
diff --git a/markup/goldmark/goldmark_integration_test.go b/markup/goldmark/goldmark_integration_test.go
index a65600951..9a8616829 100644
--- a/markup/goldmark/goldmark_integration_test.go
+++ b/markup/goldmark/goldmark_integration_test.go
@@ -897,7 +897,7 @@ title: "p1"
---
# HTML comments
-## Simple
+## Simple
@@ -918,7 +918,7 @@ title: "p1"
-->
-## XSS
+## XSS
@@ -931,10 +931,10 @@ This is a word.
This is a word.
-This is a word.
-This is a word.
@@ -961,3 +961,72 @@ hidden
)
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 := "
foo[^1] and bar[^2]
\n[^1]: footnote one\n[^2]: footnote two
" + 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 = "\n" + 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", + "\n", + ) + b.AssertFileContent("public/p2/index.html", + "\n", + ) + b.AssertFileContent("public/p3/index.html", + "\n", + ) + b.AssertFileContent("public/p4/index.html", + "\n", + ) +} diff --git a/markup/markup_config/config.go b/markup/markup_config/config.go index e944caae6..50173cd6d 100644 --- a/markup/markup_config/config.go +++ b/markup/markup_config/config.go @@ -86,20 +86,26 @@ func normalizeConfig(m map[string]any) { } } - // 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) } } } diff --git a/markup/markup_config/config_test.go b/markup/markup_config/config_test.go index 5169bd79f..51ed8a49e 100644 --- a/markup/markup_config/config_test.go +++ b/markup/markup_config/config_test.go @@ -52,14 +52,16 @@ func TestConfig(t *testing.T) { 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, }, }, @@ -68,11 +70,13 @@ func TestConfig(t *testing.T) { 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, }, }, @@ -81,6 +85,7 @@ func TestConfig(t *testing.T) { 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, "…") })