diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 06e8a44d909..1b56669633c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -122,8 +122,6 @@ cd hugo go install ``` ->Note: Some Go tools may not be fully updated to support Go Modules yet. One example would be LiteIDE. Follow [this workaround](https://github.com/visualfc/liteide/issues/986#issuecomment-428117702) for how to continue to work with Hugo below `GOPATH`. - For some convenient build and test targets, you also will want to install Mage: ```bash diff --git a/commands/commandeer.go b/commands/commandeer.go index b4f250a32aa..5d414b04a29 100644 --- a/commands/commandeer.go +++ b/commands/commandeer.go @@ -38,6 +38,7 @@ import ( "github.com/gohugoio/hugo/common/hstrings" "github.com/gohugoio/hugo/common/htime" + "github.com/gohugoio/hugo/common/hugo" "github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/common/paths" "github.com/gohugoio/hugo/config" @@ -50,9 +51,7 @@ import ( "github.com/spf13/cobra" ) -var ( - errHelp = errors.New("help requested") -) +var errHelp = errors.New("help requested") // Execute executes a command. func Execute(args []string) error { @@ -182,11 +181,9 @@ func (r *rootCommand) ConfigFromConfig(key int32, oldConf *commonConfig) (*commo cfg: oldConf.cfg, fs: fs, }, nil - }) return cc, err - } func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commonConfig, error) { @@ -211,7 +208,7 @@ func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commo if !cfg.IsSet("workingDir") { cfg.Set("workingDir", dir) } else { - if err := os.MkdirAll(cfg.GetString("workingDir"), 0777); err != nil { + if err := os.MkdirAll(cfg.GetString("workingDir"), 0o777); err != nil { return nil, fmt.Errorf("failed to create workingDir: %w", err) } } @@ -303,7 +300,6 @@ func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commo }) return cc, err - } func (r *rootCommand) HugFromConfig(conf *commonConfig) (*hugolib.HugoSites, error) { @@ -348,7 +344,6 @@ func (r *rootCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args err := b.build() return err }() - if err != nil { return err } @@ -434,26 +429,26 @@ func (r *rootCommand) createLogger(running bool) (loggers.Logger, error) { } } else { if r.verbose { - helpers.Deprecated("--verbose", "use --logLevel info", false) + hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0") + hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0") level = logg.LevelInfo } if r.debug { - helpers.Deprecated("--debug", "use --logLevel debug", false) + hugo.Deprecate("--debug", "use --logLevel debug", "v0.114.0") level = logg.LevelDebug } } optsLogger := loggers.Options{ - Distinct: true, - Level: level, - Stdout: r.Out, - Stderr: r.Out, - StoreErrors: running, + DistinctLevel: logg.LevelWarn, + Level: level, + Stdout: r.Out, + Stderr: r.Out, + StoreErrors: running, } return loggers.New(optsLogger), nil - } func (r *rootCommand) Reset() { @@ -519,7 +514,6 @@ func applyLocalFlagsBuildConfig(cmd *cobra.Command, r *rootCommand) { _ = cmd.Flags().SetAnnotation("cacheDir", cobra.BashCompSubdirsInDir, []string{}) cmd.Flags().StringP("contentDir", "c", "", "filesystem path to content directory") _ = cmd.Flags().SetAnnotation("theme", cobra.BashCompSubdirsInDir, []string{"themes"}) - } // Flags needed to do a build (used by hugo and hugo server commands) @@ -558,7 +552,6 @@ func applyLocalFlagsBuild(cmd *cobra.Command, r *rootCommand) { cmd.Flags().StringSlice("disableKinds", []string{}, "disable different kind of pages (home, RSS etc.)") cmd.Flags().Bool("minify", false, "minify any supported output format (HTML, XML etc.)") _ = cmd.Flags().SetAnnotation("destination", cobra.BashCompSubdirsInDir, []string{}) - } func (r *rootCommand) timeTrack(start time.Time, name string) { diff --git a/commands/hugo_windows.go b/commands/hugo_windows.go index e1fd981323b..169c6288f07 100644 --- a/commands/hugo_windows.go +++ b/commands/hugo_windows.go @@ -25,9 +25,9 @@ func init() { // This message to show to Windows users if Hugo is opened from explorer.exe cobra.MousetrapHelpText = ` - Hugo is a command-line tool for generating static website. + Hugo is a command-line tool for generating static websites. + + You need to open PowerShell and run Hugo from there. - You need to open cmd.exe and run Hugo from there. - Visit https://gohugo.io/ for more information.` } diff --git a/commands/new.go b/commands/new.go index ffd75b2bbb4..8e348366dd8 100644 --- a/commands/new.go +++ b/commands/new.go @@ -63,7 +63,6 @@ Ensure you run this within the root directory of your site.`, cmd.Flags().StringVarP(&contentType, "kind", "k", "", "content type to create") cmd.Flags().String("editor", "", "edit new content with this editor, if provided") cmd.Flags().BoolVarP(&force, "force", "f", false, "overwrite file if it already exists") - cmd.Flags().StringVar(&format, "format", "toml", "preferred file format (toml, yaml or json)") applyLocalFlagsBuildConfig(cmd, r) }, diff --git a/commands/server.go b/commands/server.go index 364e59f8ff3..63c09fccd50 100644 --- a/commands/server.go +++ b/commands/server.go @@ -927,7 +927,7 @@ func (c *serverCommand) serve() error { for i := range baseURLs { mu, listener, serverURL, endpoint, err := srv.createEndpoint(i) - var srv *http.Server + var srv *http.Server if c.tlsCertFile != "" && c.tlsKeyFile != "" { srv = &http.Server{ Addr: endpoint, diff --git a/common/hugo/hugo.go b/common/hugo/hugo.go index c28dd0b5308..dedbc8c98d4 100644 --- a/common/hugo/hugo.go +++ b/common/hugo/hugo.go @@ -22,14 +22,15 @@ import ( "sort" "strings" "sync" + "time" godartsassv1 "github.com/bep/godartsass" + "github.com/bep/logg" "github.com/mitchellh/mapstructure" - "time" - "github.com/bep/godartsass/v2" "github.com/gohugoio/hugo/common/hexec" + "github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/hugofs/files" "github.com/spf13/afero" @@ -78,10 +79,22 @@ func (i HugoInfo) Generator() template.HTML { return template.HTML(fmt.Sprintf(``, CurrentVersion.String())) } +// IsDevelopment reports whether the current running environment is "development". +func (i HugoInfo) IsDevelopment() bool { + return i.Environment == EnvironmentDevelopment +} + +// IsProduction reports whether the current running environment is "production". func (i HugoInfo) IsProduction() bool { return i.Environment == EnvironmentProduction } +// IsServer reports whether the built-in server is running. +func (i HugoInfo) IsServer() bool { + return i.conf.Running() +} + +// IsExtended reports whether the Hugo binary is the extended version. func (i HugoInfo) IsExtended() bool { return IsExtended } @@ -99,6 +112,7 @@ func (i HugoInfo) Deps() []*Dependency { // ConfigProvider represents the config options that are relevant for HugoInfo. type ConfigProvider interface { Environment() string + Running() bool WorkingDir() string } @@ -170,8 +184,10 @@ type buildInfo struct { *debug.BuildInfo } -var bInfo *buildInfo -var bInfoInit sync.Once +var ( + bInfo *buildInfo + bInfoInit sync.Once +) func getBuildInfo() *buildInfo { bInfoInit.Do(func() { @@ -198,7 +214,6 @@ func getBuildInfo() *buildInfo { bInfo.GoArch = s.Value } } - }) return bInfo @@ -242,7 +257,7 @@ func GetDependencyListNonGo() []string { } if dartSass := dartSassVersion(); dartSass.ProtocolVersion != "" { - var dartSassPath = "github.com/sass/dart-sass-embedded" + dartSassPath := "github.com/sass/dart-sass-embedded" if IsDartSassV2() { dartSassPath = "github.com/sass/dart-sass" } @@ -333,3 +348,45 @@ var ( func IsDartSassV2() bool { return !strings.Contains(DartSassBinaryName, "embedded") } + +// Deprecate informs about a deprecation starting at the given version. +// +// A deprecation typically needs a simple change in the template, but doing so will make the template incompatible with older versions. +// Theme maintainers generally want +// 1. No warnings or errors in the console when building a Hugo site. +// 2. Their theme to work for at least the last few Hugo versions. +func Deprecate(item, alternative string, version string) { + level := deprecationLogLevelFromVersion(version) + DeprecateLevel(item, alternative, version, level) +} + +// DeprecateLevel informs about a deprecation logging at the given level. +func DeprecateLevel(item, alternative, version string, level logg.Level) { + var msg string + if level == logg.LevelError { + msg = fmt.Sprintf("%s was deprecated in Hugo %s and will be removed in Hugo %s. %s", item, version, CurrentVersion.Next().ReleaseVersion(), alternative) + } else { + msg = fmt.Sprintf("%s was deprecated in Hugo %s and will be removed in a future release. %s", item, version, alternative) + } + + loggers.Log().Logger().WithLevel(level).WithField(loggers.FieldNameCmd, "deprecated").Logf(msg) +} + +// We ususally do about one minor version a month. +// We want people to run at least the current and previous version without any warnings. +// We want people who don't update Hugo that often to see the warnings and errors before we remove the feature. +func deprecationLogLevelFromVersion(ver string) logg.Level { + from := MustParseVersion(ver) + to := CurrentVersion + minorDiff := to.Minor - from.Minor + switch { + case minorDiff >= 12: + // Start failing the build after about a year. + return logg.LevelError + case minorDiff >= 6: + // Start printing warnings after about six months. + return logg.LevelWarn + default: + return logg.LevelInfo + } +} diff --git a/common/hugo/hugo_test.go b/common/hugo/hugo_test.go index b0279f11137..e252dffbe92 100644 --- a/common/hugo/hugo_test.go +++ b/common/hugo/hugo_test.go @@ -17,13 +17,14 @@ import ( "fmt" "testing" + "github.com/bep/logg" qt "github.com/frankban/quicktest" ) func TestHugoInfo(t *testing.T) { c := qt.New(t) - conf := testConfig{environment: "production", workingDir: "/mywork"} + conf := testConfig{environment: "production", workingDir: "/mywork", running: false} hugoInfo := NewInfo(conf, nil) c.Assert(hugoInfo.Version(), qt.Equals, CurrentVersion.Version()) @@ -38,15 +39,34 @@ func TestHugoInfo(t *testing.T) { } c.Assert(hugoInfo.Environment, qt.Equals, "production") c.Assert(string(hugoInfo.Generator()), qt.Contains, fmt.Sprintf("Hugo %s", hugoInfo.Version())) + c.Assert(hugoInfo.IsDevelopment(), qt.Equals, false) c.Assert(hugoInfo.IsProduction(), qt.Equals, true) c.Assert(hugoInfo.IsExtended(), qt.Equals, IsExtended) + c.Assert(hugoInfo.IsServer(), qt.Equals, false) - devHugoInfo := NewInfo(testConfig{environment: "development"}, nil) + devHugoInfo := NewInfo(testConfig{environment: "development", running: true}, nil) + c.Assert(devHugoInfo.IsDevelopment(), qt.Equals, true) c.Assert(devHugoInfo.IsProduction(), qt.Equals, false) + c.Assert(devHugoInfo.IsServer(), qt.Equals, true) +} + +func TestDeprecationLogLevelFromVersion(t *testing.T) { + c := qt.New(t) + + c.Assert(deprecationLogLevelFromVersion("0.55.0"), qt.Equals, logg.LevelError) + ver := CurrentVersion + c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelInfo) + ver.Minor -= 1 + c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelInfo) + ver.Minor -= 6 + c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelWarn) + ver.Minor -= 6 + c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelError) } type testConfig struct { environment string + running bool workingDir string } @@ -54,6 +74,10 @@ func (c testConfig) Environment() string { return c.environment } +func (c testConfig) Running() bool { + return c.running +} + func (c testConfig) WorkingDir() string { return c.workingDir } diff --git a/common/hugo/version.go b/common/hugo/version.go index 3bb6472e2fa..6cabfdbb964 100644 --- a/common/hugo/version.go +++ b/common/hugo/version.go @@ -67,8 +67,11 @@ func (h VersionString) String() string { // Compare implements the compare.Comparer interface. func (h VersionString) Compare(other any) int { - v := MustParseVersion(h.String()) - return compareVersions(v, other) + return compareVersions(h.Version(), other) +} + +func (h VersionString) Version() Version { + return MustParseVersion(h.String()) } // Eq implements the compare.Eqer interface. @@ -264,7 +267,6 @@ func compareFloatWithVersion(v1 float64, v2 Version) int { if v1maj > v2.Major { return 1 - } if v1maj < v2.Major { @@ -276,7 +278,6 @@ func compareFloatWithVersion(v1 float64, v2 Version) int { } return -1 - } func GoMinorVersion() int { diff --git a/common/hugo/version_current.go b/common/hugo/version_current.go index 08da3b4bd22..aa361b18945 100644 --- a/common/hugo/version_current.go +++ b/common/hugo/version_current.go @@ -17,7 +17,7 @@ package hugo // This should be the only one. var CurrentVersion = Version{ Major: 0, - Minor: 120, + Minor: 121, PatchLevel: 0, Suffix: "-DEV", } diff --git a/common/loggers/logger.go b/common/loggers/logger.go index a50502897b9..bc64ae0e5b7 100644 --- a/common/loggers/logger.go +++ b/common/loggers/logger.go @@ -40,7 +40,7 @@ type Options struct { Level logg.Level Stdout io.Writer Stderr io.Writer - Distinct bool + DistinctLevel logg.Level StoreErrors bool HandlerPost func(e *logg.Entry) error SuppressStatements map[string]bool @@ -92,8 +92,8 @@ func New(opts Options) Logger { logHandler = multi.New(handlers...) var logOnce *logOnceHandler - if opts.Distinct { - logOnce = newLogOnceHandler(logg.LevelWarn) + if opts.DistinctLevel != 0 { + logOnce = newLogOnceHandler(opts.DistinctLevel) logHandler = newStopHandler(logOnce, logHandler) } @@ -137,10 +137,10 @@ func New(opts Options) Logger { // NewDefault creates a new logger with the default options. func NewDefault() Logger { opts := Options{ - Distinct: true, - Level: logg.LevelWarn, - Stdout: os.Stdout, - Stderr: os.Stdout, + DistinctLevel: logg.LevelWarn, + Level: logg.LevelWarn, + Stdout: os.Stdout, + Stderr: os.Stdout, } return New(opts) } diff --git a/common/loggers/logger_test.go b/common/loggers/logger_test.go index 1b42eff53a6..6f589aafe99 100644 --- a/common/loggers/logger_test.go +++ b/common/loggers/logger_test.go @@ -29,10 +29,10 @@ func TestLogDistinct(t *testing.T) { c := qt.New(t) opts := loggers.Options{ - Distinct: true, - StoreErrors: true, - Stdout: io.Discard, - Stderr: io.Discard, + DistinctLevel: logg.LevelWarn, + StoreErrors: true, + Stdout: io.Discard, + Stderr: io.Discard, } l := loggers.New(opts) @@ -85,7 +85,6 @@ func TestOptionStoreErrors(t *testing.T) { c.Assert(sb.String(), qt.Contains, "error 1") c.Assert(sb.String(), qt.Contains, "ERROR") - } func TestLogCount(t *testing.T) { @@ -124,17 +123,16 @@ func TestSuppressStatements(t *testing.T) { c.Assert(errorsStr, qt.Not(qt.Contains), "error 1") c.Assert(errorsStr, qt.Contains, "error 2") c.Assert(l.LoggCount(logg.LevelError), qt.Equals, 1) - } func TestReset(t *testing.T) { c := qt.New(t) opts := loggers.Options{ - StoreErrors: true, - Distinct: true, - Stdout: io.Discard, - Stderr: io.Discard, + StoreErrors: true, + DistinctLevel: logg.LevelWarn, + Stdout: io.Discard, + Stderr: io.Discard, } l := loggers.New(opts) diff --git a/common/loggers/loggerglobal.go b/common/loggers/loggerglobal.go index 92b2469ba9a..6fd474a6935 100644 --- a/common/loggers/loggerglobal.go +++ b/common/loggers/loggerglobal.go @@ -21,7 +21,7 @@ import ( "github.com/bep/logg" ) -func InitGlobalLogger(panicOnWarnings bool) { +func InitGlobalLogger(level logg.Level, panicOnWarnings bool) { logMu.Lock() defer logMu.Unlock() var logHookLast func(e *logg.Entry) error @@ -31,8 +31,9 @@ func InitGlobalLogger(panicOnWarnings bool) { log = New( Options{ - Distinct: true, - HandlerPost: logHookLast, + Level: level, + DistinctLevel: logg.LevelInfo, + HandlerPost: logHookLast, }, ) } @@ -49,5 +50,5 @@ func Log() Logger { var log Logger func init() { - InitGlobalLogger(false) + InitGlobalLogger(logg.LevelWarn, false) } diff --git a/common/paths/url.go b/common/paths/url.go index c538d8f2cbe..cefefdf11b7 100644 --- a/common/paths/url.go +++ b/common/paths/url.go @@ -51,9 +51,10 @@ var pb pathBridge // MakePermalink combines base URL with content path to create full URL paths. // Example -// base: http://spf13.com/ -// path: post/how-i-blog -// result: http://spf13.com/post/how-i-blog +// +// base: http://spf13.com/ +// path: post/how-i-blog +// result: http://spf13.com/post/how-i-blog func MakePermalink(host, plink string) *url.URL { base, err := url.Parse(host) if err != nil { @@ -117,17 +118,19 @@ func PrettifyURL(in string) string { // PrettifyURLPath takes a URL path to a content and converts it // to enable pretty URLs. -// /section/name.html becomes /section/name/index.html -// /section/name/ becomes /section/name/index.html -// /section/name/index.html becomes /section/name/index.html +// +// /section/name.html becomes /section/name/index.html +// /section/name/ becomes /section/name/index.html +// /section/name/index.html becomes /section/name/index.html func PrettifyURLPath(in string) string { return prettifyPath(in, pb) } // Uglify does the opposite of PrettifyURLPath(). -// /section/name/index.html becomes /section/name.html -// /section/name/ becomes /section/name.html -// /section/name.html becomes /section/name.html +// +// /section/name/index.html becomes /section/name.html +// /section/name/ becomes /section/name.html +// /section/name.html becomes /section/name.html func Uglify(in string) string { if path.Ext(in) == "" { if len(in) < 2 { diff --git a/config/allconfig/allconfig.go b/config/allconfig/allconfig.go index 134df1f5c25..9f0d73ecda3 100644 --- a/config/allconfig/allconfig.go +++ b/config/allconfig/allconfig.go @@ -27,6 +27,7 @@ import ( "time" "github.com/gohugoio/hugo/cache/filecache" + "github.com/gohugoio/hugo/common/hugo" "github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/common/urls" @@ -784,7 +785,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon // We accidentally allowed it in the past, so we need to support it a little longer, // But log a warning. if _, found := params[kk]; !found { - helpers.Deprecated(fmt.Sprintf("config: languages.%s.%s: custom params on the language top level", k, kk), fmt.Sprintf("Put the value below [languages.%s.params]. See https://gohugo.io/content-management/multilingual/#changes-in-hugo-01120", k), false) + hugo.Deprecate(fmt.Sprintf("config: languages.%s.%s: custom params on the language top level", k, kk), fmt.Sprintf("Put the value below [languages.%s.params]. See https://gohugo.io/content-management/multilingual/#changes-in-hugo-01120", k), "v0.112.0") params[kk] = vv } } diff --git a/config/allconfig/configlanguage.go b/config/allconfig/configlanguage.go index 27ba00d8204..2c5a116f420 100644 --- a/config/allconfig/configlanguage.go +++ b/config/allconfig/configlanguage.go @@ -228,3 +228,7 @@ func (c ConfigLanguage) PaginatePath() string { func (c ConfigLanguage) StaticDirs() []string { return c.config.staticDirs() } + +func (c ConfigLanguage) EnableEmoji() bool { + return c.config.EnableEmoji +} diff --git a/config/allconfig/load.go b/config/allconfig/load.go index 3af6147da6a..e7dae18066e 100644 --- a/config/allconfig/load.go +++ b/config/allconfig/load.go @@ -93,10 +93,10 @@ func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) { // This is unfortunate, but these are global settings. tpl.SetSecurityAllowActionJSTmpl(configs.Base.Security.GoTemplates.AllowActionJSTmpl) - loggers.InitGlobalLogger(configs.Base.PanicOnWarning) - return configs, nil + loggers.InitGlobalLogger(d.Logger.Level(), configs.Base.PanicOnWarning) + return configs, nil } // ConfigSourceDescriptor describes where to find the config (e.g. config.toml etc.). @@ -330,7 +330,6 @@ func (l *configLoader) envStringToVal(k, v string) any { default: return v } - } func (l *configLoader) loadConfigMain(d ConfigSourceDescriptor) (config.LoadConfigResult, modules.ModulesConfig, error) { diff --git a/config/configProvider.go b/config/configProvider.go index 94683005667..8e2ab033482 100644 --- a/config/configProvider.go +++ b/config/configProvider.go @@ -66,6 +66,7 @@ type AllProvider interface { StaticDirs() []string IgnoredErrors() map[string]bool WorkingDir() string + EnableEmoji() bool } // Provider provides the configuration settings for Hugo. diff --git a/create/skeletons/theme/layouts/partials/menu.html b/create/skeletons/theme/layouts/partials/menu.html index 8f72130d07b..71831809606 100644 --- a/create/skeletons/theme/layouts/partials/menu.html +++ b/create/skeletons/theme/layouts/partials/menu.html @@ -27,6 +27,12 @@ {{- else if $page.HasMenuCurrent .Menu .}} {{- $attrs = merge $attrs (dict "class" "ancestor" "aria-current" "true") }} {{- end }} + {{- $name := .Name }} + {{- with .Identifier }} + {{- with T . }} + {{- $name = . }} + {{- end }} + {{- end }}
  • {{ or (T .Identifier) .Name | safeHTML }} + >{{ $name }} {{- with .Children }} {{< /code >}} -[dotdoc]: https://golang.org/pkg/text/template/#hdr-Variables +[`first`]: /functions/collections/first +[`index`]: /functions/collections/indexfunction +[`isset`]: /functions/collections/isset [config]: /getting-started/configuration -[first]: /functions/first +[dotdoc]: https://golang.org/pkg/text/template/#hdr-Variables [front matter]: /content-management/front-matter [functions]: /functions +[identifier]: /getting-started/glossary/#identifier [internal templates]: /templates/internal -[isset]: /functions/isset [math]: /functions/math [pagevars]: /variables/page [param]: /functions/param [partials]: /templates/partials [relpermalink]: /variables/page#page-variables -[safehtml]: /functions/safehtml +[`safehtml`]: /functions/safe/html [sitevars]: /variables/site [variables]: /variables -[with]: /functions/with +[`with`]: /functions/go-template/with diff --git a/docs/content/en/templates/lists/index.md b/docs/content/en/templates/lists/index.md index f488f6fe9aa..b48969e96be 100644 --- a/docs/content/en/templates/lists/index.md +++ b/docs/content/en/templates/lists/index.md @@ -418,7 +418,7 @@ In the above example, you may want `{{ .Title }}` to point the `title` field you {{ end }} {{< /code >}} -{{< new-in "0.97.0" >}} `GroupByDate` accepts the same time layouts as in [time.Format](/functions/dateformat/) and The `.Key` in the result will be localized for the current language. +{{< new-in "0.97.0" >}} `GroupByDate` accepts the same time layouts as in [`time.Format`] and the `.Key` in the result will be localized for the current language. ### By publish date @@ -437,8 +437,7 @@ In the above example, you may want `{{ .Title }}` to point the `title` field you {{ end }} {{< /code >}} -{{< new-in "0.97.0" >}} `GroupByDate` accepts the same time layouts as in [time.Format](/functions/dateformat/) and The `.Key` in the result will be localized for the current language. - +{{< new-in "0.97.0" >}} `GroupByDate` accepts the same time layouts as in [`time.Format`] and the `.Key` in the result will be localized for the current language. ### By expiration date @@ -457,7 +456,7 @@ In the above example, you may want `{{ .Title }}` to point the `title` field you {{ end }} {{< /code >}} -{{< new-in "0.97.0" >}} `GroupByDate` accepts the same time layouts as in [time.Format](/functions/dateformat/) and The `.Key` in the result will be localized for the current language. +{{< new-in "0.97.0" >}} `GroupByDate` accepts the same time layouts as in [`time.Format`] and the `.Key` in the result will be localized for the current language. ### By last modified date @@ -476,7 +475,7 @@ In the above example, you may want `{{ .Title }}` to point the `title` field you {{ end }} {{< /code >}} -{{< new-in "0.97.0" >}} `GroupByDate` accepts the same time layouts as in [time.Format](/functions/dateformat/) and The `.Key` in the result will be localized for the current language. +{{< new-in "0.97.0" >}} `GroupByDate` accepts the same time layouts as in [`time.Format`] and the `.Key` in the result will be localized for the current language. ### By page parameter @@ -567,11 +566,11 @@ Here is the ordering for the example that follows: ## Filtering and limiting lists Sometimes you only want to list a subset of the available content. A -common is to only display posts from [**main sections**][mainsections] +common is to only display posts from [main sections] on the blog's homepage. -See the documentation on [`where` function][wherefunction] and -[`first` function][firstfunction] for further details. +See the documentation on [`where`] and +[`first`] for further details. [base]: /templates/base/ [bepsays]: https://bepsays.com/en/2016/12/19/hugo-018/ @@ -580,11 +579,10 @@ See the documentation on [`where` function][wherefunction] and [front matter]: /content-management/front-matter/ [getpage]: /functions/getpage/ [homepage]: /templates/homepage/ -[homepage]: /templates/homepage/ [mentalmodel]: https://webstyleguide.com/wsg3/3-information-architecture/3-site-structure.html [pagevars]: /variables/page/ [partials]: /templates/partials/ -[RSS 2.0]: https://cyber.harvard.edu/rss/rss.html "RSS 2.0 Specification" +[RSS 2.0]: https://cyber.harvard.edu/rss/rss.html [rss]: /templates/rss/ [sections]: /content-management/sections/ [sectiontemps]: /templates/section-templates/ @@ -593,6 +591,7 @@ See the documentation on [`where` function][wherefunction] and [taxterms]: /templates/taxonomy-templates/#taxonomy-terms-templates [taxvars]: /variables/taxonomy/ [views]: /templates/views/ -[wherefunction]: /functions/where/ -[firstfunction]: /functions/first/ -[mainsections]: /functions/where/#mainsections +[`where`]: /functions/collections/where +[`first`]: /functions/first/ +[main sections]: /functions/collections/where#mainsections +[`time.Format`]: /functions/time/format diff --git a/docs/content/en/templates/lookup-order.md b/docs/content/en/templates/lookup-order.md index 279c6aee29b..c36956cb96b 100644 --- a/docs/content/en/templates/lookup-order.md +++ b/docs/content/en/templates/lookup-order.md @@ -39,6 +39,66 @@ Section Templates can live in either the project's or the themes' layout folders, and the most specific templates will be chosen. Hugo will interleave the lookups listed below, finding the most specific one either in the project or themes. {{% /note %}} +## Target a template + +You cannot change the lookup order to target a content page, but you can change a content page to target a template. Specify `type`, `layout`, or both in front matter. + +Consider this content structure: + +```text +content/ +├── about.md +└── contact.md +``` + +Files in the root of the content directory have a [content type] of `page`. To render these pages with a unique template, create a matching subdirectory: + +[content type]: /getting-started/glossary/#content-type + +```text +layouts/ +└── page/ + └── single.html +``` + +But the contact page probably has a form and requires a different template. In the front matter specify `layout`: + +{{< code-toggle file=content/contact.md copy=false >}} +title = 'Contact' +layout = 'contact' +{{< /code-toggle >}} + +Then create the template for the contact page: + +```text +layouts/ +└── page/ + └── contact.html <-- renders contact.md + └── single.html <-- renders about.md +``` + +As a content type, the word `page` is vague. Perhaps `miscellaneous` would be better. Add `type` to the front matter of each page: + +{{< code-toggle file=content/about.md copy=false >}} +title = 'About' +type = 'miscellaneous' +{{< /code-toggle >}} + +{{< code-toggle file=content/contact.md copy=false >}} +title = 'Contact' +type = 'miscellaneous' +layout = 'contact' +{{< /code-toggle >}} + +Now place the layouts in the corresponding directory: + +```text +layouts/ +└── miscellaneous/ + └── contact.html <-- renders contact.md + └── single.html <-- renders about.md +``` + ## Home page {{< datatable-filtered "output" "layouts" "Kind == home" "Example" "OutputFormat" "Suffix" "Template Lookup Order" >}} diff --git a/docs/content/en/templates/menu-templates.md b/docs/content/en/templates/menu-templates.md index 6c009cbdb49..e2d141f06f3 100644 --- a/docs/content/en/templates/menu-templates.md +++ b/docs/content/en/templates/menu-templates.md @@ -49,6 +49,12 @@ This partial template recursively "walks" a menu structure, rendering a localize {{- else if $page.HasMenuCurrent .Menu .}} {{- $attrs = merge $attrs (dict "class" "ancestor" "aria-current" "true") }} {{- end }} + {{- $name := .Name }} + {{- with .Identifier }} + {{- with T . }} + {{- $name = . }} + {{- end }} + {{- end }}
  • {{ or (T .Identifier) .Name | safeHTML }} + >{{ $name }} {{- with .Children }}