diff --git a/config/allconfig/allconfig.go b/config/allconfig/allconfig.go
index 0db0be1d8..3366defd7 100644
--- a/config/allconfig/allconfig.go
+++ b/config/allconfig/allconfig.go
@@ -20,6 +20,7 @@ import (
"fmt"
"reflect"
"regexp"
+ "slices"
"sort"
"strconv"
"strings"
@@ -32,7 +33,6 @@ import (
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
- "github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/common/urls"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/privacy"
@@ -42,6 +42,7 @@ import (
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugolib/segments"
"github.com/gohugoio/hugo/langs"
+ gc "github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
"github.com/gohugoio/hugo/markup/markup_config"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/minifiers"
@@ -399,7 +400,6 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
hugo.DeprecateWithLogger("site config key paginate", "Use pagination.pagerSize instead.", "v0.128.0", logger.Logger())
c.Pagination.PagerSize = c.Paginate
}
-
if c.PaginatePath != "" {
hugo.DeprecateWithLogger("site config key paginatePath", "Use pagination.path instead.", "v0.128.0", logger.Logger())
c.Pagination.Path = c.PaginatePath
@@ -410,12 +410,10 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
hugo.DeprecateWithLogger("site config key privacy.twitter.disable", "Use privacy.x.disable instead.", "v0.141.0", logger.Logger())
c.Privacy.X.Disable = c.Privacy.Twitter.Disable
}
-
if c.Privacy.Twitter.EnableDNT {
hugo.DeprecateWithLogger("site config key privacy.twitter.enableDNT", "Use privacy.x.enableDNT instead.", "v0.141.0", logger.Logger())
c.Privacy.X.EnableDNT = c.Privacy.Twitter.EnableDNT
}
-
if c.Privacy.Twitter.Simple {
hugo.DeprecateWithLogger("site config key privacy.twitter.simple", "Use privacy.x.simple instead.", "v0.141.0", logger.Logger())
c.Privacy.X.Simple = c.Privacy.Twitter.Simple
@@ -436,6 +434,45 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
hugo.DeprecateWithLogger("the \":slugorfilename\" permalink token", "Use \":slugorcontentbasename\" instead.", "0.144.0", logger.Logger())
}
+ // Legacy render hook values.
+ alternativeDetails := fmt.Sprintf(
+ "Set to %q if previous value was false, or set to %q if previous value was true.",
+ gc.RenderHookUseEmbeddedNever,
+ gc.RenderHookUseEmbeddedFallback,
+ )
+ if c.Markup.Goldmark.RenderHooks.Image.EnableDefault != nil {
+ alternative := "Use markup.goldmark.renderHooks.image.useEmbedded instead." + " " + alternativeDetails
+ hugo.DeprecateWithLogger("site config key markup.goldmark.renderHooks.image.enableDefault", alternative, "0.148.0", logger.Logger())
+ if *c.Markup.Goldmark.RenderHooks.Image.EnableDefault {
+ c.Markup.Goldmark.RenderHooks.Image.UseEmbedded = gc.RenderHookUseEmbeddedFallback
+ } else {
+ c.Markup.Goldmark.RenderHooks.Image.UseEmbedded = gc.RenderHookUseEmbeddedNever
+ }
+ }
+ if c.Markup.Goldmark.RenderHooks.Link.EnableDefault != nil {
+ alternative := "Use markup.goldmark.renderHooks.link.useEmbedded instead." + " " + alternativeDetails
+ hugo.DeprecateWithLogger("site config key markup.goldmark.renderHooks.link.enableDefault", alternative, "0.148.0", logger.Logger())
+ if *c.Markup.Goldmark.RenderHooks.Link.EnableDefault {
+ c.Markup.Goldmark.RenderHooks.Link.UseEmbedded = gc.RenderHookUseEmbeddedFallback
+ } else {
+ c.Markup.Goldmark.RenderHooks.Link.UseEmbedded = gc.RenderHookUseEmbeddedNever
+ }
+ }
+
+ // Validate render hook configuration.
+ renderHookUseEmbeddedModes := []string{
+ gc.RenderHookUseEmbeddedAlways,
+ gc.RenderHookUseEmbeddedAuto,
+ gc.RenderHookUseEmbeddedFallback,
+ gc.RenderHookUseEmbeddedNever,
+ }
+ if !slices.Contains(renderHookUseEmbeddedModes, c.Markup.Goldmark.RenderHooks.Image.UseEmbedded) {
+ return fmt.Errorf("site config markup.goldmark.renderHooks.image must be one of %s", helpers.StringSliceToList(renderHookUseEmbeddedModes, "or"))
+ }
+ if !slices.Contains(renderHookUseEmbeddedModes, c.Markup.Goldmark.RenderHooks.Link.UseEmbedded) {
+ return fmt.Errorf("site config markup.goldmark.renderHooks.link must be one of %s", helpers.StringSliceToList(renderHookUseEmbeddedModes, "or"))
+ }
+
c.C = &ConfigCompiled{
Timeout: timeout,
BaseURL: baseURL,
@@ -1082,13 +1119,11 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
// Adjust Goldmark config defaults for multilingual, single-host sites.
if len(languagesConfig) > 1 && !isMultihost && !clone.Markup.Goldmark.DuplicateResourceFiles {
- if !clone.Markup.Goldmark.DuplicateResourceFiles {
- if clone.Markup.Goldmark.RenderHooks.Link.EnableDefault == nil {
- clone.Markup.Goldmark.RenderHooks.Link.EnableDefault = types.NewBool(true)
- }
- if clone.Markup.Goldmark.RenderHooks.Image.EnableDefault == nil {
- clone.Markup.Goldmark.RenderHooks.Image.EnableDefault = types.NewBool(true)
- }
+ if clone.Markup.Goldmark.RenderHooks.Image.UseEmbedded == gc.RenderHookUseEmbeddedAuto {
+ clone.Markup.Goldmark.RenderHooks.Image.UseEmbedded = gc.RenderHookUseEmbeddedFallback
+ }
+ if clone.Markup.Goldmark.RenderHooks.Link.UseEmbedded == gc.RenderHookUseEmbeddedAuto {
+ clone.Markup.Goldmark.RenderHooks.Link.UseEmbedded = gc.RenderHookUseEmbeddedFallback
}
}
diff --git a/config/allconfig/allconfig_integration_test.go b/config/allconfig/allconfig_integration_test.go
index 8f6cacf84..79d2299de 100644
--- a/config/allconfig/allconfig_integration_test.go
+++ b/config/allconfig/allconfig_integration_test.go
@@ -2,12 +2,14 @@ package allconfig_test
import (
"path/filepath"
+ "strings"
"testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/hugolib"
+ gc "github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
"github.com/gohugoio/hugo/media"
)
@@ -379,3 +381,28 @@ weight = 3
b.Assert(len(b.H.Sites), qt.Equals, 1)
}
+
+// Issue 13535
+// We changed enablement of the embedded link and image render hooks from
+// booleans to enums in v0.148.0.
+func TestLegacyEmbeddedRenderHookEnablement(t *testing.T) {
+ files := `
+-- hugo.toml --
+[markup.goldmark.renderHooks.image]
+#KEY_VALUE
+
+[markup.goldmark.renderHooks.link]
+#KEY_VALUE
+`
+ f := strings.ReplaceAll(files, "#KEY_VALUE", "enableDefault = false")
+ b := hugolib.Test(t, f)
+ c := b.H.Configs.Base.Markup.Goldmark.RenderHooks
+ b.Assert(c.Link.UseEmbedded, qt.Equals, gc.RenderHookUseEmbeddedNever)
+ b.Assert(c.Image.UseEmbedded, qt.Equals, gc.RenderHookUseEmbeddedNever)
+
+ f = strings.ReplaceAll(files, "#KEY_VALUE", "enableDefault = true")
+ b = hugolib.Test(t, f)
+ c = b.H.Configs.Base.Markup.Goldmark.RenderHooks
+ b.Assert(c.Link.UseEmbedded, qt.Equals, gc.RenderHookUseEmbeddedFallback)
+ b.Assert(c.Image.UseEmbedded, qt.Equals, gc.RenderHookUseEmbeddedFallback)
+}
diff --git a/helpers/general.go b/helpers/general.go
index 76275a6b9..5c3af9712 100644
--- a/helpers/general.go
+++ b/helpers/general.go
@@ -255,6 +255,27 @@ func SliceToLower(s []string) []string {
return l
}
+// StringSliceToList formats a string slice into a human-readable list.
+// It joins the elements of the slice s with commas, using an Oxford comma,
+// and precedes the final element with the conjunction c.
+func StringSliceToList(s []string, c string) string {
+ const defaultConjunction = "and"
+
+ if c == "" {
+ c = defaultConjunction
+ }
+ if len(s) == 0 {
+ return ""
+ }
+ if len(s) == 1 {
+ return s[0]
+ }
+ if len(s) == 2 {
+ return fmt.Sprintf("%s %s %s", s[0], c, s[1])
+ }
+ return fmt.Sprintf("%s, %s %s", strings.Join(s[:len(s)-1], ", "), c, s[len(s)-1])
+}
+
// IsWhitespace determines if the given rune is whitespace.
func IsWhitespace(r rune) bool {
return r == ' ' || r == '\t' || r == '\n' || r == '\r'
diff --git a/helpers/general_test.go b/helpers/general_test.go
index 686e95ded..6d2a78e02 100644
--- a/helpers/general_test.go
+++ b/helpers/general_test.go
@@ -18,9 +18,8 @@ import (
"strings"
"testing"
- "github.com/gohugoio/hugo/helpers"
-
qt "github.com/frankban/quicktest"
+ "github.com/gohugoio/hugo/helpers"
)
func TestResolveMarkup(t *testing.T) {
@@ -304,3 +303,26 @@ func BenchmarkUniqueStrings(b *testing.B) {
}
})
}
+
+func TestStringSliceToList(t *testing.T) {
+ for _, tt := range []struct {
+ slice []string
+ conjunction string
+ want string
+ }{
+ {[]string{}, "", ""},
+ {[]string{"foo"}, "", "foo"},
+ {[]string{"foo"}, "and", "foo"},
+ {[]string{"foo", "bar"}, "", "foo and bar"},
+ {[]string{"foo", "bar"}, "and", "foo and bar"},
+ {[]string{"foo", "bar"}, "or", "foo or bar"},
+ {[]string{"foo", "bar", "baz"}, "", "foo, bar, and baz"},
+ {[]string{"foo", "bar", "baz"}, "and", "foo, bar, and baz"},
+ {[]string{"foo", "bar", "baz"}, "or", "foo, bar, or baz"},
+ } {
+ got := helpers.StringSliceToList(tt.slice, tt.conjunction)
+ if got != tt.want {
+ t.Errorf("StringSliceToList() got: %q, want: %q", got, tt.want)
+ }
+ }
+}
diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go
index 1f7f3411e..15e9a890c 100644
--- a/hugolib/page__per_output.go
+++ b/hugolib/page__per_output.go
@@ -29,6 +29,7 @@ import (
"github.com/spf13/cast"
"github.com/gohugoio/hugo/markup/converter/hooks"
+ gc "github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
"github.com/gohugoio/hugo/markup/tableofcontents"
"github.com/gohugoio/hugo/markup/converter"
@@ -304,12 +305,17 @@ func (pco *pageContentOutput) initRenderHooks() error {
}
renderHookConfig := pco.po.p.s.conf.Markup.Goldmark.RenderHooks
- var ignoreInternal bool
+ var ignoreEmbedded bool
+
+ // For multilingual single-host sites, "auto" becomes "fallback"
+ // earlier in the process.
switch layoutDescriptor.Variant1 {
case "link":
- ignoreInternal = !renderHookConfig.Link.IsEnableDefault()
+ ignoreEmbedded = renderHookConfig.Link.UseEmbedded == gc.RenderHookUseEmbeddedNever ||
+ renderHookConfig.Link.UseEmbedded == gc.RenderHookUseEmbeddedAuto
case "image":
- ignoreInternal = !renderHookConfig.Image.IsEnableDefault()
+ ignoreEmbedded = renderHookConfig.Image.UseEmbedded == gc.RenderHookUseEmbeddedNever ||
+ renderHookConfig.Image.UseEmbedded == gc.RenderHookUseEmbeddedAuto
}
candidates := pco.po.p.s.renderFormats
@@ -323,8 +329,8 @@ func (pco *pageContentOutput) initRenderHooks() error {
return false
}
- if ignoreInternal && candidate.SubCategory() == tplimpl.SubCategoryEmbedded {
- // Don't consider the internal hook templates.
+ if ignoreEmbedded && candidate.SubCategory() == tplimpl.SubCategoryEmbedded {
+ // Don't consider the embedded hook templates.
return false
}
diff --git a/hugolib/site.go b/hugolib/site.go
index acd3b5410..e37dbd5ef 100644
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -406,6 +406,7 @@ func newHugoSites(cfg deps.DepsCfg, d *deps.Deps, pageTrees *pageTrees, sites []
MediaTypes: s.conf.MediaTypes.Config,
DefaultOutputFormat: s.conf.DefaultOutputFormat,
TaxonomySingularPlural: s.conf.Taxonomies,
+ RenderHooks: s.conf.Markup.Goldmark.RenderHooks,
}, tplimpl.SiteOptions{
Site: s,
TemplateFuncs: tplimplinit.CreateFuncMap(s.Deps),
diff --git a/markup/goldmark/goldmark_config/config.go b/markup/goldmark/goldmark_config/config.go
index 04eb371d9..168a3b8fa 100644
--- a/markup/goldmark/goldmark_config/config.go
+++ b/markup/goldmark/goldmark_config/config.go
@@ -15,9 +15,13 @@
package goldmark_config
const (
- AutoIDTypeGitHub = "github"
- AutoIDTypeGitHubAscii = "github-ascii"
- AutoIDTypeBlackfriday = "blackfriday"
+ AutoIDTypeBlackfriday = "blackfriday"
+ AutoIDTypeGitHub = "github"
+ AutoIDTypeGitHubAscii = "github-ascii"
+ RenderHookUseEmbeddedAlways = "always"
+ RenderHookUseEmbeddedAuto = "auto"
+ RenderHookUseEmbeddedFallback = "fallback"
+ RenderHookUseEmbeddedNever = "never"
)
// Default holds the default Goldmark configuration.
@@ -87,6 +91,14 @@ var Default = Config{
Block: false,
},
},
+ RenderHooks: RenderHooks{
+ Image: ImageRenderHook{
+ UseEmbedded: RenderHookUseEmbeddedAuto,
+ },
+ Link: LinkRenderHook{
+ UseEmbedded: RenderHookUseEmbeddedAuto,
+ },
+ },
}
// Config configures Goldmark.
@@ -118,22 +130,24 @@ type RenderHooks struct {
type ImageRenderHook struct {
// Enable the default image render hook.
// We need to know if it is set or not, hence the pointer.
+ // Deprecated: Use UseEmbedded instead.
EnableDefault *bool
-}
-func (h ImageRenderHook) IsEnableDefault() bool {
- return h.EnableDefault != nil && *h.EnableDefault
+ // When to use the embedded image render hook.
+ // One of auto, never, always, or fallback. Default is auto.
+ UseEmbedded string
}
// LinkRenderHook contains configuration for the link render hook.
type LinkRenderHook struct {
// Disable the default image render hook.
// We need to know if it is set or not, hence the pointer.
+ // Deprecated: Use UseEmbedded instead.
EnableDefault *bool
-}
-func (h LinkRenderHook) IsEnableDefault() bool {
- return h.EnableDefault != nil && *h.EnableDefault
+ // When to use the embedded link render hook.
+ // One of auto, never, always, or fallback. Default is auto.
+ UseEmbedded string
}
type Extensions struct {
diff --git a/tpl/tplimpl/render_hook_integration_test.go b/tpl/tplimpl/render_hook_integration_test.go
index 45c6cced1..e67701594 100644
--- a/tpl/tplimpl/render_hook_integration_test.go
+++ b/tpl/tplimpl/render_hook_integration_test.go
@@ -192,3 +192,115 @@ iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAA
``,
)
}
+
+// Issue 13535
+func TestEmbeddedLinkAndImageRenderHookConfig(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+disableKinds = ['home','rss','section','sitemap','taxonomy','term']
+
+[markup.goldmark]
+duplicateResourceFiles = false
+
+[markup.goldmark.renderHooks.image]
+#KEY_VALUE
+
+[markup.goldmark.renderHooks.link]
+#KEY_VALUE
+
+#LANGUAGES
+-- content/s1/p1/index.md --
+---
+title: p1
+---
+[p2](p2)
+
+[a](a.txt)
+
+
+-- content/s1/p1/a.txt --
+-- content/s1/p1/b.jpg --
+-- content/s1/p2.md --
+---
+title: p2
+---
+-- layouts/all.html --
+{{ .Content }}
+`
+
+ const customHooks string = `
+-- layouts/s1/_markup/render-link.html --
+custom link render hook: {{ .Text }}|{{ .Destination }}
+-- layouts/s1/_markup/render-image.html --
+custom image render hook: {{ .Text }}|{{ .Destination }}
+`
+
+ const languages string = `
+[languages.en]
+[languages.fr]
+`
+
+ const (
+ fileToCheck = "public/s1/p1/index.html"
+ wantCustom string = "
custom link render hook: p2|p2
\ncustom link render hook: a|a.txt
\ncustom image render hook: b|b.jpg
" + wantEmbedded string = "\n\n