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" Trulli --> -## 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 = "

foo1 and bar2

\n
\n
\n
    \n
  1. \n

    footnote one ↩︎

    \n
  2. \n
  3. \n

    footnote two ↩︎

    \n
  4. \n
\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", + "

foo1 and bar2

\n
\n
\n
    \n
  1. \n

    footnote one ↩︎

    \n
  2. \n
  3. \n

    footnote two ↩︎

    \n
  4. \n
\n
", + ) + b.AssertFileContent("public/p2/index.html", + "

foo1 and bar2

\n
\n
\n
    \n
  1. \n

    footnote one ↩︎

    \n
  2. \n
  3. \n

    footnote two ↩︎

    \n
  4. \n
\n
", + ) + b.AssertFileContent("public/p3/index.html", + "

foo1 and bar2

\n
\n
\n
    \n
  1. \n

    footnote one ↩︎

    \n
  2. \n
  3. \n

    footnote two ↩︎

    \n
  4. \n
\n
", + ) + b.AssertFileContent("public/p4/index.html", + "

foo1 and bar2

\n
\n
\n
    \n
  1. \n

    footnote one ↩︎

    \n
  2. \n
  3. \n

    footnote two ↩︎

    \n
  4. \n
\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, "…") })