mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-25 15:58:53 +00:00
Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c3347a77f | |||
| a3a00016fb | |||
| ffb41d1111 | |||
| 5b0b663ec3 | |||
| 3f68309148 | |||
| ab03588db9 | |||
| 0450d69fc6 | |||
| 1158e63072 | |||
| d1ba52f3c3 | |||
| d0dca65625 | |||
| f30603c47f | |||
| 12c9ce34bb | |||
| 47b055589c | |||
| e56ea40666 | |||
| 4c02a52f7c | |||
| 578442f892 | |||
| 0d390d7eb8 | |||
| da72ac2db9 | |||
| 0ea796dad1 | |||
| 1e690c0f23 | |||
| a619deec18 | |||
| 5fb333b9f5 | |||
| e07028cb90 | |||
| 22a9f3fc98 | |||
| e363964f2f | |||
| c260cb28a9 | |||
| e079145373 | |||
| 5b442b3cce | |||
| 2bc27657d8 | |||
| 28f621d4a7 | |||
| fe7e137e28 | |||
| 1c74abd260 | |||
| 3d6baedaec | |||
| 84ee00bbc2 | |||
| 2f89169baa | |||
| 66a3a11001 | |||
| 8f2eac0195 | |||
| b570a5f118 | |||
| 39fd3b5570 | |||
| aacff7aff4 | |||
| 77df7bbbff | |||
| ae3815898f | |||
| 504a23184f | |||
| e651d29801 | |||
| dec8cd4ada | |||
| 0c453420e6 | |||
| 469124823c | |||
| 96afea4acc | |||
| 6d97ee711e | |||
| f738669a4d | |||
| b63f24adc7 | |||
| 8fb933550f | |||
| a3684c8361 | |||
| 53a8de21b8 | |||
| 37609262dc | |||
| 2b5c335e93 | |||
| 12a28ef773 | |||
| 4ded32d077 | |||
| 1ecd0596a3 | |||
| 371246de25 | |||
| aa0f66b290 | |||
| 47d00202e7 | |||
| 261d7a03ae | |||
| 445283a593 | |||
| 5f667f8796 |
@@ -4,7 +4,7 @@ parameters:
|
||||
defaults: &defaults
|
||||
resource_class: large
|
||||
docker:
|
||||
- image: bepsays/ci-hugoreleaser:1.22300.20000
|
||||
- image: bepsays/ci-hugoreleaser:1.22300.20200
|
||||
environment: &buildenv
|
||||
GOMODCACHE: /root/project/gomodcache
|
||||
version: 2
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
environment:
|
||||
<<: [*buildenv]
|
||||
docker:
|
||||
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22300.20000
|
||||
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22300.20200
|
||||
steps:
|
||||
- *restore-cache
|
||||
- &attach-workspace
|
||||
|
||||
+10
-79
@@ -1,10 +1,9 @@
|
||||
name: Build Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
pull_request:
|
||||
permissions:
|
||||
packages: write
|
||||
|
||||
@@ -14,18 +13,8 @@ env:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform:
|
||||
- linux/amd64
|
||||
- linux/arm64
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
@@ -35,9 +24,6 @@ jobs:
|
||||
with:
|
||||
images: ${{ env.REGISTRY_IMAGE }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
|
||||
|
||||
@@ -49,69 +35,14 @@ jobs:
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push by digest
|
||||
- name: Build and push
|
||||
id: build
|
||||
uses: docker/build-push-action@16ebe778df0e7752d2cfcbd924afdbbd89c1a755 # v6.6.1
|
||||
with:
|
||||
context: .
|
||||
push: ${{ startsWith(github.ref, 'refs/tags') }}
|
||||
platforms: ${{ matrix.platform }}
|
||||
provenance: mode=max
|
||||
sbom: true
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true
|
||||
|
||||
- name: Export digest
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
merge:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build
|
||||
steps:
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
with:
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
|
||||
with:
|
||||
images: ${{ env.REGISTRY_IMAGE }}
|
||||
|
||||
flavor: |
|
||||
latest=false
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create manifest list and push
|
||||
if: ${{ startsWith(github.ref, 'refs/tags') }}
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
$(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *)
|
||||
|
||||
- name: Inspect image
|
||||
if: ${{ startsWith(github.ref, 'refs/tags') }}
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.22.x, 1.23.x]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
os: [ubuntu-latest, windows-latest] # macos disabled for now because of disk space issues.
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
|
||||
+74
-24
@@ -2,44 +2,94 @@
|
||||
# Twitter: https://twitter.com/gohugoio
|
||||
# Website: https://gohugo.io/
|
||||
|
||||
FROM golang:1.21-alpine AS build
|
||||
ARG GO_VERSION="1.23.2"
|
||||
ARG ALPINE_VERSION=3.20
|
||||
|
||||
# Optionally set HUGO_BUILD_TAGS to "extended" or "nodeploy" when building like so:
|
||||
# docker build --build-arg HUGO_BUILD_TAGS=extended .
|
||||
ARG HUGO_BUILD_TAGS
|
||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.5.0 AS xx
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS gobuild
|
||||
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS gorun
|
||||
|
||||
ARG CGO=1
|
||||
ENV CGO_ENABLED=${CGO}
|
||||
ENV GOOS=linux
|
||||
ENV GO111MODULE=on
|
||||
|
||||
FROM gobuild AS build
|
||||
|
||||
RUN apk add clang lld
|
||||
|
||||
# Set up cross-compilation helpers
|
||||
COPY --from=xx / /
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
RUN xx-apk add musl-dev gcc g++
|
||||
|
||||
# Optionally set HUGO_BUILD_TAGS to "none" or "nodeploy" when building like so:
|
||||
# docker build --build-arg HUGO_BUILD_TAGS=nodeploy .
|
||||
#
|
||||
# We build the extended version by default.
|
||||
ARG HUGO_BUILD_TAGS="extended"
|
||||
ENV CGO_ENABLED=1
|
||||
ENV GOPROXY=https://proxy.golang.org
|
||||
ENV GOCACHE=/root/.cache/go-build
|
||||
ENV GOMODCACHE=/go/pkg/mod
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
WORKDIR /go/src/github.com/gohugoio/hugo
|
||||
|
||||
COPY . /go/src/github.com/gohugoio/hugo/
|
||||
# For --mount=type=cache the value of target is the default cache id, so
|
||||
# for the go mod cache it would be good if we could share it with other Go images using the same setup,
|
||||
# but the go build cache needs to be per platform.
|
||||
# See this comment: https://github.com/moby/buildkit/issues/1706#issuecomment-702238282
|
||||
RUN --mount=target=. \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=cache,target=/root/.cache/go-build,id=go-build-$TARGETPLATFORM <<EOT
|
||||
set -ex
|
||||
xx-go build -tags "$HUGO_BUILD_TAGS" -ldflags "-s -w -X github.com/gohugoio/hugo/common/hugo.vendorInfo=docker" -o /usr/bin/hugo
|
||||
xx-verify /usr/bin/hugo
|
||||
EOT
|
||||
|
||||
# gcc/g++ are required to build SASS libraries for extended version
|
||||
RUN apk update && \
|
||||
apk add --no-cache gcc g++ musl-dev git && \
|
||||
go install github.com/magefile/mage
|
||||
FROM gorun AS final
|
||||
|
||||
RUN mage hugo && mage install
|
||||
COPY --from=build /usr/bin/hugo /usr/bin/hugo
|
||||
|
||||
# ---
|
||||
# libc6-compat are required for extended libraries (libsass, libwebp).
|
||||
RUN apk add --no-cache \
|
||||
libc6-compat \
|
||||
git \
|
||||
runuser \
|
||||
curl \
|
||||
nodejs \
|
||||
npm
|
||||
|
||||
FROM alpine:3.18
|
||||
RUN mkdir -p /var/hugo/bin && \
|
||||
addgroup -Sg 1000 hugo && \
|
||||
adduser -Sg hugo -u 1000 -h /var/hugo hugo && \
|
||||
chown -R hugo: /var/hugo && \
|
||||
# For the Hugo's Git integration to work.
|
||||
runuser -u hugo -- git config --global --add safe.directory /project && \
|
||||
# See https://github.com/gohugoio/hugo/issues/9810
|
||||
runuser -u hugo -- git config --global core.quotepath false
|
||||
|
||||
COPY --from=build /go/bin/hugo /usr/bin/hugo
|
||||
VOLUME /project
|
||||
WORKDIR /project
|
||||
USER hugo:hugo
|
||||
ENV HUGO_CACHEDIR=/cache
|
||||
ARG BUILDARCH
|
||||
ENV BUILDARCH=${BUILDARCH}
|
||||
ENV PATH="/var/hugo/bin:$PATH"
|
||||
|
||||
# libc6-compat & libstdc++ are required for extended SASS libraries
|
||||
# ca-certificates are required to fetch outside resources (like Twitter oEmbeds)
|
||||
RUN apk update && \
|
||||
apk add --no-cache ca-certificates libc6-compat libstdc++ git
|
||||
COPY scripts/docker scripts/docker
|
||||
COPY scripts/docker/entrypoint.sh /entrypoint.sh
|
||||
|
||||
VOLUME /site
|
||||
WORKDIR /site
|
||||
# Install default dependencies.
|
||||
RUN scripts/docker/install_runtimedeps_default.sh
|
||||
# Update PATH to reflect the new dependencies.
|
||||
# For more complex setups, we should probably find a way to
|
||||
# delegate this to the script itself, but this will have to do for now.
|
||||
# Also, the dart-sass binary is a little special, other binaries can be put/linked
|
||||
# directly in /var/hugo/bin.
|
||||
ENV PATH="/var/hugo/bin/dart-sass:$PATH"
|
||||
|
||||
# Expose port for live server
|
||||
EXPOSE 1313
|
||||
|
||||
ENTRYPOINT ["hugo"]
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
CMD ["--help"]
|
||||
|
||||
|
||||
+49
-26
@@ -88,6 +88,11 @@ type commonConfig struct {
|
||||
fs *hugofs.Fs
|
||||
}
|
||||
|
||||
type configKey struct {
|
||||
counter int32
|
||||
ignoreModulesDoesNotExists bool
|
||||
}
|
||||
|
||||
// This is the root command.
|
||||
type rootCommand struct {
|
||||
Printf func(format string, v ...interface{})
|
||||
@@ -101,8 +106,8 @@ type rootCommand struct {
|
||||
|
||||
// Some, but not all commands need access to these.
|
||||
// Some needs more than one, so keep them in a small cache.
|
||||
commonConfigs *lazycache.Cache[int32, *commonConfig]
|
||||
hugoSites *lazycache.Cache[int32, *hugolib.HugoSites]
|
||||
commonConfigs *lazycache.Cache[configKey, *commonConfig]
|
||||
hugoSites *lazycache.Cache[configKey, *hugolib.HugoSites]
|
||||
|
||||
// changesFromBuild received from Hugo in watch mode.
|
||||
changesFromBuild chan []identity.Identity
|
||||
@@ -160,17 +165,18 @@ func (r *rootCommand) Commands() []simplecobra.Commander {
|
||||
return r.commands
|
||||
}
|
||||
|
||||
func (r *rootCommand) ConfigFromConfig(key int32, oldConf *commonConfig) (*commonConfig, error) {
|
||||
cc, _, err := r.commonConfigs.GetOrCreate(key, func(key int32) (*commonConfig, error) {
|
||||
func (r *rootCommand) ConfigFromConfig(key configKey, oldConf *commonConfig) (*commonConfig, error) {
|
||||
cc, _, err := r.commonConfigs.GetOrCreate(key, func(key configKey) (*commonConfig, error) {
|
||||
fs := oldConf.fs
|
||||
configs, err := allconfig.LoadConfig(
|
||||
allconfig.ConfigSourceDescriptor{
|
||||
Flags: oldConf.cfg,
|
||||
Fs: fs.Source,
|
||||
Filename: r.cfgFile,
|
||||
ConfigDir: r.cfgDir,
|
||||
Logger: r.logger,
|
||||
Environment: r.environment,
|
||||
Flags: oldConf.cfg,
|
||||
Fs: fs.Source,
|
||||
Filename: r.cfgFile,
|
||||
ConfigDir: r.cfgDir,
|
||||
Logger: r.logger,
|
||||
Environment: r.environment,
|
||||
IgnoreModuleDoesNotExist: key.ignoreModulesDoesNotExists,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -193,11 +199,11 @@ func (r *rootCommand) ConfigFromConfig(key int32, oldConf *commonConfig) (*commo
|
||||
return cc, err
|
||||
}
|
||||
|
||||
func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commonConfig, error) {
|
||||
func (r *rootCommand) ConfigFromProvider(key configKey, cfg config.Provider) (*commonConfig, error) {
|
||||
if cfg == nil {
|
||||
panic("cfg must be set")
|
||||
}
|
||||
cc, _, err := r.commonConfigs.GetOrCreate(key, func(key int32) (*commonConfig, error) {
|
||||
cc, _, err := r.commonConfigs.GetOrCreate(key, func(key configKey) (*commonConfig, error) {
|
||||
var dir string
|
||||
if r.source != "" {
|
||||
dir, _ = filepath.Abs(r.source)
|
||||
@@ -220,12 +226,13 @@ func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commo
|
||||
// Load the config first to allow publishDir to be configured in config file.
|
||||
configs, err := allconfig.LoadConfig(
|
||||
allconfig.ConfigSourceDescriptor{
|
||||
Flags: cfg,
|
||||
Fs: hugofs.Os,
|
||||
Filename: r.cfgFile,
|
||||
ConfigDir: r.cfgDir,
|
||||
Environment: r.environment,
|
||||
Logger: r.logger,
|
||||
Flags: cfg,
|
||||
Fs: hugofs.Os,
|
||||
Filename: r.cfgFile,
|
||||
ConfigDir: r.cfgDir,
|
||||
Environment: r.environment,
|
||||
Logger: r.logger,
|
||||
IgnoreModuleDoesNotExist: key.ignoreModulesDoesNotExists,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -307,7 +314,8 @@ func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commo
|
||||
}
|
||||
|
||||
func (r *rootCommand) HugFromConfig(conf *commonConfig) (*hugolib.HugoSites, error) {
|
||||
h, _, err := r.hugoSites.GetOrCreate(r.configVersionID.Load(), func(key int32) (*hugolib.HugoSites, error) {
|
||||
k := configKey{counter: r.configVersionID.Load()}
|
||||
h, _, err := r.hugoSites.GetOrCreate(k, func(key configKey) (*hugolib.HugoSites, error) {
|
||||
depsCfg := r.newDepsConfig(conf)
|
||||
return hugolib.NewHugoSites(depsCfg)
|
||||
})
|
||||
@@ -315,7 +323,12 @@ func (r *rootCommand) HugFromConfig(conf *commonConfig) (*hugolib.HugoSites, err
|
||||
}
|
||||
|
||||
func (r *rootCommand) Hugo(cfg config.Provider) (*hugolib.HugoSites, error) {
|
||||
h, _, err := r.hugoSites.GetOrCreate(r.configVersionID.Load(), func(key int32) (*hugolib.HugoSites, error) {
|
||||
return r.getOrCreateHugo(cfg, false)
|
||||
}
|
||||
|
||||
func (r *rootCommand) getOrCreateHugo(cfg config.Provider, ignoreModuleDoesNotExist bool) (*hugolib.HugoSites, error) {
|
||||
k := configKey{counter: r.configVersionID.Load(), ignoreModulesDoesNotExists: ignoreModuleDoesNotExist}
|
||||
h, _, err := r.hugoSites.GetOrCreate(k, func(key configKey) (*hugolib.HugoSites, error) {
|
||||
conf, err := r.ConfigFromProvider(key, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -418,11 +431,11 @@ func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
|
||||
|
||||
r.changesFromBuild = make(chan []identity.Identity, 10)
|
||||
|
||||
r.commonConfigs = lazycache.New(lazycache.Options[int32, *commonConfig]{MaxEntries: 5})
|
||||
r.commonConfigs = lazycache.New(lazycache.Options[configKey, *commonConfig]{MaxEntries: 5})
|
||||
// We don't want to keep stale HugoSites in memory longer than needed.
|
||||
r.hugoSites = lazycache.New(lazycache.Options[int32, *hugolib.HugoSites]{
|
||||
r.hugoSites = lazycache.New(lazycache.Options[configKey, *hugolib.HugoSites]{
|
||||
MaxEntries: 1,
|
||||
OnEvict: func(key int32, value *hugolib.HugoSites) {
|
||||
OnEvict: func(key configKey, value *hugolib.HugoSites) {
|
||||
value.Close()
|
||||
runtime.GC()
|
||||
},
|
||||
@@ -486,16 +499,26 @@ func (r *rootCommand) IsTestRun() bool {
|
||||
}
|
||||
|
||||
func (r *rootCommand) Init(cd *simplecobra.Commandeer) error {
|
||||
return r.initRootCommand("", cd)
|
||||
}
|
||||
|
||||
func (r *rootCommand) initRootCommand(subCommandName string, cd *simplecobra.Commandeer) error {
|
||||
cmd := cd.CobraCommand
|
||||
cmd.Use = "hugo [flags]"
|
||||
cmd.Short = "hugo builds your site"
|
||||
cmd.Long = `hugo is the main command, used to build your Hugo site.
|
||||
commandName := "hugo"
|
||||
if subCommandName != "" {
|
||||
commandName = subCommandName
|
||||
}
|
||||
cmd.Use = fmt.Sprintf("%s [flags]", commandName)
|
||||
cmd.Short = fmt.Sprintf("%s builds your site", commandName)
|
||||
cmd.Long = `COMMAND_NAME is the main command, used to build your Hugo site.
|
||||
|
||||
Hugo is a Fast and Flexible Static Site Generator
|
||||
built with love by spf13 and friends in Go.
|
||||
|
||||
Complete documentation is available at https://gohugo.io/.`
|
||||
|
||||
cmd.Long = strings.ReplaceAll(cmd.Long, "COMMAND_NAME", commandName)
|
||||
|
||||
// Configure persistent flags
|
||||
cmd.PersistentFlags().StringVarP(&r.source, "source", "s", "", "filesystem path to read files relative from")
|
||||
_ = cmd.MarkFlagDirname("source")
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/bep/simplecobra"
|
||||
)
|
||||
|
||||
@@ -21,6 +23,7 @@ import (
|
||||
func newExec() (*simplecobra.Exec, error) {
|
||||
rootCmd := &rootCommand{
|
||||
commands: []simplecobra.Commander{
|
||||
newHugoBuildCmd(),
|
||||
newVersionCmd(),
|
||||
newEnvCommand(),
|
||||
newServerCommand(),
|
||||
@@ -38,3 +41,33 @@ func newExec() (*simplecobra.Exec, error) {
|
||||
|
||||
return simplecobra.New(rootCmd)
|
||||
}
|
||||
|
||||
func newHugoBuildCmd() simplecobra.Commander {
|
||||
return &hugoBuildCommand{}
|
||||
}
|
||||
|
||||
// hugoBuildCommand just delegates to the rootCommand.
|
||||
type hugoBuildCommand struct {
|
||||
rootCmd *rootCommand
|
||||
}
|
||||
|
||||
func (c *hugoBuildCommand) Commands() []simplecobra.Commander {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *hugoBuildCommand) Name() string {
|
||||
return "build"
|
||||
}
|
||||
|
||||
func (c *hugoBuildCommand) Init(cd *simplecobra.Commandeer) error {
|
||||
c.rootCmd = cd.Root.Command.(*rootCommand)
|
||||
return c.rootCmd.initRootCommand("build", cd)
|
||||
}
|
||||
|
||||
func (c *hugoBuildCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
|
||||
return c.rootCmd.PreRun(cd, runner)
|
||||
}
|
||||
|
||||
func (c *hugoBuildCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
|
||||
return c.rootCmd.Run(ctx, cd, args)
|
||||
}
|
||||
|
||||
+2
-2
@@ -58,7 +58,7 @@ func (c *configCommand) Name() string {
|
||||
}
|
||||
|
||||
func (c *configCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
|
||||
conf, err := c.r.ConfigFromProvider(c.r.configVersionID.Load(), flagsToCfg(cd, nil))
|
||||
conf, err := c.r.ConfigFromProvider(configKey{counter: c.r.configVersionID.Load()}, flagsToCfg(cd, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -209,7 +209,7 @@ func (c *configMountsCommand) Name() string {
|
||||
|
||||
func (c *configMountsCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
|
||||
r := c.configCmd.r
|
||||
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, nil))
|
||||
conf, err := r.ConfigFromProvider(configKey{counter: c.r.configVersionID.Load()}, flagsToCfg(cd, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -779,6 +779,7 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
||||
istemp := strings.HasSuffix(ext, "~") ||
|
||||
(ext == ".swp") || // vim
|
||||
(ext == ".swx") || // vim
|
||||
(ext == ".bck") || // helix
|
||||
(ext == ".tmp") || // generic temp file
|
||||
(ext == ".DS_Store") || // OSX Thumbnail
|
||||
baseName == "4913" || // vim
|
||||
@@ -1046,7 +1047,7 @@ func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error
|
||||
"fastRenderMode": c.fastRenderMode,
|
||||
})
|
||||
|
||||
conf, err := c.r.ConfigFromProvider(c.r.configVersionID.Load(), flagsToCfg(cd, cfg))
|
||||
conf, err := c.r.ConfigFromProvider(configKey{counter: c.r.configVersionID.Load()}, flagsToCfg(cd, cfg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1115,7 +1116,7 @@ func (c *hugoBuilder) reloadConfig() error {
|
||||
|
||||
if err := c.withConfE(func(conf *commonConfig) error {
|
||||
oldConf := conf
|
||||
newConf, err := c.r.ConfigFromConfig(c.r.configVersionID.Load(), conf)
|
||||
newConf, err := c.r.ConfigFromConfig(configKey{counter: c.r.configVersionID.Load()}, conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+11
-7
@@ -94,7 +94,7 @@ so this may/will change in future versions of Hugo.
|
||||
applyLocalFlagsBuildConfig(cmd, r)
|
||||
},
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
h, err := r.Hugo(flagsToCfg(cd, nil))
|
||||
h, err := r.getOrCreateHugo(flagsToCfg(cd, nil), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -102,7 +102,11 @@ so this may/will change in future versions of Hugo.
|
||||
if len(args) >= 1 {
|
||||
initPath = args[0]
|
||||
}
|
||||
return h.Configs.ModulesClient.Init(initPath)
|
||||
c := h.Configs.ModulesClient
|
||||
if err := c.Init(initPath); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
&simpleCommand{
|
||||
@@ -115,7 +119,7 @@ so this may/will change in future versions of Hugo.
|
||||
cmd.Flags().BoolVarP(&clean, "clean", "", false, "delete module cache for dependencies that fail verification")
|
||||
},
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, nil))
|
||||
conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -135,7 +139,7 @@ Note that for vendored modules, that is the version listed and not the one from
|
||||
cmd.Flags().BoolVarP(&clean, "clean", "", false, "delete module cache for dependencies that fail verification")
|
||||
},
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, nil))
|
||||
conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -271,7 +275,7 @@ Run "go help get" for more information. All flags available for "go get" is also
|
||||
|
||||
cfg := config.New()
|
||||
cfg.Set("workingDir", dir)
|
||||
conf, err := r.ConfigFromProvider(r.configVersionID.Add(1), flagsToCfg(cd, cfg))
|
||||
conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Add(1)}, flagsToCfg(cd, cfg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -284,7 +288,7 @@ Run "go help get" for more information. All flags available for "go get" is also
|
||||
})
|
||||
return nil
|
||||
} else {
|
||||
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, nil))
|
||||
conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -313,7 +317,7 @@ func (c *modCommands) Name() string {
|
||||
}
|
||||
|
||||
func (c *modCommands) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
|
||||
_, err := c.r.ConfigFromProvider(c.r.configVersionID.Load(), nil)
|
||||
_, err := c.r.ConfigFromProvider(configKey{counter: c.r.configVersionID.Load()}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+2
-2
@@ -93,7 +93,7 @@ Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
|
||||
cfg.Set("workingDir", createpath)
|
||||
cfg.Set("publishDir", "public")
|
||||
|
||||
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, cfg))
|
||||
conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, cfg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -136,7 +136,7 @@ according to your needs.`,
|
||||
cfg := config.New()
|
||||
cfg.Set("publishDir", "public")
|
||||
|
||||
conf, err := r.ConfigFromProvider(r.configVersionID.Load(), flagsToCfg(cd, cfg))
|
||||
conf, err := r.ConfigFromProvider(configKey{counter: r.configVersionID.Load()}, flagsToCfg(cd, cfg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+26
-1
@@ -14,6 +14,7 @@
|
||||
package hugo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"os"
|
||||
@@ -29,6 +30,7 @@ import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"github.com/bep/godartsass/v2"
|
||||
"github.com/gohugoio/hugo/common/hcontext"
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
@@ -69,6 +71,9 @@ type HugoInfo struct {
|
||||
|
||||
conf ConfigProvider
|
||||
deps []*Dependency
|
||||
|
||||
// Context gives access to some of the context scoped variables.
|
||||
Context Context
|
||||
}
|
||||
|
||||
// Version returns the current version as a comparable version string.
|
||||
@@ -127,6 +132,26 @@ func (i HugoInfo) IsMultilingual() bool {
|
||||
return i.conf.IsMultilingual()
|
||||
}
|
||||
|
||||
type contextKey string
|
||||
|
||||
var markupScope = hcontext.NewContextDispatcher[string](contextKey("markupScope"))
|
||||
|
||||
type Context struct{}
|
||||
|
||||
func (c Context) MarkupScope(ctx context.Context) string {
|
||||
return GetMarkupScope(ctx)
|
||||
}
|
||||
|
||||
// SetMarkupScope sets the markup scope in the context.
|
||||
func SetMarkupScope(ctx context.Context, s string) context.Context {
|
||||
return markupScope.Set(ctx, s)
|
||||
}
|
||||
|
||||
// GetMarkupScope gets the markup scope from the context.
|
||||
func GetMarkupScope(ctx context.Context) string {
|
||||
return markupScope.Get(ctx)
|
||||
}
|
||||
|
||||
// ConfigProvider represents the config options that are relevant for HugoInfo.
|
||||
type ConfigProvider interface {
|
||||
Environment() string
|
||||
@@ -276,7 +301,7 @@ func GetDependencyListNonGo() []string {
|
||||
if IsExtended {
|
||||
deps = append(
|
||||
deps,
|
||||
formatDep("github.com/sass/libsass", "3.6.5"),
|
||||
formatDep("github.com/sass/libsass", "3.6.6"),
|
||||
formatDep("github.com/webmproject/libwebp", "v1.3.2"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
package hugo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
@@ -64,6 +65,19 @@ func TestDeprecationLogLevelFromVersion(t *testing.T) {
|
||||
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelError)
|
||||
}
|
||||
|
||||
func TestMarkupScope(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
conf := testConfig{environment: "production", workingDir: "/mywork", running: false}
|
||||
info := NewInfo(conf, nil)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
ctx = SetMarkupScope(ctx, "foo")
|
||||
|
||||
c.Assert(info.Context.MarkupScope(ctx), qt.Equals, "foo")
|
||||
}
|
||||
|
||||
type testConfig struct {
|
||||
environment string
|
||||
running bool
|
||||
|
||||
@@ -17,7 +17,7 @@ package hugo
|
||||
// This should be the only one.
|
||||
var CurrentVersion = Version{
|
||||
Major: 0,
|
||||
Minor: 134,
|
||||
Minor: 136,
|
||||
PatchLevel: 0,
|
||||
Suffix: "-DEV",
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
|
||||
} else {
|
||||
high = len(p.s)
|
||||
}
|
||||
id := types.LowHigh{Low: i + 1, High: high}
|
||||
id := types.LowHigh[string]{Low: i + 1, High: high}
|
||||
if len(p.identifiers) == 0 {
|
||||
p.identifiers = append(p.identifiers, id)
|
||||
} else if len(p.identifiers) == 1 {
|
||||
@@ -260,7 +260,7 @@ type Path struct {
|
||||
component string
|
||||
bundleType PathType
|
||||
|
||||
identifiers []types.LowHigh
|
||||
identifiers []types.LowHigh[string]
|
||||
|
||||
posIdentifierLanguage int
|
||||
disabled bool
|
||||
|
||||
@@ -13,8 +13,24 @@
|
||||
|
||||
package hstring
|
||||
|
||||
type RenderedString string
|
||||
import (
|
||||
"html/template"
|
||||
|
||||
func (s RenderedString) String() string {
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
)
|
||||
|
||||
var _ types.PrintableValueProvider = HTML("")
|
||||
|
||||
// HTML is a string that represents rendered HTML.
|
||||
// When printed in templates it will be rendered as template.HTML and considered safe so no need to pipe it into `safeHTML`.
|
||||
// This type was introduced as a wasy to prevent a common case of inifinite recursion in the template rendering
|
||||
// when the `linkify` option is enabled with a common (wrong) construct like `{{ .Text | .Page.RenderString }}` in a hook template.
|
||||
type HTML string
|
||||
|
||||
func (s HTML) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (s HTML) PrintableValue() any {
|
||||
return template.HTML(s)
|
||||
}
|
||||
|
||||
@@ -25,6 +25,6 @@ func TestRenderedString(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
// Validate that it will behave like a string in Hugo settings.
|
||||
c.Assert(cast.ToString(RenderedString("Hugo")), qt.Equals, "Hugo")
|
||||
c.Assert(template.HTML(RenderedString("Hugo")), qt.Equals, template.HTML("Hugo"))
|
||||
c.Assert(cast.ToString(HTML("Hugo")), qt.Equals, "Hugo")
|
||||
c.Assert(template.HTML(HTML("Hugo")), qt.Equals, template.HTML("Hugo"))
|
||||
}
|
||||
|
||||
+10
-2
@@ -107,12 +107,20 @@ func Unwrapv(v any) any {
|
||||
return v
|
||||
}
|
||||
|
||||
// LowHigh is typically used to represent a slice boundary.
|
||||
type LowHigh struct {
|
||||
// LowHigh represents a byte or slice boundary.
|
||||
type LowHigh[S ~[]byte | string] struct {
|
||||
Low int
|
||||
High int
|
||||
}
|
||||
|
||||
func (l LowHigh[S]) IsZero() bool {
|
||||
return l.Low < 0 || (l.Low == 0 && l.High == 0)
|
||||
}
|
||||
|
||||
func (l LowHigh[S]) Value(source S) S {
|
||||
return source[l.Low:l.High]
|
||||
}
|
||||
|
||||
// This is only used for debugging purposes.
|
||||
var InvocationCounter atomic.Int64
|
||||
|
||||
|
||||
@@ -27,3 +27,25 @@ func TestKeyValues(t *testing.T) {
|
||||
c.Assert(kv.KeyString(), qt.Equals, "key")
|
||||
c.Assert(kv.Values, qt.DeepEquals, []any{"a1", "a2"})
|
||||
}
|
||||
|
||||
func TestLowHigh(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
lh := LowHigh[string]{
|
||||
Low: 2,
|
||||
High: 10,
|
||||
}
|
||||
|
||||
s := "abcdefghijklmnopqrstuvwxyz"
|
||||
c.Assert(lh.IsZero(), qt.IsFalse)
|
||||
c.Assert(lh.Value(s), qt.Equals, "cdefghij")
|
||||
|
||||
lhb := LowHigh[[]byte]{
|
||||
Low: 2,
|
||||
High: 10,
|
||||
}
|
||||
|
||||
sb := []byte(s)
|
||||
c.Assert(lhb.IsZero(), qt.IsFalse)
|
||||
c.Assert(lhb.Value(sb), qt.DeepEquals, []byte("cdefghij"))
|
||||
}
|
||||
|
||||
@@ -809,7 +809,7 @@ func (c *Configs) Init() error {
|
||||
}
|
||||
|
||||
if len(c.Modules) == 0 {
|
||||
return errors.New("no modules loaded (ned at least the main module)")
|
||||
return errors.New("no modules loaded (need at least the main module)")
|
||||
}
|
||||
|
||||
// Apply default project mounts.
|
||||
|
||||
@@ -103,31 +103,7 @@ suffixes = ["html", "xhtml"]
|
||||
b.Assert(contentTypes.Markdown.Suffixes(), qt.DeepEquals, []string{"md", "mdown", "markdown"})
|
||||
}
|
||||
|
||||
func TestPaginationConfigOld(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
[languages.en]
|
||||
weight = 1
|
||||
paginatePath = "page-en"
|
||||
|
||||
[languages.de]
|
||||
weight = 2
|
||||
paginatePath = "page-de"
|
||||
paginate = 20
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
confEn := b.H.Sites[0].Conf.Pagination()
|
||||
confDe := b.H.Sites[1].Conf.Pagination()
|
||||
|
||||
b.Assert(confEn.Path, qt.Equals, "page-en")
|
||||
b.Assert(confEn.PagerSize, qt.Equals, 10)
|
||||
b.Assert(confDe.Path, qt.Equals, "page-de")
|
||||
b.Assert(confDe.PagerSize, qt.Equals, 20)
|
||||
}
|
||||
|
||||
func TestPaginationConfigNew(t *testing.T) {
|
||||
func TestPaginationConfig(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
[languages.en]
|
||||
|
||||
+17
-13
@@ -64,7 +64,7 @@ func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) {
|
||||
return nil, fmt.Errorf("failed to create config from result: %w", err)
|
||||
}
|
||||
|
||||
moduleConfig, modulesClient, err := l.loadModules(configs)
|
||||
moduleConfig, modulesClient, err := l.loadModules(configs, d.IgnoreModuleDoesNotExist)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load modules: %w", err)
|
||||
}
|
||||
@@ -116,6 +116,9 @@ type ConfigSourceDescriptor struct {
|
||||
|
||||
// Defaults to os.Environ if not set.
|
||||
Environ []string
|
||||
|
||||
// If set, this will be used to ignore the module does not exist error.
|
||||
IgnoreModuleDoesNotExist bool
|
||||
}
|
||||
|
||||
func (d ConfigSourceDescriptor) configFilenames() []string {
|
||||
@@ -453,7 +456,7 @@ func (l *configLoader) loadConfigMain(d ConfigSourceDescriptor) (config.LoadConf
|
||||
return res, l.ModulesConfig, err
|
||||
}
|
||||
|
||||
func (l *configLoader) loadModules(configs *Configs) (modules.ModulesConfig, *modules.Client, error) {
|
||||
func (l *configLoader) loadModules(configs *Configs, ignoreModuleDoesNotExist bool) (modules.ModulesConfig, *modules.Client, error) {
|
||||
bcfg := configs.LoadingInfo.BaseConfig
|
||||
conf := configs.Base
|
||||
workingDir := bcfg.WorkingDir
|
||||
@@ -487,17 +490,18 @@ func (l *configLoader) loadModules(configs *Configs) (modules.ModulesConfig, *mo
|
||||
}
|
||||
|
||||
modulesClient := modules.NewClient(modules.ClientConfig{
|
||||
Fs: l.Fs,
|
||||
Logger: l.Logger,
|
||||
Exec: ex,
|
||||
HookBeforeFinalize: hook,
|
||||
WorkingDir: workingDir,
|
||||
ThemesDir: themesDir,
|
||||
PublishDir: publishDir,
|
||||
Environment: l.Environment,
|
||||
CacheDir: conf.Caches.CacheDirModules(),
|
||||
ModuleConfig: conf.Module,
|
||||
IgnoreVendor: ignoreVendor,
|
||||
Fs: l.Fs,
|
||||
Logger: l.Logger,
|
||||
Exec: ex,
|
||||
HookBeforeFinalize: hook,
|
||||
WorkingDir: workingDir,
|
||||
ThemesDir: themesDir,
|
||||
PublishDir: publishDir,
|
||||
Environment: l.Environment,
|
||||
CacheDir: conf.Caches.CacheDirModules(),
|
||||
ModuleConfig: conf.Module,
|
||||
IgnoreVendor: ignoreVendor,
|
||||
IgnoreModuleDoesNotExist: ignoreModuleDoesNotExist,
|
||||
})
|
||||
|
||||
moduleConfig, err := modulesClient.Collect()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ or site.Language.LanguageCode }}" dir="{{ or site.Language.LanguageDirection `ltr` }}">
|
||||
<html lang="{{ site.Language.LanguageCode }}" dir="{{ or site.Language.LanguageDirection `ltr` }}">
|
||||
<head>
|
||||
{{ partial "head.html" . }}
|
||||
</head>
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
{{ define "main" }}
|
||||
{{ $section_to_display := (.Site.Taxonomies.categories.fundamentals).Pages | lang.Merge (.Sites.First.Taxonomies.categories.fundamentals).Pages }}
|
||||
{{ $section_to_display := (.Site.Taxonomies.categories.fundamentals).Pages | lang.Merge (.Sites.Default.Taxonomies.categories.fundamentals).Pages }}
|
||||
{{ partial "pagelayout.html" (dict "context" . "section_to_display" $section_to_display ) }}
|
||||
{{ end }}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{ define "main" }}
|
||||
{{ $paginator := .Paginate (.Pages | lang.Merge (where .Sites.First.RegularPages "Section" .Section)) }}
|
||||
{{ $paginator := .Paginate (.Pages | lang.Merge (where .Sites.Default.RegularPages "Section" .Section)) }}
|
||||
{{ $section_to_display := .Sections | default $paginator.Pages }}
|
||||
{{ partial "pagelayout.html" (dict "context" . "section_to_display" $section_to_display ) }}
|
||||
{{ end }}
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
{{ $text := `
|
||||
Most of the commands for **Hugo Modules** require a newer version of Go installed (see https://golang.org/dl/) and the relevant VCS client (e.g. Git, see https://git-scm.com/downloads/ ). If you have an "older" site running on Netlify, you may have to set GO_VERSION to 1.12 in your Environment settings.
|
||||
Most of the commands for **Hugo Modules** require a newer version (>= 1.18) of Go installed (see https://golang.org/dl/) and the relevant VCS client (e.g. Git, see https://git-scm.com/downloads/ ).
|
||||
If you have an "older" site running on Netlify, you may have to set GO_VERSION to 1.19 or newer in your Environment settings.
|
||||
|
||||
For more information about Go Modules, see:
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
# github.com/gohugoio/gohugoioTheme v0.0.0-20240728210410-d42c342ce472
|
||||
# github.com/gohugoio/gohugoioTheme v0.0.0-20240815082608-66ccd383a90f
|
||||
|
||||
@@ -49,7 +49,7 @@ toc: true
|
||||
: Leverage the embedded Markdown extensions to create tables, definition lists, footnotes, task lists, inserted text, mark text, subscripts, superscripts, and more.
|
||||
|
||||
[Markdown render hooks]
|
||||
: Override the conversion of Markdown to HTML when rendering fenced code blocks, headings, images, and links. For example, render every standalone image as an HTML `figure` element.
|
||||
: Override the conversion of Markdown to HTML when rendering blockquotes, fenced code blocks, headings, images, links, and tables. For example, render every standalone image as an HTML `figure` element.
|
||||
|
||||
[Diagrams]
|
||||
: Use fenced code blocks and Markdown render hooks to include diagrams in your content.
|
||||
|
||||
@@ -70,6 +70,7 @@ hugo [flags]
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [hugo build](/commands/hugo_build/) - build builds your site
|
||||
* [hugo completion](/commands/hugo_completion/) - Generate the autocompletion script for the specified shell
|
||||
* [hugo config](/commands/hugo_config/) - Print the site configuration
|
||||
* [hugo convert](/commands/hugo_convert/) - Convert your content to different formats
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
---
|
||||
title: "hugo build"
|
||||
slug: hugo_build
|
||||
url: /commands/hugo_build/
|
||||
---
|
||||
## hugo build
|
||||
|
||||
build builds your site
|
||||
|
||||
### Synopsis
|
||||
|
||||
build is the main command, used to build your Hugo site.
|
||||
|
||||
Hugo is a Fast and Flexible Static Site Generator
|
||||
built with love by spf13 and friends in Go.
|
||||
|
||||
Complete documentation is available at https://gohugo.io/.
|
||||
|
||||
```
|
||||
hugo build [flags]
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
-D, --buildDrafts include content marked as draft
|
||||
-E, --buildExpired include expired content
|
||||
-F, --buildFuture include content with publishdate in the future
|
||||
--cacheDir string filesystem path to cache directory
|
||||
--cleanDestinationDir remove files from destination not found in static directories
|
||||
--clock string set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00
|
||||
--config string config file (default is hugo.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
--debug debug output
|
||||
-d, --destination string filesystem path to write files to
|
||||
--disableKinds strings disable different kind of pages (home, RSS etc.)
|
||||
--enableGitInfo add Git revision, date, author, and CODEOWNERS info to the pages
|
||||
-e, --environment string build environment
|
||||
--forceSyncStatic copy all files when static is changed.
|
||||
--gc enable to run some cleanup tasks (remove unused cache files) after the build
|
||||
-h, --help help for build
|
||||
--ignoreCache ignores the cache directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
-l, --layoutDir string filesystem path to layout directory
|
||||
--logLevel string log level (debug|info|warn|error)
|
||||
--minify minify any supported output format (HTML, XML etc.)
|
||||
--noBuildLock don't create .hugo_build.lock file
|
||||
--noChmod don't sync permission mode of files
|
||||
--noTimes don't sync modification time of files
|
||||
--panicOnWarning panic on first WARNING log
|
||||
--poll string set this to a poll interval, e.g --poll 700ms, to use a poll based approach to watch for file system changes
|
||||
--printI18nWarnings print missing translations
|
||||
--printMemoryUsage print memory usage to screen at intervals
|
||||
--printPathWarnings print warnings on duplicate target paths etc.
|
||||
--printUnusedTemplates print warnings on unused templates.
|
||||
--quiet build in quiet mode
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-M, --renderToMemory render to memory (mostly useful when running the server)
|
||||
-s, --source string filesystem path to read files relative from
|
||||
--templateMetrics display metrics about template executions
|
||||
--templateMetricsHints calculate some improvement hints when combined with --templateMetrics
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
--themesDir string filesystem path to themes directory
|
||||
--trace file write trace to file (not useful in general)
|
||||
-v, --verbose verbose output
|
||||
-w, --watch watch filesystem for changes and recreate as needed
|
||||
```
|
||||
|
||||
### SEE ALSO
|
||||
|
||||
* [hugo](/commands/hugo/) - hugo builds your site
|
||||
|
||||
@@ -125,7 +125,7 @@ Set any [front matter field] in the map passed to the [`AddPage`](#addpage) meth
|
||||
|
||||
This table describes the fields most commonly passed to the `AddPage` method.
|
||||
|
||||
Key|Descripion|Required
|
||||
Key|Description|Required
|
||||
:--|:--|:-:
|
||||
`content.mediaType`|The content [media type]. Default is `text/markdown`. See [content formats] for examples.|
|
||||
`content.value`|The content value as a string.|
|
||||
@@ -148,7 +148,7 @@ When setting the `path`, Hugo transforms the given string to a logical path. For
|
||||
|
||||
Construct the map passed to the [`AddResource`](#addresource) method using the fields below.
|
||||
|
||||
Key|Descripion|Required
|
||||
Key|Description|Required
|
||||
:--|:--|:-:
|
||||
`content.mediaType`|The content [media type].|:heavy_check_mark:
|
||||
`content.value`|The content value as a string or resource.|:heavy_check_mark:
|
||||
|
||||
@@ -21,15 +21,22 @@ This is the default language configuration:
|
||||
|
||||
In the above, `en` is the language key.
|
||||
|
||||
{{% note %}}
|
||||
Each language key must conform to the syntax described in [RFC 5646]. You must use hyphens to separate subtags. For example:
|
||||
|
||||
Language keys must conform to the syntax described in [RFC 5646]. For example:
|
||||
|
||||
- `en`
|
||||
- `en-GB`
|
||||
- `pt-BR`
|
||||
- `en-US`
|
||||
|
||||
Artificial languages with private use subtags as defined in [RFC 5646 § 2.2.7] are also supported. Omit the `art-x-` prefix from the language key. For example:
|
||||
|
||||
- `hugolang`
|
||||
|
||||
{{% note %}}
|
||||
Private use subtags must not exceed 8 alphanumeric characters.
|
||||
{{% /note %}}
|
||||
|
||||
[RFC 5646]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.1
|
||||
{{% /note %}}
|
||||
[RFC 5646 § 2.2.7]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.2.7
|
||||
|
||||
This is an example of a site configuration for a multilingual project. Any key not defined in a `languages` object will fall back to the global value in the root of your site configuration.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Page resources
|
||||
description: Page resources -- images, other pages, documents, etc. -- have page-relative URLs and their own metadata.
|
||||
description: Use page resources to logically associate assets with a page.
|
||||
categories: [content management]
|
||||
keywords: [bundle,content,resources]
|
||||
menu:
|
||||
@@ -37,82 +37,83 @@ content
|
||||
└── index.md (root of page bundle)
|
||||
```
|
||||
|
||||
## Properties
|
||||
## Examples
|
||||
|
||||
ResourceType
|
||||
: The main type of the resource's [Media Type](/templates/output-formats/#media-types). For example, a file of MIME type `image/jpeg` has the ResourceType `image`. A `Page` will have `ResourceType` with value `page`.
|
||||
Use any of these methods on a `Page` object to capture page resources:
|
||||
|
||||
Name
|
||||
: Default value is the file name (relative to the owning page). Can be set in front matter.
|
||||
- [`Resources.ByType`]
|
||||
- [`Resources.Get`]
|
||||
- [`Resources.GetMatch`]
|
||||
- [`Resources.Match`]
|
||||
|
||||
Title
|
||||
: Default value is the same as `.Name`. Can be set in front matter.
|
||||
Once you have captured a resource, use any of the applicable [`Resource`] methods to return a value or perform an action.
|
||||
|
||||
Permalink
|
||||
: The absolute URL to the resource. Resources of type `page` will have no value.
|
||||
[`Resource`]: /methods/resource
|
||||
[`Resources.ByType`]: /methods/page/resources#bytype
|
||||
[`Resources.GetMatch`]: /methods/page/resources#getmatch
|
||||
[`Resources.Get`]: /methods/page/resources#get
|
||||
[`Resources.Match`]: /methods/page/resources#match
|
||||
|
||||
RelPermalink
|
||||
: The relative URL to the resource. Resources of type `page` will have no value.
|
||||
The following examples assume this content structure:
|
||||
|
||||
Content
|
||||
: The content of the resource itself. For most resources, this returns a string
|
||||
with the contents of the file. Use this to create inline resources.
|
||||
```text
|
||||
content/
|
||||
└── example/
|
||||
├── data/
|
||||
│ └── books.json <-- page resource
|
||||
├── images/
|
||||
│ ├── a.jpg <-- page resource
|
||||
│ └── b.jpg <-- page resource
|
||||
├── snippets/
|
||||
│ └── text.md <-- page resource
|
||||
└── index.md
|
||||
```
|
||||
|
||||
Render a single image, and throw an error if the file does not exist:
|
||||
|
||||
```go-html-template
|
||||
{{ with .Resources.GetMatch "script.js" }}
|
||||
<script>{{ .Content | safeJS }}</script>
|
||||
{{ end }}
|
||||
|
||||
{{ with .Resources.GetMatch "style.css" }}
|
||||
<style>{{ .Content | safeCSS }}</style>
|
||||
{{ end }}
|
||||
|
||||
{{ with .Resources.GetMatch "img.png" }}
|
||||
<img src="data:{{ .MediaType.Type }};base64,{{ .Content | base64Encode }}">
|
||||
{{ $path := "images/a.jpg" }}
|
||||
{{ with .Resources.Get $path }}
|
||||
<img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}" alt="">
|
||||
{{ else }}
|
||||
{{ errorf "Unable to get page resource %q" $path }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
MediaType.Type
|
||||
: The media type (formerly known as a MIME type) of the resource (e.g., `image/jpeg`).
|
||||
|
||||
MediaType.MainType
|
||||
: The main type of the resource's media type (e.g., `image`).
|
||||
|
||||
MediaType.SubType
|
||||
: The subtype of the resource's type (e.g., `jpeg`). This may or may not correspond to the file suffix.
|
||||
|
||||
MediaType.Suffixes
|
||||
: A slice of possible file suffixes for the resource's media type (e.g., `[jpg jpeg jpe jif jfif]`).
|
||||
|
||||
## Methods
|
||||
|
||||
ByType
|
||||
: Returns the page resources of the given type.
|
||||
Render all images, resized to 300 px wide:
|
||||
|
||||
```go-html-template
|
||||
{{ .Resources.ByType "image" }}
|
||||
{{ range .Resources.ByType "image" }}
|
||||
{{ with .Resize "300x" }}
|
||||
<img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}" alt="">
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
```
|
||||
Match
|
||||
: Returns all the page resources (as a slice) whose `Name` matches the given Glob pattern ([examples](https://github.com/gobwas/glob/blob/master/readme.md)). The matching is case-insensitive.
|
||||
|
||||
Render the markdown snippet:
|
||||
|
||||
```go-html-template
|
||||
{{ .Resources.Match "images/*" }}
|
||||
{{ with .Resources.Get "snippets/text.md" }}
|
||||
{{ .Content }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
GetMatch
|
||||
: Same as `Match` but will return the first match.
|
||||
List the titles in the data file, and throw an error if the file does not exist.
|
||||
|
||||
### Pattern matching
|
||||
|
||||
```go
|
||||
// Using Match/GetMatch to find this images/sunset.jpg ?
|
||||
.Resources.Match "images/sun*" ✅
|
||||
.Resources.Match "**/sunset.jpg" ✅
|
||||
.Resources.Match "images/*.jpg" ✅
|
||||
.Resources.Match "**.jpg" ✅
|
||||
.Resources.Match "*" 🚫
|
||||
.Resources.Match "sunset.jpg" 🚫
|
||||
.Resources.Match "*sunset.jpg" 🚫
|
||||
```go-html-template
|
||||
{{ $path := "data/books.json" }}
|
||||
{{ with .Resources.Get $path }}
|
||||
{{ with . | transform.Unmarshal }}
|
||||
<p>Books:</p>
|
||||
<ul>
|
||||
{{ range . }}
|
||||
<li>{{ .title }}</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
{{ errorf "Unable to get page resource %q" $path }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
## Metadata
|
||||
@@ -124,21 +125,21 @@ Resources of type `page` get `Title` etc. from their own front matter.
|
||||
{{% /note %}}
|
||||
|
||||
name
|
||||
: Sets the value returned in `Name`.
|
||||
: (`string`) Sets the value returned in `Name`.
|
||||
|
||||
{{% note %}}
|
||||
The methods `Match`, `Get` and `GetMatch` use `Name` to match the resources.
|
||||
{{% /note %}}
|
||||
|
||||
title
|
||||
: Sets the value returned in `Title`
|
||||
: (`string`) Sets the value returned in `Title`
|
||||
|
||||
params
|
||||
: A map of custom key-value pairs.
|
||||
: (`map`) A map of custom key-value pairs.
|
||||
|
||||
### Resources metadata example
|
||||
|
||||
{{< code-toggle >}}
|
||||
{{< code-toggle file=content/example.md fm=true >}}
|
||||
title: Application
|
||||
date : 2018-01-25
|
||||
resources :
|
||||
@@ -173,7 +174,7 @@ From the example above:
|
||||
- Every docx in the bundle will receive the `word` icon.
|
||||
|
||||
{{% note %}}
|
||||
The __order matters__ --- Only the **first set** values of the `title`, `name` and `params`-**keys** will be used. Consecutive parameters will be set only for the ones not already set. In the above example, `.Params.icon` is first set to `"photo"` in `src = "documents/photo_specs.pdf"`. So that would not get overridden to `"pdf"` by the later set `src = "**.pdf"` rule.
|
||||
The order matters; only the first set values of the `title`, `name` and `params` keys will be used. Consecutive parameters will be set only for the ones not already set. In the above example, `.Params.icon` is first set to `"photo"` in `src = "documents/photo_specs.pdf"`. So that would not get overridden to `"pdf"` by the later set `src = "**.pdf"` rule.
|
||||
{{% /note %}}
|
||||
|
||||
### The `:counter` placeholder in `name` and `title`
|
||||
|
||||
@@ -92,11 +92,11 @@ Note that the `summaryLength` is an approximate number of words.
|
||||
|
||||
Each summary type has different characteristics:
|
||||
|
||||
Type|Precedence|Renders markdown|Renders shortcodes|Strips HTML tags|Wraps single lines with `<p>`
|
||||
:--|:-:|:-:|:-:|:-:|:-:
|
||||
Manual|1|:heavy_check_mark:|:heavy_check_mark:|:x:|:heavy_check_mark:
|
||||
Front matter|2|:heavy_check_mark:|:x:|:x:|:x:
|
||||
Automatic|3|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:x:
|
||||
Type|Precedence|Renders markdown|Renders shortcodes|Wraps single lines with `<p>`
|
||||
:--|:-:|:-:|:-:|:-:
|
||||
Manual|1|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:
|
||||
Front matter|2|:heavy_check_mark:|:x:|:x:
|
||||
Automatic|3|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:
|
||||
|
||||
## Rendering
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ For a complete guide to contributing to Hugo, see the [Contribution Guide].
|
||||
To build the extended edition of Hugo from source you must:
|
||||
|
||||
1. Install [Git]
|
||||
1. Install [Go] version 1.20 or later
|
||||
1. Install [Go] version 1.23.0 or later
|
||||
1. Install a C compiler, either [GCC] or [Clang]
|
||||
1. Update your `PATH` environment variable as described in the [Go documentation]
|
||||
|
||||
|
||||
@@ -2,4 +2,6 @@
|
||||
# Do not remove front matter.
|
||||
---
|
||||
|
||||
In Go templates, the falsy values are `false`, `0`, any nil pointer or interface value, and any array, slice, map, or string of length zero. Everything else is truthy.
|
||||
The falsy values are `false`, `0`, any `nil` pointer or interface value, any array, slice, map, or string of length zero, and zero `time.Time` values.
|
||||
|
||||
Everything else is truthy.
|
||||
|
||||
@@ -34,7 +34,7 @@ Use with the [`else`] statement:
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
Use `else if` to check multiple conditions.
|
||||
Use `else if` to check multiple conditions:
|
||||
|
||||
```go-html-template
|
||||
{{ $var := 12 }}
|
||||
|
||||
@@ -36,6 +36,20 @@ Use with the [`else`] statement:
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
Use `else with` to check multiple conditions:
|
||||
|
||||
```go-html-template
|
||||
{{ $v1 := 0 }}
|
||||
{{ $v2 := 42 }}
|
||||
{{ with $v1 }}
|
||||
{{ . }}
|
||||
{{ else with $v2 }}
|
||||
{{ . }} → 42
|
||||
{{ else }}
|
||||
{{ print "v1 and v2 are falsy" }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
Initialize a variable, scoped to the current block:
|
||||
|
||||
```go-html-template
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
---
|
||||
title: transform.ToMath
|
||||
description: Renders a math expression using KaTeX.
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
aliases: []
|
||||
related:
|
||||
- content-management/mathematics
|
||||
returnType: types.Result[template.HTML]
|
||||
signatures: ['transform.ToMath EXPRESSION [OPTIONS]']
|
||||
aliases: [/functions/tomath]
|
||||
toc: true
|
||||
---
|
||||
|
||||
{{< new-in "0.132.0" >}}
|
||||
|
||||
{{% note %}}
|
||||
This feature was introduced in Hugo 0.132.0 and is marked as experimental.
|
||||
|
||||
This does not mean that it's going to be removed, but this is our first use of WASI/Wasm in Hugo, and we need to see how it [works in the wild](https://github.com/gohugoio/hugo/issues/12736) before we can set it in stone.
|
||||
{{% /note %}}
|
||||
|
||||
## Arguments
|
||||
|
||||
EXPRESSION
|
||||
: The math expression to render using KaTeX.
|
||||
|
||||
OPTIONS
|
||||
: A map of zero or more options.
|
||||
|
||||
## Options
|
||||
|
||||
These are a subset of the [KaTeX options].
|
||||
|
||||
output
|
||||
: (`string`). Determines the markup language of the output. One of `html`, `mathml`, or `htmlAndMathml`. Default is `mathml`.
|
||||
|
||||
<!-- Indent to prevent spliting the description list. -->
|
||||
|
||||
With `html` and `htmlAndMathml` you must include KaTeX CSS within the `head` element of your base template. For example:
|
||||
|
||||
```html
|
||||
<head>
|
||||
...
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css" integrity="sha384-nB0miv6/jRmo5UMMR1wu3Gz6NLsoTkbqJghGIsx//Rlm+ZU03BU6SQNC66uf4l5+" crossorigin="anonymous">
|
||||
...
|
||||
</head>
|
||||
```
|
||||
|
||||
displayMode
|
||||
: (`bool`) If `true` render in display mode, else render in inline mode. Default is `false`.
|
||||
|
||||
leqno
|
||||
: (`bool`) If `true` render with the equation numbers on the left. Default is `false`.
|
||||
|
||||
fleqn
|
||||
: (`bool`) If `true` render flush left with a 2em left margin. Default is `false`.
|
||||
|
||||
minRuleThickness
|
||||
: (`float`) The minimum thickness of the fraction lines in `em`. Default is `0.04`.
|
||||
|
||||
macros
|
||||
: (`map`) A map of macros to be used in the math expression. Default is `{}`.
|
||||
|
||||
throwOnError
|
||||
: (`bool`) If `true` throw a `ParseError` when KaTeX encounters an unsupported command or invalid LaTex. See [error handling]. Default is `true`.
|
||||
|
||||
errorColor
|
||||
: (`string`) The color of the error messages expressed as an RGB [hexadecimal color]. Default is `#cc0000`.
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic
|
||||
|
||||
```go-html-template
|
||||
{{ transform.ToMath "c = \\pm\\sqrt{a^2 + b^2}" }}
|
||||
```
|
||||
|
||||
### Macros
|
||||
|
||||
```go-html-template
|
||||
{{ $macros := dict
|
||||
"\\addBar" "\\bar{#1}"
|
||||
"\\bold" "\\mathbf{#1}"
|
||||
}}
|
||||
{{ $opts := dict "macros" $macros }}
|
||||
{{ transform.ToMath "\\addBar{y} + \\bold{H}" $opts }}
|
||||
```
|
||||
|
||||
## Error handling
|
||||
|
||||
There are 3 ways to handle errors from KaTeX:
|
||||
|
||||
1. Let KaTeX throw an error and make the build fail. This is the default behavior.
|
||||
1. Handle the error in your template. See the render hook example below.
|
||||
1. Set the `throwOnError` option to `false` to make KaTeX render the expression as an error instead of throwing an error. See [options].
|
||||
|
||||
{{< code file=layouts/_default/_markup/render-passthrough-inline.html copy=true >}}
|
||||
{{ with transform.ToMath .Inner }}
|
||||
{{ with .Err }}
|
||||
{{ errorf "Failed to render KaTeX: %q. See %s" . $.Position }}
|
||||
{{ else }}
|
||||
{{ . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{- /* chomp trailing newline */ -}}
|
||||
{{< /code >}}
|
||||
|
||||
[error handling]: #error-handling
|
||||
[KaTeX options]: https://katex.org/docs/options.html
|
||||
[hexadecimal color]: https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color
|
||||
@@ -238,7 +238,7 @@ This is the default configuration for the AsciiDoc renderer:
|
||||
|
||||
###### attributes
|
||||
|
||||
(`map`) A map of key-value pairs, each a document attributes,See Asciidoctor’s [attributes].
|
||||
(`map`) A map of key-value pairs, each a document attributes. See Asciidoctor’s [attributes].
|
||||
|
||||
[attributes]: https://asciidoctor.org/docs/asciidoc-syntax-quick-reference/#attributes-and-substitutions
|
||||
|
||||
@@ -302,6 +302,51 @@ To mitigate security risks, entries in the extension array may not contain forwa
|
||||
my-attribute-name = "my value"
|
||||
{{< /code-toggle >}}
|
||||
|
||||
### AsciiDoc syntax highlighting
|
||||
|
||||
Follow the steps below to enable syntax highlighting.
|
||||
|
||||
Step 1
|
||||
: Set the `source-highlighter` attribute in your site configuration. For example:
|
||||
|
||||
{{< code-toggle file=hugo >}}
|
||||
[markup.asciidocExt.attributes]
|
||||
source-highlighter = 'rouge'
|
||||
{{< /code-toggle >}}
|
||||
|
||||
Step 2
|
||||
: Generate the highlighter CSS. For example:
|
||||
|
||||
```text
|
||||
rougify style monokai.sublime > assets/css/syntax.css
|
||||
```
|
||||
|
||||
Step 3
|
||||
: In your base template add a link to the CSS file:
|
||||
|
||||
{{< code file=layouts/_default/baseof.html >}}
|
||||
<head>
|
||||
...
|
||||
{{ with resources.Get "css/syntax.css" }}
|
||||
<link rel="stylesheet" href="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous">
|
||||
{{ end }}
|
||||
...
|
||||
</head>
|
||||
{{< /code >}}
|
||||
|
||||
Then add the code to be highlighted to your markup:
|
||||
|
||||
```text
|
||||
[#hello,ruby]
|
||||
----
|
||||
require 'sinatra'
|
||||
|
||||
get '/hi' do
|
||||
"Hello World!"
|
||||
end
|
||||
----
|
||||
```
|
||||
|
||||
### AsciiDoc troubleshooting
|
||||
|
||||
Run `hugo --logLevel debug` to examine Hugo's call to the Asciidoctor executable:
|
||||
|
||||
@@ -378,6 +378,10 @@ Module configuration see [module configuration](/hugo-modules/configuration/).
|
||||
|
||||
See [custom output formats].
|
||||
|
||||
###### page
|
||||
|
||||
See [configure page](#configure-page).
|
||||
|
||||
###### pagination
|
||||
|
||||
See [configure pagination](/templates/pagination/#configuration).
|
||||
@@ -496,6 +500,50 @@ enableemoji: true
|
||||
```
|
||||
{{% /note %}}
|
||||
|
||||
## Configure page
|
||||
|
||||
{{< new-in 0.133.0 >}}
|
||||
|
||||
These methods on a `Page` object navigate to the next or previous page within a page collection, relative to the current page:
|
||||
|
||||
- [Next](/methods/page/next/)
|
||||
- [NextInSection](/methods/page/nextinsection/)
|
||||
- [Prev](/methods/page/prev/)
|
||||
- [PrevInSection](/methods/page/previnsection/)
|
||||
|
||||
Hugo determines the _next_ and _previous_ page by sorting a page collection according to this sorting hierarchy:
|
||||
|
||||
Field|Precedence|Sort direction
|
||||
:--|:--|:--
|
||||
[`weight`]|1|descending
|
||||
[`date`]|2|descending
|
||||
[`linkTitle`]|3|descending
|
||||
[`path`]|4|descending
|
||||
|
||||
[`date`]: /methods/page/date/
|
||||
[`weight`]: /methods/page/weight/
|
||||
[`linkTitle`]: /methods/page/linktitle/
|
||||
[`path`]: /methods/page/path/
|
||||
|
||||
The sort direction in the table above corresponds to these default site configuration values:
|
||||
|
||||
{{< code-toggle config=page />}}
|
||||
|
||||
To sort all fields in ascending order:
|
||||
|
||||
{{< code-toggle file=hugo >}}
|
||||
[page]
|
||||
nextPrevInSectionSortOrder = 'asc'
|
||||
nextPrevSortOrder = 'asc'
|
||||
{{< /code-toggle >}}
|
||||
|
||||
{{% note %}}
|
||||
These settings do not apply to the [`Next`] or [`Prev`] methods on a `Pages` object.
|
||||
|
||||
[`Next`]: /methods/pages/next
|
||||
[`Prev`]: /methods/pages/next
|
||||
{{% /note %}}
|
||||
|
||||
## Configure build
|
||||
|
||||
The `build` configuration section contains global build-related configuration options.
|
||||
@@ -609,8 +657,6 @@ Setting `force=true` will make a redirect even if there is existing content in t
|
||||
|
||||
## 404 server error page {#_404-server-error-page}
|
||||
|
||||
{{< new-in 0.103.0 >}}
|
||||
|
||||
Hugo will, by default, render all 404 errors when running `hugo server` with the `404.html` template. Note that if you have already added one or more redirects to your [server configuration](#configure-server), you need to add the 404 redirect explicitly, e.g:
|
||||
|
||||
{{< code-toggle file=config/development/server >}}
|
||||
@@ -924,7 +970,6 @@ output
|
||||
|
||||
It is recommended to put coarse grained filters (e.g. for language and output format) in the excludes section, e.g.:
|
||||
|
||||
|
||||
{{< code-toggle file=hugo >}}
|
||||
[segments.segment1]
|
||||
[[segments.segment1.excludes]]
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
cascade:
|
||||
_build:
|
||||
list: never
|
||||
publishResources: false
|
||||
render: never
|
||||
---
|
||||
|
||||
<!--
|
||||
Files within this headless branch bundle are Markdown snippets. Each file must contain front matter delimiters, though front matter fields are not required.
|
||||
|
||||
Include the rendered content using the "include" shortcode.
|
||||
-->
|
||||
@@ -1,37 +0,0 @@
|
||||
---
|
||||
# Do not remove front matter.
|
||||
---
|
||||
|
||||
The `Next` and `Prev` methods on a `Pages` object are more flexible than the `Next` and `Prev` methods on a `Page` object.
|
||||
|
||||
||Page collection|Custom sort order
|
||||
:--|:--|:-:
|
||||
[`PAGES.Next`] and [`PAGES.Prev`]|locally defined|✔️
|
||||
[`PAGE.Next`] and [`PAGE.Prev`]|globally defined|❌
|
||||
|
||||
[`PAGES.Next`]: /methods/pages/next/
|
||||
[`PAGES.Prev`]: /methods/pages/prev/
|
||||
[`PAGE.Next`]: /methods/page/next/
|
||||
[`PAGE.Prev`]: /methods/page/prev/
|
||||
|
||||
locally defined
|
||||
: Build the page collection every time you call `PAGES.Next` and `PAGES.Prev`. Navigation between pages is relative to the current page's position within the local collection, independent of the global collection.
|
||||
|
||||
With a local collection, the navigation sort order is the same as the collection sort order.
|
||||
|
||||
globally defined
|
||||
: Build the page collection once, on a list page. Navigation between pages is relative to the current page's position within the global collection.
|
||||
|
||||
With a global collection, the navigation sort order is fixed, using Hugo's default sort order. In order of precedence:
|
||||
|
||||
1. Page [weight]
|
||||
2. Page [date] (descending)
|
||||
3. Page [linkTitle], falling back to page [title]
|
||||
4. Page file path if the page is backed by a file
|
||||
|
||||
For example, with a global collection sorted by title, the navigation sort order will use Hugo's default sort order. This is probably not what you want or expect. For this reason, the `Next` and `Prev` methods on a `Pages` object are generally a better choice.
|
||||
|
||||
[date]: /methods/page/date/
|
||||
[weight]: /methods/page/weight/
|
||||
[linkTitle]: /methods/page/linktitle/
|
||||
[title]: /methods/page/title/
|
||||
@@ -5,6 +5,8 @@ categories: []
|
||||
keywords: []
|
||||
action:
|
||||
related:
|
||||
- methods/page/Summary
|
||||
- methods/page/ContentWithoutSummary
|
||||
- methods/page/RawContent
|
||||
- methods/page/Plain
|
||||
- methods/page/PlainWords
|
||||
@@ -13,9 +15,7 @@ action:
|
||||
signatures: [PAGE.Content]
|
||||
---
|
||||
|
||||
The `Content` method on a `Page` object renders Markdown and shortcodes to HTML. The content does not include front matter.
|
||||
|
||||
[shortcodes]: /getting-started/glossary/#shortcode
|
||||
The `Content` method on a `Page` object renders Markdown and shortcodes to HTML.
|
||||
|
||||
```go-html-template
|
||||
{{ .Content }}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
title: ContentWithoutSummary
|
||||
description: Returns the rendered content of the given page, excluding the content summary.
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
related:
|
||||
- methods/page/Content
|
||||
- methods/page/Summary
|
||||
- methods/page/RawContent
|
||||
- methods/page/Plain
|
||||
- methods/page/PlainWords
|
||||
- methods/page/RenderShortcodes
|
||||
returnType: template.HTML
|
||||
signatures: [PAGE.ContentWithoutSummary]
|
||||
---
|
||||
|
||||
{{< new-in 0.134.0 >}}
|
||||
|
||||
Applicable when using manual or automatic [content summaries], the `ContentWithoutSummary` method on a `Page` object renders Markdown and shortcodes to HTML, excluding the content summary from the result.
|
||||
|
||||
[content summaries]: /content-management/summaries/#manual-summary
|
||||
|
||||
```go-html-template
|
||||
{{ .ContentWithoutSummary }}
|
||||
```
|
||||
|
||||
The `ContentWithoutSummary` method returns an empty string if you define the content summary in front matter.
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: Next
|
||||
description: Returns the next page in a global page collection, relative to the given page.
|
||||
description: Returns the next page in a site's collection of regular pages, relative to the current page.
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
@@ -12,42 +12,6 @@ action:
|
||||
- methods/pages/Prev
|
||||
returnType: page.Page
|
||||
signatures: [PAGE.Next]
|
||||
toc: true
|
||||
---
|
||||
|
||||
The behavior of the `Prev` and `Next` methods on a `Page` object is probably the reverse of what you expect.
|
||||
|
||||
With this content structure:
|
||||
|
||||
```text
|
||||
content/
|
||||
├── pages/
|
||||
│ ├── _index.md
|
||||
│ ├── page-1.md <-- front matter: weight = 10
|
||||
│ ├── page-2.md <-- front matter: weight = 20
|
||||
│ └── page-3.md <-- front matter: weight = 30
|
||||
└── _index.md
|
||||
```
|
||||
|
||||
When you visit page-2:
|
||||
|
||||
- The `Prev` method points to page-3
|
||||
- The `Next` method points to page-1
|
||||
|
||||
{{% note %}}
|
||||
Use the opposite label in your navigation links as shown in the example below.
|
||||
{{% /note %}}
|
||||
|
||||
```go-html-template
|
||||
{{ with .Next }}
|
||||
<a href="{{ .RelPermalink }}">Prev</a>
|
||||
{{ end }}
|
||||
|
||||
{{ with .Prev }}
|
||||
<a href="{{ .RelPermalink }}">Next</a>
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
## Compare to Pages methods
|
||||
|
||||
{{% include "methods/_common/next-prev-on-page-vs-next-prev-on-pages.md" %}}
|
||||
{{% include "methods/page/_common/next-and-prev.md" %}}
|
||||
|
||||
@@ -1,71 +1,15 @@
|
||||
---
|
||||
title: NextInSection
|
||||
description: Returns the next page within a section, relative to the given page.
|
||||
description: Returns the next regular page in a section, relative to the given page.
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
related:
|
||||
- methods/page/PrevInSection
|
||||
- methods/page/Next
|
||||
- methods/page/Prev
|
||||
- methods/pages/Next
|
||||
- methods/pages/Prev
|
||||
returnType: page.Page
|
||||
signatures: [PAGE.NextInSection]
|
||||
---
|
||||
|
||||
The behavior of the `PrevInSection` and `NextInSection` methods on a `Page` object is probably the reverse of what you expect.
|
||||
|
||||
With this content structure:
|
||||
|
||||
```text
|
||||
content/
|
||||
├── books/
|
||||
│ ├── _index.md
|
||||
│ ├── book-1.md
|
||||
│ ├── book-2.md
|
||||
│ └── book-3.md
|
||||
├── films/
|
||||
│ ├── _index.md
|
||||
│ ├── film-1.md
|
||||
│ ├── film-2.md
|
||||
│ └── film-3.md
|
||||
└── _index.md
|
||||
```
|
||||
|
||||
When you visit book-2:
|
||||
|
||||
- The `PrevInSection` method points to book-3
|
||||
- The `NextInSection` method points to book-1
|
||||
|
||||
{{% note %}}
|
||||
Use the opposite label in your navigation links as shown in the example below.
|
||||
{{% /note %}}
|
||||
|
||||
```go-html-template
|
||||
{{ with .NextInSection }}
|
||||
<a href="{{ .RelPermalink }}">Previous in section</a>
|
||||
{{ end }}
|
||||
|
||||
{{ with .PrevInSection }}
|
||||
<a href="{{ .RelPermalink }}">Next in section</a>
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
{{% note %}}
|
||||
The navigation sort order may be different than the page collection sort order.
|
||||
{{% /note %}}
|
||||
|
||||
With the `PrevInSection` and `NextInSection` methods, the navigation sort order is fixed, using Hugo’s default sort order. In order of precedence:
|
||||
|
||||
1. Page [weight]
|
||||
2. Page [date] (descending)
|
||||
3. Page [linkTitle], falling back to page [title]
|
||||
4. Page file path if the page is backed by a file
|
||||
|
||||
For example, with a page collection sorted by title, the navigation sort order will use Hugo’s default sort order. This is probably not what you want or expect. For this reason, the Next and Prev methods on a Pages object are generally a better choice.
|
||||
|
||||
[date]: /methods/page/date/
|
||||
[weight]: /methods/page/weight/
|
||||
[linkTitle]: /methods/page/linktitle/
|
||||
[title]: /methods/page/title/
|
||||
{{% include "methods/page/_common/nextinsection-and-previnsection.md" %}}
|
||||
|
||||
@@ -6,6 +6,8 @@ keywords: []
|
||||
action:
|
||||
related:
|
||||
- methods/page/Content
|
||||
- methods/page/Summary
|
||||
- methods/page/ContentWithoutSummary
|
||||
- methods/page/RawContent
|
||||
- methods/page/PlainWords
|
||||
- methods/page/RenderShortcodes
|
||||
@@ -13,7 +15,7 @@ action:
|
||||
signatures: [PAGE.Plain]
|
||||
---
|
||||
|
||||
The `Plain` method on a `Page` object renders Markdown and [shortcodes] to HTML, then strips the HTML [tags]. It does not strip HTML [entities]. The plain content does not include front matter.
|
||||
The `Plain` method on a `Page` object renders Markdown and [shortcodes] to HTML, then strips the HTML [tags]. It does not strip HTML [entities].
|
||||
|
||||
To prevent Go's [html/template] package from escaping HTML entities, pass the result through the [`htmlUnescape`] function.
|
||||
|
||||
|
||||
@@ -6,8 +6,11 @@ keywords: []
|
||||
action:
|
||||
related:
|
||||
- methods/page/Content
|
||||
- methods/page/Summary
|
||||
- methods/page/ContentWithoutSummary
|
||||
- methods/page/RawContent
|
||||
- methods/page/Plain
|
||||
- methods/page/RenderShortcodes
|
||||
returnType: '[]string'
|
||||
signatures: [PAGE.PlainWords]
|
||||
---
|
||||
|
||||
@@ -1,53 +1,17 @@
|
||||
---
|
||||
title: Prev
|
||||
description: Returns the previous page in a global page collection, relative to the given page.
|
||||
description: Returns the previous page in a site's collection of regular pages, relative to the current page.
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
related:
|
||||
- methods/page/Next
|
||||
- methods/page/PrevInSection
|
||||
- methods/page/NextInSection
|
||||
- methods/pages/Prev
|
||||
- methods/page/PrevInSection
|
||||
- methods/pages/Next
|
||||
- methods/pages/Prev
|
||||
returnType: page.Page
|
||||
signatures: [PAGE.Prev]
|
||||
toc: true
|
||||
---
|
||||
|
||||
The behavior of the `Prev` and `Next` methods on a `Page` object is probably the reverse of what you expect.
|
||||
|
||||
With this content structure:
|
||||
|
||||
```text
|
||||
content/
|
||||
├── pages/
|
||||
│ ├── _index.md
|
||||
│ ├── page-1.md <-- front matter: weight = 10
|
||||
│ ├── page-2.md <-- front matter: weight = 20
|
||||
│ └── page-3.md <-- front matter: weight = 30
|
||||
└── _index.md
|
||||
```
|
||||
|
||||
When you visit page-2:
|
||||
|
||||
- The `Prev` method points to page-3
|
||||
- The `Next` method points to page-1
|
||||
|
||||
{{% note %}}
|
||||
Use the opposite label in your navigation links as shown in the example below.
|
||||
{{% /note %}}
|
||||
|
||||
```go-html-template
|
||||
{{ with .Next }}
|
||||
<a href="{{ .RelPermalink }}">Prev</a>
|
||||
{{ end }}
|
||||
|
||||
{{ with .Prev }}
|
||||
<a href="{{ .RelPermalink }}">Next</a>
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
## Compare to Pages methods
|
||||
|
||||
{{% include "methods/_common/next-prev-on-page-vs-next-prev-on-pages.md" %}}
|
||||
{{% include "methods/page/_common/next-and-prev.md" %}}
|
||||
|
||||
@@ -1,72 +1,15 @@
|
||||
---
|
||||
title: PrevInSection
|
||||
description: Returns the previous page within a section, relative to the given page.
|
||||
description: Returns the previous regular page in a section, relative to the given page.
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
related:
|
||||
- methods/page/NextInSection
|
||||
- methods/page/Next
|
||||
- methods/pages/Next
|
||||
- methods/page/Prev
|
||||
- methods/pages/Prev
|
||||
returnType: page.Page
|
||||
signatures: [PAGE.PrevInSection]
|
||||
---
|
||||
|
||||
|
||||
The behavior of the `PrevInSection` and `NextInSection` methods on a `Page` object is probably the reverse of what you expect.
|
||||
|
||||
With this content structure:
|
||||
|
||||
```text
|
||||
content/
|
||||
├── books/
|
||||
│ ├── _index.md
|
||||
│ ├── book-1.md
|
||||
│ ├── book-2.md
|
||||
│ └── book-3.md
|
||||
├── films/
|
||||
│ ├── _index.md
|
||||
│ ├── film-1.md
|
||||
│ ├── film-2.md
|
||||
│ └── film-3.md
|
||||
└── _index.md
|
||||
```
|
||||
|
||||
When you visit book-2:
|
||||
|
||||
- The `PrevInSection` method points to book-3
|
||||
- The `NextInSection` method points to book-1
|
||||
|
||||
{{% note %}}
|
||||
Use the opposite label in your navigation links as shown in the example below.
|
||||
{{% /note %}}
|
||||
|
||||
```go-html-template
|
||||
{{ with .NextInSection }}
|
||||
<a href="{{ .RelPermalink }}">Previous in section</a>
|
||||
{{ end }}
|
||||
|
||||
{{ with .PrevInSection }}
|
||||
<a href="{{ .RelPermalink }}">Next in section</a>
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
{{% note %}}
|
||||
The navigation sort order may be different than the page collection sort order.
|
||||
{{% /note %}}
|
||||
|
||||
With the `PrevInSection` and `NextInSection` methods, the navigation sort order is fixed, using Hugo’s default sort order. In order of precedence:
|
||||
|
||||
1. Page [weight]
|
||||
2. Page [date] (descending)
|
||||
3. Page [linkTitle], falling back to page [title]
|
||||
4. Page file path if the page is backed by a file
|
||||
|
||||
For example, with a page collection sorted by title, the navigation sort order will use Hugo’s default sort order. This is probably not what you want or expect. For this reason, the Next and Prev methods on a Pages object are generally a better choice.
|
||||
|
||||
[date]: /methods/page/date/
|
||||
[weight]: /methods/page/weight/
|
||||
[linkTitle]: /methods/page/linktitle/
|
||||
[title]: /methods/page/title/
|
||||
{{% include "methods/page/_common/nextinsection-and-previnsection.md" %}}
|
||||
|
||||
@@ -6,6 +6,8 @@ keywords: []
|
||||
action:
|
||||
related:
|
||||
- methods/page/Content
|
||||
- methods/page/Summary
|
||||
- methods/page/ContentWithoutSummary
|
||||
- methods/page/Plain
|
||||
- methods/page/PlainWords
|
||||
- methods/page/RenderShortcodes
|
||||
|
||||
@@ -5,11 +5,13 @@ categories: []
|
||||
keywords: []
|
||||
action:
|
||||
related:
|
||||
- methods/page/RenderString
|
||||
- methods/page/Content
|
||||
- methods/page/Summary
|
||||
- methods/page/ContentWithoutSummary
|
||||
- methods/page/RawContent
|
||||
- methods/page/Plain
|
||||
- methods/page/PlainWords
|
||||
- methods/page/RenderString
|
||||
returnType: template.HTML
|
||||
signatures: [PAGE.RenderShortcodes]
|
||||
toc: true
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
# Do not remove front matter.
|
||||
---
|
||||
|
||||
Hugo determines the _next_ and _previous_ page by sorting the site's collection of regular pages according to this sorting hierarchy:
|
||||
|
||||
Field|Precedence|Sort direction
|
||||
:--|:--|:--
|
||||
[`weight`]|1|descending
|
||||
[`date`]|2|descending
|
||||
[`linkTitle`]|3|descending
|
||||
[`path`]|4|descending
|
||||
|
||||
[`date`]: /methods/page/date/
|
||||
[`weight`]: /methods/page/weight/
|
||||
[`linkTitle`]: /methods/page/linktitle/
|
||||
[`path`]: /methods/page/path/
|
||||
|
||||
The sorted page collection used to determine the _next_ and _previous_ page is independent of other page collections, which may lead to unexpected behavior.
|
||||
|
||||
For example, with this content structure:
|
||||
|
||||
```text
|
||||
content/
|
||||
├── pages/
|
||||
│ ├── _index.md
|
||||
│ ├── page-1.md <-- front matter: weight = 10
|
||||
│ ├── page-2.md <-- front matter: weight = 20
|
||||
│ └── page-3.md <-- front matter: weight = 30
|
||||
└── _index.md
|
||||
```
|
||||
|
||||
And these templates:
|
||||
|
||||
{{< code file=layouts/_default/list.html >}}
|
||||
{{ range .Pages.ByWeight }}
|
||||
<h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
|
||||
{{ end }}
|
||||
{{< /code >}}
|
||||
|
||||
{{< code file=layouts/_default/single.html >}}
|
||||
{{ with .Prev }}
|
||||
<a href="{{ .RelPermalink }}">Previous</a>
|
||||
{{ end }}
|
||||
|
||||
{{ with .Next }}
|
||||
<a href="{{ .RelPermalink }}">Next</a>
|
||||
{{ end }}
|
||||
{{< /code >}}
|
||||
|
||||
When you visit page-2:
|
||||
|
||||
- The `Prev` method points to page-3
|
||||
- The `Next` method points to page-1
|
||||
|
||||
To reverse the meaning of _next_ and _previous_ you can change the sort direction in your [site configuration], or use the [`Next`] and [`Prev`] methods on a `Pages` object for more flexibility.
|
||||
|
||||
[site configuration]: getting-started/configuration/#configure-page
|
||||
[`Next`]: /methods/pages/prev
|
||||
[`Prev`]: /methods/pages/prev
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
# Do not remove front matter.
|
||||
---
|
||||
|
||||
Hugo determines the _next_ and _previous_ page by sorting the current section's regular pages according to this sorting hierarchy:
|
||||
|
||||
Field|Precedence|Sort direction
|
||||
:--|:--|:--
|
||||
[`weight`]|1|descending
|
||||
[`date`]|2|descending
|
||||
[`linkTitle`]|3|descending
|
||||
[`path`]|4|descending
|
||||
|
||||
[`date`]: /methods/page/date/
|
||||
[`weight`]: /methods/page/weight/
|
||||
[`linkTitle`]: /methods/page/linktitle/
|
||||
[`path`]: /methods/page/path/
|
||||
|
||||
The sorted page collection used to determine the _next_ and _previous_ page is independent of other page collections, which may lead to unexpected behavior.
|
||||
|
||||
For example, with this content structure:
|
||||
|
||||
```text
|
||||
content/
|
||||
├── pages/
|
||||
│ ├── _index.md
|
||||
│ ├── page-1.md <-- front matter: weight = 10
|
||||
│ ├── page-2.md <-- front matter: weight = 20
|
||||
│ └── page-3.md <-- front matter: weight = 30
|
||||
└── _index.md
|
||||
```
|
||||
|
||||
And these templates:
|
||||
|
||||
{{< code file=layouts/_default/list.html >}}
|
||||
{{ range .Pages.ByWeight }}
|
||||
<h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
|
||||
{{ end }}
|
||||
{{< /code >}}
|
||||
|
||||
{{< code file=layouts/_default/single.html >}}
|
||||
{{ with .PrevInSection }}
|
||||
<a href="{{ .RelPermalink }}">Previous</a>
|
||||
{{ end }}
|
||||
|
||||
{{ with .NextInSection }}
|
||||
<a href="{{ .RelPermalink }}">Next</a>
|
||||
{{ end }}
|
||||
{{< /code >}}
|
||||
|
||||
When you visit page-2:
|
||||
|
||||
- The `PrevInSection` method points to page-3
|
||||
- The `NextInSection` method points to page-1
|
||||
|
||||
To reverse the meaning of _next_ and _previous_ you can change the sort direction in your [site configuration], or use the [`Next`] and [`Prev`] methods on a `Pages` object for more flexibility.
|
||||
|
||||
[site configuration]: getting-started/configuration/#configure-page
|
||||
[`Next`]: /methods/pages/prev
|
||||
[`Prev`]: /methods/pages/prev
|
||||
|
||||
## Example
|
||||
|
||||
Code defensively by checking for page existence:
|
||||
|
||||
```go-html-template
|
||||
{{ with .PrevInSection }}
|
||||
<a href="{{ .RelPermalink }}">Previous</a>
|
||||
{{ end }}
|
||||
|
||||
{{ with .NextInSection }}
|
||||
<a href="{{ .RelPermalink }}">Next</a>
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
## Alternative
|
||||
|
||||
Use the [`Next`] and [`Prev`] methods on a `Pages` object for more flexibility.
|
||||
@@ -1,55 +1,17 @@
|
||||
---
|
||||
title: Next
|
||||
description: Returns the next page in a local page collection, relative to the given page.
|
||||
description: Returns the next page in a page collection, relative to the given page.
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
related:
|
||||
- methods/pages/Prev
|
||||
- methods/page/Next
|
||||
- methods/page/NextInSection
|
||||
- methods/page/Prev
|
||||
- methods/page/NextInSection
|
||||
- methods/page/PrevInSection
|
||||
returnType: page.Page
|
||||
signatures: [PAGES.Next PAGE]
|
||||
toc: true
|
||||
---
|
||||
|
||||
The behavior of the `Prev` and `Next` methods on a `Pages` objects is probably the reverse of what you expect.
|
||||
|
||||
With this content structure and the page collection sorted by weight in ascending order:
|
||||
|
||||
```text
|
||||
content/
|
||||
├── pages/
|
||||
│ ├── _index.md
|
||||
│ ├── page-1.md <-- front matter: weight = 10
|
||||
│ ├── page-2.md <-- front matter: weight = 20
|
||||
│ └── page-3.md <-- front matter: weight = 30
|
||||
└── _index.md
|
||||
```
|
||||
|
||||
When you visit page-2:
|
||||
|
||||
- The `Prev` method points to page-3
|
||||
- The `Next` method points to page-1
|
||||
|
||||
{{% note %}}
|
||||
Use the opposite label in your navigation links as shown in the example below.
|
||||
{{% /note %}}
|
||||
|
||||
```go-html-template
|
||||
{{ $pages := where .Site.RegularPages.ByWeight "Section" "pages" }}
|
||||
|
||||
{{ with $pages.Next . }}
|
||||
<a href="{{ .RelPermalink }}">Previous</a>
|
||||
{{ end }}
|
||||
|
||||
{{ with $pages.Prev . }}
|
||||
<a href="{{ .RelPermalink }}">Next</a>
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
## Compare to Page methods
|
||||
|
||||
{{% include "methods/_common/next-prev-on-page-vs-next-prev-on-pages.md" %}}
|
||||
{{% include "methods/pages/_common/next-and-prev.md" %}}
|
||||
|
||||
@@ -1,55 +1,17 @@
|
||||
---
|
||||
title: Prev
|
||||
description: Returns the previous page in a local page collection, relative to the given page.
|
||||
description: Returns the previous page in a page collection, relative to the given page.
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
related:
|
||||
- methods/pages/Next
|
||||
- methods/page/Next
|
||||
- methods/page/NextInSection
|
||||
- methods/page/Prev
|
||||
- methods/page/NextInSection
|
||||
- methods/page/PrevInSection
|
||||
returnType: page.Pages
|
||||
signatures: [PAGES.Prev PAGE]
|
||||
toc: true
|
||||
---
|
||||
|
||||
The behavior of the `Prev` and `Next` methods on a `Pages` objects is probably the reverse of what you expect.
|
||||
|
||||
With this content structure and the page collection sorted by weight in ascending order:
|
||||
|
||||
```text
|
||||
content/
|
||||
├── pages/
|
||||
│ ├── _index.md
|
||||
│ ├── page-1.md <-- front matter: weight = 10
|
||||
│ ├── page-2.md <-- front matter: weight = 20
|
||||
│ └── page-3.md <-- front matter: weight = 30
|
||||
└── _index.md
|
||||
```
|
||||
|
||||
When you visit page-2:
|
||||
|
||||
- The `Prev` method points to page-3
|
||||
- The `Next` method points to page-1
|
||||
|
||||
{{% note %}}
|
||||
Use the opposite label in your navigation links as shown in the example below.
|
||||
{{% /note %}}
|
||||
|
||||
```go-html-template
|
||||
{{ $pages := where .Site.RegularPages.ByWeight "Section" "pages" }}
|
||||
|
||||
{{ with $pages.Next . }}
|
||||
<a href="{{ .RelPermalink }}">Previous</a>
|
||||
{{ end }}
|
||||
|
||||
{{ with $pages.Prev . }}
|
||||
<a href="{{ .RelPermalink }}">Next</a>
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
## Compare to Page methods
|
||||
|
||||
{{% include "methods/_common/next-prev-on-page-vs-next-prev-on-pages.md" %}}
|
||||
{{% include "methods/pages/_common/next-and-prev.md" %}}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
# Do not remove front matter.
|
||||
---
|
||||
|
||||
Hugo determines the _next_ and _previous_ page by sorting the page collection according to this sorting hierarchy:
|
||||
|
||||
Field|Precedence|Sort direction
|
||||
:--|:--|:--
|
||||
[`weight`]|1|descending
|
||||
[`date`]|2|descending
|
||||
[`linkTitle`]|3|descending
|
||||
[`path`]|4|descending
|
||||
|
||||
[`date`]: /methods/page/date/
|
||||
[`weight`]: /methods/page/weight/
|
||||
[`linkTitle`]: /methods/page/linktitle/
|
||||
[`path`]: /methods/page/path/
|
||||
|
||||
The sorted page collection used to determine the _next_ and _previous_ page is independent of other page collections, which may lead to unexpected behavior.
|
||||
|
||||
For example, with this content structure:
|
||||
|
||||
```text
|
||||
content/
|
||||
├── pages/
|
||||
│ ├── _index.md
|
||||
│ ├── page-1.md <-- front matter: weight = 10
|
||||
│ ├── page-2.md <-- front matter: weight = 20
|
||||
│ └── page-3.md <-- front matter: weight = 30
|
||||
└── _index.md
|
||||
```
|
||||
|
||||
And these templates:
|
||||
|
||||
{{< code file=layouts/_default/list.html >}}
|
||||
{{ range .Pages.ByWeight}}
|
||||
<h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
|
||||
{{ end }}
|
||||
{{< /code >}}
|
||||
|
||||
{{< code file=layouts/_default/single.html >}}
|
||||
{{ $pages := .CurrentSection.Pages.ByWeight }}
|
||||
|
||||
{{ with $pages.Prev . }}
|
||||
<a href="{{ .RelPermalink }}">Previous</a>
|
||||
{{ end }}
|
||||
|
||||
{{ with $pages.Next . }}
|
||||
<a href="{{ .RelPermalink }}">Next</a>
|
||||
{{ end }}
|
||||
{{< /code >}}
|
||||
|
||||
When you visit page-2:
|
||||
|
||||
- The `Prev` method points to page-3
|
||||
- The `Next` method points to page-1
|
||||
|
||||
To reverse the meaning of _next_ and _previous_ you can chain the [`Reverse`] method to the page collection definition:
|
||||
|
||||
{{< code file=layouts/_default/single.html >}}
|
||||
{{ $pages := .CurrentSection.Pages.ByWeight.Reverse }}
|
||||
|
||||
{{ with $pages.Prev . }}
|
||||
<a href="{{ .RelPermalink }}">Previous</a>
|
||||
{{ end }}
|
||||
|
||||
{{ with $pages.Next . }}
|
||||
<a href="{{ .RelPermalink }}">Next</a>
|
||||
{{ end }}
|
||||
{{< /code >}}
|
||||
|
||||
[`Reverse`]: /methods/pages/reverse/
|
||||
Executable
+207
@@ -0,0 +1,207 @@
|
||||
---
|
||||
title: Blockquote render hooks
|
||||
linkTitle: Blockquotes
|
||||
description: Create a blockquote render hook to override the rendering of Markdown blockquotes to HTML.
|
||||
categories: [render hooks]
|
||||
keywords: []
|
||||
menu:
|
||||
docs:
|
||||
parent: render-hooks
|
||||
weight: 30
|
||||
weight: 30
|
||||
toc: true
|
||||
---
|
||||
|
||||
{{< new-in 0.132.0 >}}
|
||||
|
||||
## Context
|
||||
|
||||
Blockquote render hook templates receive the following [context]:
|
||||
|
||||
[context]: /getting-started/glossary/#context
|
||||
|
||||
###### AlertType
|
||||
|
||||
(`string`) Applicable when [`Type`](#type) is `alert`, this is the alert type converted to lowercase. See the [alerts](#alerts) section below.
|
||||
|
||||
###### AlertTitle
|
||||
|
||||
{{< new-in 0.134.0 >}}
|
||||
|
||||
(`template.HTML`) Applicable when [`Type`](#type) is `alert`, this is the alert title. See the [alerts](#alerts) section below.
|
||||
|
||||
###### AlertSign
|
||||
|
||||
{{< new-in 0.134.0 >}}
|
||||
|
||||
(`string`) Applicable when [`Type`](#type) is `alert`, this is the alert sign. Typically used to indicate whether an alert is graphically foldable, this is one of `+`, `-`, or an empty string. See the [alerts](#alerts) section below.
|
||||
|
||||
###### Attributes
|
||||
|
||||
(`map`) The [Markdown attributes], available if you configure your site as follows:
|
||||
|
||||
[Markdown attributes]: /content-management/markdown-attributes/
|
||||
|
||||
{{< code-toggle file=hugo >}}
|
||||
[markup.goldmark.parser.attribute]
|
||||
block = true
|
||||
{{< /code-toggle >}}
|
||||
|
||||
###### Ordinal
|
||||
|
||||
(`int`) The zero-based ordinal of the blockquote on the page.
|
||||
|
||||
###### Page
|
||||
|
||||
(`page`) A reference to the current page.
|
||||
|
||||
###### PageInner
|
||||
|
||||
(`page`) A reference to a page nested via the [`RenderShortcodes`] method. [See details](#pageinner-details).
|
||||
|
||||
[`RenderShortcodes`]: /methods/page/rendershortcodes
|
||||
|
||||
###### Position
|
||||
|
||||
(`string`) The position of the blockquote within the page content.
|
||||
|
||||
###### Text
|
||||
(`template.HTML`) The blockquote text, excluding the first line if [`Type`](#type) is `alert`. See the [alerts](#alerts) section below.
|
||||
|
||||
###### Type
|
||||
|
||||
(`bool`) The blockquote type. Returns `alert` if the blockquote has an alert designator, else `regular`. See the [alerts](#alerts) section below.
|
||||
|
||||
## Examples
|
||||
|
||||
In its default configuration, Hugo renders Markdown blockquotes according to the [CommonMark specification]. To create a render hook that does the same thing:
|
||||
|
||||
[CommonMark specification]: https://spec.commonmark.org/current/
|
||||
|
||||
{{< code file=layouts/_default/_markup/render-blockquote.html copy=true >}}
|
||||
<blockquote>
|
||||
{{ .Text }}
|
||||
</blockquote>
|
||||
{{< /code >}}
|
||||
|
||||
To render a blockquote as an HTML `figure` element with an optional citation and caption:
|
||||
|
||||
{{< code file=layouts/_default/_markup/render-blockquote.html copy=true >}}
|
||||
<figure>
|
||||
<blockquote {{ with .Attributes.cite }}cite="{{ . }}"{{ end }}>
|
||||
{{ .Text }}
|
||||
</blockquote>
|
||||
{{ with .Attributes.caption }}
|
||||
<figcaption class="blockquote-caption">
|
||||
{{ . | safeHTML }}
|
||||
</figcaption>
|
||||
{{ end }}
|
||||
</figure>
|
||||
{{< /code >}}
|
||||
|
||||
Then in your markdown:
|
||||
|
||||
```text
|
||||
> Some text
|
||||
{cite="https://gohugo.io" caption="Some caption"}
|
||||
```
|
||||
|
||||
## Alerts
|
||||
|
||||
Also known as _callouts_ or _admonitions_, alerts are blockquotes used to emphasize critical information.
|
||||
|
||||
### Basic syntax
|
||||
|
||||
With the basic Markdown syntax, the first line of each alert is an alert designator consisting of an exclamation point followed by the alert type, wrapped within brackets. For example:
|
||||
|
||||
{{< code file=content/example.md lang=text >}}
|
||||
> [!NOTE]
|
||||
> Useful information that users should know, even when skimming content.
|
||||
|
||||
> [!TIP]
|
||||
> Helpful advice for doing things better or more easily.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Key information users need to know to achieve their goal.
|
||||
|
||||
> [!WARNING]
|
||||
> Urgent info that needs immediate user attention to avoid problems.
|
||||
|
||||
> [!CAUTION]
|
||||
> Advises about risks or negative outcomes of certain actions.
|
||||
{{< /code >}}
|
||||
|
||||
The basic syntax is compatible with [GitHub], [Obsidian], and [Typora].
|
||||
|
||||
[GitHub]: https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts
|
||||
[Obsidian]: https://help.obsidian.md/Editing+and+formatting/Callouts
|
||||
[Typora]: https://support.typora.io/Markdown-Reference/#callouts--github-style-alerts
|
||||
|
||||
### Extended syntax
|
||||
|
||||
With the extended Markdown syntax, you may optionally include an alert sign and/or an alert title. The alert sign is one of `+` or `-`, typically used to indicate whether an alert is graphically foldable. For example:
|
||||
|
||||
{{< code file=content/example.md lang=text >}}
|
||||
> [!WARNING]+ Radiation hazard
|
||||
> Do not approach or handle without protective gear.
|
||||
{{< /code >}}
|
||||
|
||||
The extended syntax is compatible with [Obsidian].
|
||||
|
||||
{{% note %}}
|
||||
The extended syntax is not compatible with GitHub or Typora. If you include an alert sign or an alert title, these applications render the Markdown as a blockquote.
|
||||
{{% /note %}}
|
||||
|
||||
### Example
|
||||
|
||||
This blockquote render hook renders a multilingual alert if an alert designator is present, otherwise it renders a blockquote according to the CommonMark specification.
|
||||
|
||||
{{< code file=layouts/_default/_markup/render-blockquote.html copy=true >}}
|
||||
{{ $emojis := dict
|
||||
"caution" ":exclamation:"
|
||||
"important" ":information_source:"
|
||||
"note" ":information_source:"
|
||||
"tip" ":bulb:"
|
||||
"warning" ":information_source:"
|
||||
}}
|
||||
|
||||
{{ if eq .Type "alert" }}
|
||||
<blockquote class="alert alert-{{ .AlertType }}">
|
||||
<p class="alert-heading">
|
||||
{{ transform.Emojify (index $emojis .AlertType) }}
|
||||
{{ with .AlertTitle }}
|
||||
{{ . }}
|
||||
{{ else }}
|
||||
{{ or (i18n .AlertType) (title .AlertType) }}
|
||||
{{ end }}
|
||||
</p>
|
||||
{{ .Text }}
|
||||
</blockquote>
|
||||
{{ else }}
|
||||
<blockquote>
|
||||
{{ .Text }}
|
||||
</blockquote>
|
||||
{{ end }}
|
||||
{{< /code >}}
|
||||
|
||||
To override the label, create these entries in your i18n files:
|
||||
|
||||
{{< code-toggle file=i18n/en.toml >}}
|
||||
caution = 'Caution'
|
||||
important = 'Important'
|
||||
note = 'Note'
|
||||
tip = 'Tip'
|
||||
warning = 'Warning'
|
||||
{{< /code-toggle >}}
|
||||
|
||||
Although you can use one template with conditional logic as shown above, you can also create separate templates for each [`Type`](#type) of blockquote:
|
||||
|
||||
```text
|
||||
layouts/
|
||||
└── _default/
|
||||
└── _markup/
|
||||
├── render-blockquote-alert.html
|
||||
└── render-blockquote-regular.html
|
||||
```
|
||||
|
||||
{{% include "/render-hooks/_common/pageinner.md" %}}
|
||||
@@ -7,8 +7,8 @@ keywords: []
|
||||
menu:
|
||||
docs:
|
||||
parent: render-hooks
|
||||
weight: 30
|
||||
weight: 30
|
||||
weight: 40
|
||||
weight: 40
|
||||
toc: true
|
||||
---
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ keywords: []
|
||||
menu:
|
||||
docs:
|
||||
parent: render-hooks
|
||||
weight: 40
|
||||
weight: 40
|
||||
weight: 50
|
||||
weight: 50
|
||||
toc: true
|
||||
---
|
||||
|
||||
@@ -24,7 +24,9 @@ Heading render hook templates receive the following [context]:
|
||||
|
||||
###### Attributes
|
||||
|
||||
(`map`) The Markdown attributes, available if you configure your site as follows:
|
||||
(`map`) The [Markdown attributes], available if you configure your site as follows:
|
||||
|
||||
[Markdown attributes]: /content-management/markdown-attributes/
|
||||
|
||||
{{< code-toggle file=hugo >}}
|
||||
[markup.goldmark.parser.attribute]
|
||||
@@ -53,7 +55,7 @@ title = true
|
||||
|
||||
###### Text
|
||||
|
||||
(`string`) The heading text.
|
||||
(`template.HTML`) The heading text.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -63,7 +65,7 @@ In its default configuration, Hugo renders Markdown headings according to the [C
|
||||
|
||||
{{< code file=layouts/_default/_markup/render-heading.html copy=true >}}
|
||||
<h{{ .Level }} id="{{ .Anchor }}">
|
||||
{{- .Text | safeHTML -}}
|
||||
{{- .Text -}}
|
||||
</h{{ .Level }}>
|
||||
{{< /code >}}
|
||||
|
||||
@@ -71,7 +73,7 @@ To add an anchor link to the right of each heading:
|
||||
|
||||
{{< code file=layouts/_default/_markup/render-heading.html copy=true >}}
|
||||
<h{{ .Level }} id="{{ .Anchor }}">
|
||||
{{ .Text | safeHTML }}
|
||||
{{ .Text }}
|
||||
<a href="#{{ .Anchor }}">#</a>
|
||||
</h{{ .Level }}>
|
||||
{{< /code >}}
|
||||
|
||||
@@ -7,8 +7,8 @@ keywords: []
|
||||
menu:
|
||||
docs:
|
||||
parent: render-hooks
|
||||
weight: 50
|
||||
weight: 50
|
||||
weight: 60
|
||||
weight: 60
|
||||
toc: true
|
||||
---
|
||||
|
||||
@@ -32,7 +32,9 @@ Image render hook templates receive the following context:
|
||||
|
||||
###### Attributes
|
||||
|
||||
(`map`) The Markdown attributes, available if you configure your site as follows:
|
||||
(`map`) The [Markdown attributes], available if you configure your site as follows:
|
||||
|
||||
[Markdown attributes]: /content-management/markdown-attributes/
|
||||
|
||||
{{< code-toggle file=hugo >}}
|
||||
[markup.goldmark.parser]
|
||||
@@ -71,7 +73,7 @@ block = true
|
||||
|
||||
###### Text
|
||||
|
||||
(`string`) The image description.
|
||||
(`template.HTML`) The image description.
|
||||
|
||||
###### Title
|
||||
|
||||
@@ -141,7 +143,7 @@ The embedded image render hook is automatically enabled for multilingual single-
|
||||
[duplication of shared page resources]: /getting-started/configuration-markup/#duplicateresourcefiles
|
||||
{{% /note %}}
|
||||
|
||||
The embedded image render hook resolves internal Markdown destinations by looking for a matching [page resource], falling back to a matching [global resource]. Remote destinations are passed through, and the render hook will not throw an error or warning if it is unable to resolve a destination.
|
||||
The embedded image render hook resolves internal Markdown destinations by looking for a matching [page resource], falling back to a matching [global resource]. Remote destinations are passed through, and the render hook will not throw an error or warning if unable to resolve a destination.
|
||||
|
||||
[page resource]: /getting-started/glossary/#page-resource
|
||||
[global resource]: /getting-started/glossary/#global-resource
|
||||
|
||||
@@ -13,10 +13,13 @@ weight: 20
|
||||
|
||||
When rendering Markdown to HTML, render hooks override the conversion. Each render hook is a template, with one template for each supported element type:
|
||||
|
||||
- [Blockquotes](/render-hooks/blockquotes)
|
||||
- [Code blocks](/render-hooks/code-blocks)
|
||||
- [Headings](/render-hooks/headings)
|
||||
- [Images](/render-hooks/images)
|
||||
- [Links](/render-hooks/links)
|
||||
- [Passthrough elements](/render-hooks/passthrough)
|
||||
- [Tables](/render-hooks/tables)
|
||||
|
||||
{{% note %}}
|
||||
Hugo supports multiple [content formats] including Markdown, HTML, AsciiDoc, Emacs Org Mode, Pandoc, and reStructuredText.
|
||||
@@ -54,10 +57,13 @@ Each render hook is a template, with one template for each supported element typ
|
||||
layouts/
|
||||
└── _default/
|
||||
└── _markup/
|
||||
├── render-blockquote.html
|
||||
├── render-codeblock.html
|
||||
├── render-heading.html
|
||||
├── render-image.html
|
||||
└── render-link.html
|
||||
├── render-link.html
|
||||
├── render-passthrough.html
|
||||
└── render-table.html
|
||||
```
|
||||
|
||||
The template lookup order allows you to create different render hooks for each page [type], [kind], language, and [output format]. For example:
|
||||
|
||||
@@ -7,8 +7,8 @@ keywords: []
|
||||
menu:
|
||||
docs:
|
||||
parent: render-hooks
|
||||
weight: 60
|
||||
weight: 60
|
||||
weight: 70
|
||||
weight: 70
|
||||
toc: true
|
||||
---
|
||||
|
||||
@@ -54,7 +54,7 @@ Link render hook templates receive the following context:
|
||||
|
||||
###### Text
|
||||
|
||||
(`string`) The link description.
|
||||
(`template.HTML`) The link description.
|
||||
|
||||
###### Title
|
||||
|
||||
@@ -74,7 +74,7 @@ In its default configuration, Hugo renders Markdown links according to the [Comm
|
||||
<a href="{{ .Destination | safeURL }}"
|
||||
{{- with .Title }} title="{{ . }}"{{ end -}}
|
||||
>
|
||||
{{- with .Text | safeHTML }}{{ . }}{{ end -}}
|
||||
{{- with .Text }}{{ . }}{{ end -}}
|
||||
</a>
|
||||
{{- /* chomp trailing newline */ -}}
|
||||
{{< /code >}}
|
||||
@@ -87,7 +87,7 @@ To include a `rel` attribute set to `external` for external links:
|
||||
{{- with .Title }} title="{{ . }}"{{ end -}}
|
||||
{{- if $u.IsAbs }} rel="external"{{ end -}}
|
||||
>
|
||||
{{- with .Text | safeHTML }}{{ . }}{{ end -}}
|
||||
{{- with .Text }}{{ . }}{{ end -}}
|
||||
</a>
|
||||
{{- /* chomp trailing newline */ -}}
|
||||
{{< /code >}}
|
||||
@@ -113,7 +113,7 @@ The embedded link render hook is automatically enabled for multilingual single-h
|
||||
[duplication of shared page resources]: /getting-started/configuration-markup/#duplicateresourcefiles
|
||||
{{% /note %}}
|
||||
|
||||
The embedded link render hook resolves internal Markdown destinations by looking for a matching page, falling back to a matching [page resource], then falling back to a matching [global resource]. Remote destinations are passed through, and the render hook will not throw an error or warning if it is unable to resolve a destination.
|
||||
The embedded link render hook resolves internal Markdown destinations by looking for a matching page, falling back to a matching [page resource], then falling back to a matching [global resource]. Remote destinations are passed through, and the render hook will not throw an error or warning if unable to resolve a destination.
|
||||
|
||||
[page resource]: /getting-started/glossary/#page-resource
|
||||
[global resource]: /getting-started/glossary/#global-resource
|
||||
|
||||
Executable
+125
@@ -0,0 +1,125 @@
|
||||
---
|
||||
title: Passthrough render hooks
|
||||
linkTitle: Passthrough
|
||||
description: Create a passthrough render hook to override the rendering of text snippets captured by the Goldmark passthrough extension.
|
||||
categories: [render hooks]
|
||||
keywords: []
|
||||
menu:
|
||||
docs:
|
||||
parent: render-hooks
|
||||
weight: 80
|
||||
weight: 80
|
||||
toc: true
|
||||
---
|
||||
|
||||
{{< new-in 0.132.0 >}}
|
||||
|
||||
## Overview
|
||||
|
||||
Hugo uses [Goldmark] to render Markdown to HTML. Goldmark supports custom extensions to extend its core functionality. The Goldmark [passthrough extension] captures and preserves raw Markdown within delimited snippets of text, including the delimiters themselves. These are known as _passthrough elements_.
|
||||
|
||||
[Goldmark]: https://github.com/yuin/goldmark
|
||||
[passthrough extension]: /getting-started/configuration-markup/#passthrough
|
||||
|
||||
Depending on your choice of delimiters, Hugo will classify a passthrough element as either _block_ or _inline_. Consider this contrived example:
|
||||
|
||||
{{< code file=content/sample.md >}}
|
||||
This is a
|
||||
|
||||
\[block\]
|
||||
|
||||
passthrough element with opening and closing block delimiters.
|
||||
|
||||
This is an \(inline\) passthrough element with opening and closing inline delimiters.
|
||||
{{< /code >}}
|
||||
|
||||
Update your site configuration to enable the passthrough extension and define opening and closing delimiters for each passthrough element type, either `block` or `inline`. For example:
|
||||
|
||||
{{< code-toggle file=hugo >}}
|
||||
[markup.goldmark.extensions.passthrough]
|
||||
enable = true
|
||||
[markup.goldmark.extensions.passthrough.delimiters]
|
||||
block = [['\[', '\]'], ['$$', '$$']]
|
||||
inline = [['\(', '\)']]
|
||||
{{< /code-toggle >}}
|
||||
|
||||
In the example above there are two sets of `block` delimiters. You may use either one in your Markdown.
|
||||
|
||||
The Goldmark passthrough extension is often used in conjunction with the MathJax or KaTeX display engine to render [mathematical expressions] written in [LaTeX] or [Tex].
|
||||
|
||||
[mathematical expressions]: /content-management/mathematics/
|
||||
[LaTeX]: https://www.latex-project.org/
|
||||
[Tex]: https://en.wikipedia.org/wiki/TeX
|
||||
|
||||
To enable custom rendering of passthrough elements, create a render hook.
|
||||
|
||||
## Context
|
||||
|
||||
Passthrough render hook templates receive the following [context]:
|
||||
|
||||
[context]: /getting-started/glossary/#context
|
||||
|
||||
###### Attributes
|
||||
|
||||
(`map`) The [Markdown attributes], available if you configure your site as follows:
|
||||
|
||||
[Markdown attributes]: /content-management/markdown-attributes/
|
||||
|
||||
{{< code-toggle file=hugo >}}
|
||||
[markup.goldmark.parser.attribute]
|
||||
block = true
|
||||
{{< /code-toggle >}}
|
||||
|
||||
Hugo populates the `Attributes` map for _block_ passthrough elements. Markdown attributes are not applicable to _inline_ elements.
|
||||
|
||||
###### Inner
|
||||
(`string`) The inner content of the passthrough element, excluding the delimiters.
|
||||
|
||||
###### Ordinal
|
||||
|
||||
(`int`) The zero-based ordinal of the passthrough element on the page.
|
||||
|
||||
###### Page
|
||||
|
||||
(`page`) A reference to the current page.
|
||||
|
||||
###### PageInner
|
||||
|
||||
(`page`) A reference to a page nested via the [`RenderShortcodes`] method. [See details](#pageinner-details).
|
||||
|
||||
[`RenderShortcodes`]: /methods/page/rendershortcodes
|
||||
|
||||
###### Position
|
||||
|
||||
(`string`) The position of the passthrough element within the page content.
|
||||
|
||||
###### Type
|
||||
|
||||
(`bool`) The passthrough element type, either `block` or `inline`.
|
||||
|
||||
## Example
|
||||
|
||||
As an alternative to rendering mathematical expressions with the MathJax or KaTeX display engine, create a passthrough render hook which calls the [`transform.ToMath`] function:
|
||||
|
||||
[`transform.ToMath`]: /functions/transform/tomath/
|
||||
|
||||
{{< code file=layouts/_default/_markup/render-passthrough.html copy=true >}}
|
||||
{{ if eq .Type "block" }}
|
||||
{{ $opts := dict "displayMode" true }}
|
||||
{{ transform.ToMath .Inner $opts }}
|
||||
{{ else }}
|
||||
{{ transform.ToMath .Inner }}
|
||||
{{ end }}
|
||||
{{< /code >}}
|
||||
|
||||
Although you can use one template with conditional logic as shown above, you can also create separate templates for each [`Type`](#type) of passthrough element:
|
||||
|
||||
```text
|
||||
layouts/
|
||||
└── _default/
|
||||
└── _markup/
|
||||
├── render-passthrough-block.html
|
||||
└── render-passthrough-inline.html
|
||||
```
|
||||
|
||||
{{% include "/render-hooks/_common/pageinner.md" %}}
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
---
|
||||
title: Table render hooks
|
||||
linkTitle: Tables
|
||||
description: Create a table render hook to override the rendering of Markdown tables to HTML.
|
||||
categories: [render hooks]
|
||||
keywords: []
|
||||
menu:
|
||||
docs:
|
||||
parent: render-hooks
|
||||
weight: 90
|
||||
weight: 90
|
||||
toc: true
|
||||
---
|
||||
|
||||
{{< new-in 0.134.0 >}}
|
||||
|
||||
## Context
|
||||
|
||||
Table render hook templates receive the following [context]:
|
||||
|
||||
[context]: /getting-started/glossary/#context
|
||||
|
||||
###### Attributes
|
||||
|
||||
(`map`) The [Markdown attributes], available if you configure your site as follows:
|
||||
|
||||
[Markdown attributes]: /content-management/markdown-attributes/
|
||||
|
||||
{{< code-toggle file=hugo >}}
|
||||
[markup.goldmark.parser.attribute]
|
||||
block = true
|
||||
{{< /code-toggle >}}
|
||||
|
||||
###### Ordinal
|
||||
|
||||
(`int`) The zero-based ordinal of the table on the page.
|
||||
|
||||
###### Page
|
||||
|
||||
(`page`) A reference to the current page.
|
||||
|
||||
###### PageInner
|
||||
|
||||
(`page`) A reference to a page nested via the [`RenderShortcodes`] method. [See details](#pageinner-details).
|
||||
|
||||
[`RenderShortcodes`]: /methods/page/rendershortcodes
|
||||
|
||||
###### Position
|
||||
|
||||
(`string`) The position of the table within the page content.
|
||||
|
||||
###### THead
|
||||
(`slice`) A slice of table header rows, where each element is a slice of table cells.
|
||||
|
||||
###### TBody
|
||||
(`slice`) A slice of table body rows, where each element is a slice of table cells.
|
||||
|
||||
## Table cells
|
||||
|
||||
Each table cell within the slice of slices returned by the `THead` and `TBody` methods has the following fields:
|
||||
|
||||
###### Alignment
|
||||
(`string`) The alignment of the text within the table cell, one of `left`, `center`, or `right`.
|
||||
|
||||
###### Text
|
||||
(`template.HTML`) The text within the table cell.
|
||||
|
||||
## Example
|
||||
|
||||
In its default configuration, Hugo renders Markdown tables according to the [GitHub Flavored Markdown specification]. To create a render hook that does the same thing:
|
||||
|
||||
[GitHub Flavored Markdown specification]: https://github.github.com/gfm/#tables-extension-
|
||||
|
||||
{{< code file=layouts/_default/_markup/render-table.html copy=true >}}
|
||||
<table
|
||||
{{- range $k, $v := .Attributes }}
|
||||
{{- if $v }}
|
||||
{{- printf " %s=%q" $k $v | safeHTMLAttr }}
|
||||
{{- end }}
|
||||
{{- end }}>
|
||||
<thead>
|
||||
{{- range .THead }}
|
||||
<tr>
|
||||
{{- range . }}
|
||||
<th {{ printf "style=%q" (printf "text-align: %s" .Alignment) | safeHTMLAttr }}>
|
||||
{{- .Text -}}
|
||||
</th>
|
||||
{{- end }}
|
||||
</tr>
|
||||
{{- end }}
|
||||
</thead>
|
||||
<tbody>
|
||||
{{- range .TBody }}
|
||||
<tr>
|
||||
{{- range . }}
|
||||
<td {{ printf "style=%q" (printf "text-align: %s" .Alignment) | safeHTMLAttr }}>
|
||||
{{- .Text -}}
|
||||
</td>
|
||||
{{- end }}
|
||||
</tr>
|
||||
{{- end }}
|
||||
</tbody>
|
||||
</table>
|
||||
{{< /code >}}
|
||||
|
||||
{{% include "/render-hooks/_common/pageinner.md" %}}
|
||||
@@ -135,7 +135,7 @@ tags = []
|
||||
|
||||
Hugo uses the page title and description for the title and description metadata.
|
||||
The first 6 URLs from the `images` array are used for image metadata.
|
||||
If [page bundles](/content-management/page-bundles/) are used and the `images` array is empty or undefined, images with file names matching `*feature*` or `*cover*,*thumbnail*` are used for image metadata.
|
||||
If [page bundles](/content-management/page-bundles/) are used and the `images` array is empty or undefined, images with file names matching `*feature*`, `*cover*`, or `*thumbnail*` are used for image metadata.
|
||||
|
||||
Various optional metadata can also be set:
|
||||
|
||||
@@ -203,7 +203,7 @@ description = "Text about this post"
|
||||
images = ["post-cover.png"]
|
||||
{{</ code-toggle >}}
|
||||
|
||||
If `images` aren't specified in the page front-matter, then hugo searches for [image page resources](/content-management/image-processing/) with `feature`, `cover`, or `thumbnail` in their name.
|
||||
If [page bundles](/content-management/page-bundles/) are used and the `images` array is empty or undefined, images with file names matching `*feature*`, `*cover*`, or `*thumbnail*` are used for image metadata.
|
||||
If no image resources with those names are found, the images defined in the [site config](/getting-started/configuration/) are used instead.
|
||||
If no images are found at all, then an image-less Twitter `summary` card is used instead of `summary_large_image`.
|
||||
|
||||
|
||||
@@ -509,6 +509,20 @@ See documentation for [`with`], [`else`], and [`end`].
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
To test multiple conditions:
|
||||
|
||||
```go-html-template
|
||||
{{ $v1 := 0 }}
|
||||
{{ $v2 := 42 }}
|
||||
{{ with $v1 }}
|
||||
{{ . }}
|
||||
{{ else with $v2 }}
|
||||
{{ . }} → 42
|
||||
{{ else }}
|
||||
{{ print "v1 and v2 are falsy" }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
### Access site parameters
|
||||
|
||||
See documentation for the [`Params`](/methods/site/params/) method on a `Site` object.
|
||||
|
||||
@@ -129,5 +129,5 @@ Hugo provides two methods to localize your menu entries. See [multilingual].
|
||||
[localize the menu entries]: /content-management/multilingual/#menus
|
||||
[menu entry defined in front matter]: /content-management/menus/#example-front-matter
|
||||
[menu entry defined in site configuration]: /content-management/menus/#example-site-configuration
|
||||
[menu and methods]: /methods/menu/
|
||||
[menu methods]: /methods/menu/
|
||||
[multilingual]: /content-management/multilingual/#menus
|
||||
|
||||
@@ -37,12 +37,29 @@ You can organize your shortcodes in subdirectories, e.g. in `layouts/shortcodes/
|
||||
|
||||
Note the forward slash.
|
||||
|
||||
### Shortcode template lookup order
|
||||
### Template lookup order
|
||||
|
||||
Shortcode templates have a simple [lookup order]:
|
||||
Hugo selects shortcode templates based on the shortcode name, the current output format, and the current language. The examples below are sorted by specificity in descending order. The least specific path is at the bottom of the list.
|
||||
|
||||
1. `/layouts/shortcodes/<SHORTCODE>.html`
|
||||
2. `/themes/<THEME>/layouts/shortcodes/<SHORTCODE>.html`
|
||||
Shortcode name|Output format|Language|Template path
|
||||
:--|:--|:--|:--
|
||||
foo|html|en|layouts/shortcodes/foo.en.html
|
||||
foo|html|en|layouts/shortcodes/foo.html.html
|
||||
foo|html|en|layouts/shortcodes/foo.html
|
||||
foo|html|en|layouts/shortcodes/foo.html.en.html
|
||||
|
||||
Shortcode name|Output format|Language|Template path
|
||||
:--|:--|:--|:--
|
||||
foo|rss|en|layouts/shortcodes/foo.en.xml
|
||||
foo|rss|en|layouts/shortcodes/foo.rss.xml
|
||||
foo|rss|en|layouts/shortcodes/foo.en.html
|
||||
foo|rss|en|layouts/shortcodes/foo.rss.en.xml
|
||||
foo|rss|en|layouts/shortcodes/foo.xml
|
||||
foo|rss|en|layouts/shortcodes/foo.html.en.html
|
||||
foo|rss|en|layouts/shortcodes/foo.html.html
|
||||
foo|rss|en|layouts/shortcodes/foo.html
|
||||
|
||||
Note that templates provided by a theme or module always take precedence.
|
||||
|
||||
### Positional vs. named arguments
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@ toc: true
|
||||
aliases: [/developer-tools/migrations/, /developer-tools/migrated/]
|
||||
---
|
||||
|
||||
This section highlights some projects around Hugo that are independently developed. These tools try to extend the functionality of our static site generator or help you to get started.
|
||||
This section highlights some independently developed projects related to Hugo. These tools extend functionality or help you to get started.
|
||||
|
||||
Take a look at this list of migration tools if you currently use other blogging tools like Jekyll or WordPress but intend to switch to Hugo instead. They'll take care to export your content into Hugo-friendly formats.
|
||||
Take a look at this list of migration tools if you currently use other blogging tools like Jekyll or WordPress but intend to switch to Hugo instead. They'll help you export your content into Hugo-friendly formats.
|
||||
|
||||
## Jekyll
|
||||
|
||||
@@ -35,18 +35,18 @@ Alternatively, you can use the [Jekyll import command](/commands/hugo_import_jek
|
||||
## DokuWiki
|
||||
|
||||
[dokuwiki-to-hugo](https://github.com/wgroeneveld/dokuwiki-to-hugo)
|
||||
: Migrates your DokuWiki source pages from [DokuWiki syntax](https://www.dokuwiki.org/wiki:syntax) to Hugo Markdown syntax. Includes extra's like the TODO plugin. Written with extensibility in mind using python 3. Also generates a TOML header for each page. Designed to copypaste the wiki directory into your /content directory.
|
||||
: Migrates your DokuWiki source pages from [DokuWiki syntax](https://www.dokuwiki.org/wiki:syntax) to Hugo Markdown syntax. Includes extras like the TODO plugin. Written with extensibility in mind using Python 3. Also generates a TOML header for each page. Designed to copy-paste the wiki directory into your /content directory.
|
||||
|
||||
## WordPress
|
||||
|
||||
[wordpress-to-hugo-exporter](https://github.com/SchumacherFM/wordpress-to-hugo-exporter)
|
||||
: A one-click WordPress plugin that converts all posts, pages, taxonomies, metadata, and settings to Markdown and YAML which can be dropped into Hugo. (Note: If you have trouble using this plugin, you can [export your site for Jekyll](https://wordpress.org/plugins/jekyll-exporter/) and use Hugo's built in Jekyll converter listed above.)
|
||||
: A one-click WordPress plugin that converts all posts, pages, taxonomies, metadata, and settings to Markdown and YAML which can be dropped into Hugo. (Note: If you have trouble using this plugin, you can [export your site for Jekyll](https://wordpress.org/plugins/jekyll-exporter/) and use Hugo's built-in Jekyll converter listed above.)
|
||||
|
||||
[blog2md](https://github.com/palaniraja/blog2md)
|
||||
: Works with [exported xml](https://en.support.wordpress.com/export/) file of your free YOUR-TLD.wordpress.com website. It also saves approved comments to `YOUR-POST-NAME-comments.md` file along with posts.
|
||||
|
||||
[wordhugopress](https://github.com/nantipov/wordhugopress)
|
||||
: A small utility written in Java, exports the entire WordPress site from the database and resource (e.g. images) files stored locally or remotely. Therefore, migration from the backup files is possible. Supports merging of the multiple WordPress sites into a single Hugo one.
|
||||
: A small utility written in Java that exports the entire WordPress site from the database and resource (e.g., images) files stored locally or remotely. Therefore, migration from the backup files is possible. Supports merging multiple WordPress sites into a single Hugo site.
|
||||
|
||||
[wp2hugo](https://github.com/ashishb/wp2hugo)
|
||||
: A Go-based CLI tool to migrate WordPress website to Hugo while preserving original URLs, GUIDs (for feeds), image URLs, code highlights, table of contents, YouTube embeds, Google Maps embeds, and original WordPress navigation categories.
|
||||
@@ -57,7 +57,7 @@ Alternatively, you can use the [Jekyll import command](/commands/hugo_import_jek
|
||||
: A simple Medium to Hugo exporter able to import stories in one command, including front matter.
|
||||
|
||||
[medium-to-hugo](https://github.com/bgadrian/medium-to-hugo)
|
||||
: CLI tool written in Go to export medium posts into a Hugo compatible Markdown format. Tags and images are included. All images will be downloaded locally and linked appropriately.
|
||||
: A CLI tool written in Go to export medium posts into a Hugo-compatible Markdown format. Tags and images are included. All images will be downloaded locally and linked appropriately.
|
||||
|
||||
## Tumblr
|
||||
|
||||
@@ -68,7 +68,7 @@ Alternatively, you can use the [Jekyll import command](/commands/hugo_import_jek
|
||||
: Export all your Tumblr content to Hugo Markdown files with preserved original formatting.
|
||||
|
||||
[Tumblr to Hugo](https://github.com/jipiboily/tumblr-to-hugo)
|
||||
: A migration tool that converts each of your Tumblr posts to a content file with a proper title and path. Furthermore, "Tumblr to Hugo" creates a CSV file with the original URL and the new path on Hugo, to help you setup the redirections.
|
||||
: A migration tool that converts each of your Tumblr posts to a content file with a proper title and path. It also generates a CSV file to help you set up URL redirects.
|
||||
|
||||
## Drupal
|
||||
|
||||
|
||||
@@ -43,6 +43,10 @@ A static website with a dynamic search function? Yes, Hugo provides an alternati
|
||||
[Hugo Lyra](https://github.com/paolomainardi/hugo-lyra)
|
||||
: Hugo-Lyra is a JavaScript module to integrate [Lyra](https://github.com/LyraSearch/lyra) into a Hugo website. It contains the server-side part to generate the index and the client-side library (optional) to bootstrap the search engine easily.
|
||||
|
||||
[INFINI Pizza for WebAssembly](https://github.com/infinilabs/pizza-docsearch)
|
||||
: Pizza is a super-lightweight yet fully featured search engine written in Rust. You can quickly add offline search functionality to your Hugo website in just five minutes with only three lines of code. For a step-by-step guide on integrating it with Hugo, check out [this blog tutorial](https://dev.to/medcl/adding-search-functionality-to-a-hugo-static-site-based-on-infini-pizza-for-webassembly-4h5e).
|
||||
|
||||
|
||||
## Commercial
|
||||
|
||||
[Algolia](https://www.algolia.com/)
|
||||
|
||||
+7
-7
@@ -1865,7 +1865,7 @@ output:
|
||||
- layouts/_default/demolayout-baseof.html
|
||||
- layouts/_default/single-baseof.html
|
||||
- layouts/_default/baseof.html
|
||||
- Example: AMP single page
|
||||
- Example: AMP single page in "posts" section
|
||||
Kind: page
|
||||
OutputFormat: amp
|
||||
Suffix: html
|
||||
@@ -1874,17 +1874,17 @@ output:
|
||||
- layouts/posts/single.html
|
||||
- layouts/_default/single.amp.html
|
||||
- layouts/_default/single.html
|
||||
- Example: AMP single page, French language
|
||||
- Example: AMP single page in "posts" section, French language
|
||||
Kind: page
|
||||
OutputFormat: html
|
||||
OutputFormat: amp
|
||||
Suffix: html
|
||||
Template Lookup Order:
|
||||
- layouts/posts/single.fr.html.html
|
||||
- layouts/posts/single.html.html
|
||||
- layouts/posts/single.fr.amp.html
|
||||
- layouts/posts/single.amp.html
|
||||
- layouts/posts/single.fr.html
|
||||
- layouts/posts/single.html
|
||||
- layouts/_default/single.fr.html.html
|
||||
- layouts/_default/single.html.html
|
||||
- layouts/_default/single.fr.amp.html
|
||||
- layouts/_default/single.amp.html
|
||||
- layouts/_default/single.fr.html
|
||||
- layouts/_default/single.html
|
||||
- Example: Home page
|
||||
|
||||
+1
-1
@@ -2,4 +2,4 @@ module github.com/gohugoio/hugoDocs
|
||||
|
||||
go 1.16
|
||||
|
||||
require github.com/gohugoio/gohugoioTheme v0.0.0-20240728210410-d42c342ce472 // indirect
|
||||
require github.com/gohugoio/gohugoioTheme v0.0.0-20240815082608-66ccd383a90f // indirect
|
||||
|
||||
@@ -10,3 +10,7 @@ github.com/gohugoio/gohugoioTheme v0.0.0-20240623150114-cc7096eab3fd h1:I8X7c0oB
|
||||
github.com/gohugoio/gohugoioTheme v0.0.0-20240623150114-cc7096eab3fd/go.mod h1:GOYeAPQJ/ok8z7oz1cjfcSlsFpXrmx6VkzQ5RpnyhZM=
|
||||
github.com/gohugoio/gohugoioTheme v0.0.0-20240728210410-d42c342ce472 h1:AYZUibKKFRBp2VCQpDHW+JmQKvCvyhX7z7/SOLUSCcw=
|
||||
github.com/gohugoio/gohugoioTheme v0.0.0-20240728210410-d42c342ce472/go.mod h1:GOYeAPQJ/ok8z7oz1cjfcSlsFpXrmx6VkzQ5RpnyhZM=
|
||||
github.com/gohugoio/gohugoioTheme v0.0.0-20240812175901-cc0ef8e4a14a h1:E3JbZo69eqFBz6B+meQlKyy/ZBZQ73ldVDw8TADiIrQ=
|
||||
github.com/gohugoio/gohugoioTheme v0.0.0-20240812175901-cc0ef8e4a14a/go.mod h1:GOYeAPQJ/ok8z7oz1cjfcSlsFpXrmx6VkzQ5RpnyhZM=
|
||||
github.com/gohugoio/gohugoioTheme v0.0.0-20240815082608-66ccd383a90f h1:Eo5z3uUYfmrtIxQvHm388dFOERZwWGTjLuUO6vobzLc=
|
||||
github.com/gohugoio/gohugoioTheme v0.0.0-20240815082608-66ccd383a90f/go.mod h1:GOYeAPQJ/ok8z7oz1cjfcSlsFpXrmx6VkzQ5RpnyhZM=
|
||||
|
||||
+3
-1
@@ -5,7 +5,6 @@ defaultContentLanguage = "en"
|
||||
enableEmoji = true
|
||||
ignoreErrors = ["error-remote-getjson", "error-missing-instagram-accesstoken"]
|
||||
languageCode = "en-us"
|
||||
paginate = 100
|
||||
pluralizeListTitles = false
|
||||
timeZone = "Europe/Oslo"
|
||||
title = "Hugo"
|
||||
@@ -13,6 +12,9 @@ title = "Hugo"
|
||||
# We do redirects via Netlify's _redirects file, generated by Hugo (see "outputs" below).
|
||||
disableAliases = true
|
||||
|
||||
[pagination]
|
||||
pagerSize = 100
|
||||
|
||||
[services.googleAnalytics]
|
||||
ID = 'G-MBZGKNMDWC'
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
command = "hugo --gc --minify"
|
||||
|
||||
[build.environment]
|
||||
HUGO_VERSION = "0.131.0"
|
||||
HUGO_VERSION = "0.134.0"
|
||||
|
||||
[context.production.environment]
|
||||
HUGO_ENV = "production"
|
||||
|
||||
@@ -12,9 +12,9 @@ require (
|
||||
github.com/bep/goat v0.5.0
|
||||
github.com/bep/godartsass v1.2.0
|
||||
github.com/bep/godartsass/v2 v2.1.0
|
||||
github.com/bep/golibsass v1.1.1
|
||||
github.com/bep/golibsass v1.2.0
|
||||
github.com/bep/gowebp v0.3.0
|
||||
github.com/bep/helpers v0.4.0
|
||||
github.com/bep/helpers v0.5.0
|
||||
github.com/bep/imagemeta v0.8.1
|
||||
github.com/bep/lazycache v0.4.0
|
||||
github.com/bep/logg v0.4.0
|
||||
@@ -27,14 +27,14 @@ require (
|
||||
github.com/cli/safeexec v1.0.1
|
||||
github.com/disintegration/gift v1.2.1
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/evanw/esbuild v0.23.0
|
||||
github.com/evanw/esbuild v0.23.1
|
||||
github.com/fatih/color v1.17.0
|
||||
github.com/fortytw2/leaktest v1.3.0
|
||||
github.com/frankban/quicktest v1.14.6
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/getkin/kin-openapi v0.123.0
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/gobuffalo/flect v1.0.2
|
||||
github.com/gobuffalo/flect v1.0.3
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e
|
||||
github.com/gohugoio/hashstructure v0.1.0
|
||||
@@ -46,7 +46,7 @@ require (
|
||||
github.com/gohugoio/testmodBuilder/mods v0.0.0-20190520184928-c56af20f2e95
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/hairyhenderson/go-codeowners v0.5.0
|
||||
github.com/hairyhenderson/go-codeowners v0.6.0
|
||||
github.com/jdkato/prose v1.2.1
|
||||
github.com/kylelemons/godebug v1.1.0
|
||||
github.com/kyokomi/emoji/v2 v2.2.13
|
||||
@@ -59,7 +59,7 @@ require (
|
||||
github.com/niklasfasching/go-org v1.7.0
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
|
||||
github.com/pelletier/go-toml/v2 v2.2.2
|
||||
github.com/pelletier/go-toml/v2 v2.2.3
|
||||
github.com/rogpeppe/go-internal v1.12.0
|
||||
github.com/sanity-io/litter v1.5.5
|
||||
github.com/spf13/afero v1.11.0
|
||||
@@ -75,11 +75,11 @@ require (
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
gocloud.dev v0.39.0
|
||||
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326
|
||||
golang.org/x/image v0.19.0
|
||||
golang.org/x/mod v0.19.0
|
||||
golang.org/x/net v0.28.0
|
||||
golang.org/x/image v0.20.0
|
||||
golang.org/x/mod v0.21.0
|
||||
golang.org/x/net v0.29.0
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/text v0.17.0
|
||||
golang.org/x/text v0.18.0
|
||||
golang.org/x/tools v0.23.0
|
||||
google.golang.org/api v0.191.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
@@ -154,9 +154,9 @@ require (
|
||||
go.opentelemetry.io/otel v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.28.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.28.0 // indirect
|
||||
golang.org/x/crypto v0.26.0 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
golang.org/x/oauth2 v0.22.0 // indirect
|
||||
golang.org/x/sys v0.24.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/time v0.6.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect
|
||||
|
||||
@@ -133,14 +133,12 @@ github.com/bep/godartsass v1.2.0 h1:E2VvQrxAHAFwbjyOIExAMmogTItSKodoKuijNrGm5yU=
|
||||
github.com/bep/godartsass v1.2.0/go.mod h1:6LvK9RftsXMxGfsA0LDV12AGc4Jylnu6NgHL+Q5/pE8=
|
||||
github.com/bep/godartsass/v2 v2.1.0 h1:fq5Y1xYf4diu4tXABiekZUCA+5l/dmNjGKCeQwdy+s0=
|
||||
github.com/bep/godartsass/v2 v2.1.0/go.mod h1:AcP8QgC+OwOXEq6im0WgDRYK7scDsmZCEW62o1prQLo=
|
||||
github.com/bep/golibsass v1.1.1 h1:xkaet75ygImMYjM+FnHIT3xJn7H0xBA9UxSOJjk8Khw=
|
||||
github.com/bep/golibsass v1.1.1/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA=
|
||||
github.com/bep/golibsass v1.2.0 h1:nyZUkKP/0psr8nT6GR2cnmt99xS93Ji82ZD9AgOK6VI=
|
||||
github.com/bep/golibsass v1.2.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA=
|
||||
github.com/bep/gowebp v0.3.0 h1:MhmMrcf88pUY7/PsEhMgEP0T6fDUnRTMpN8OclDrbrY=
|
||||
github.com/bep/gowebp v0.3.0/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
|
||||
github.com/bep/helpers v0.4.0 h1:ab9veaAiWY4ST48Oxp5usaqivDmYdB744fz+tcZ3Ifs=
|
||||
github.com/bep/helpers v0.4.0/go.mod h1:/QpHdmcPagDw7+RjkLFCvnlUc8lQ5kg4KDrEkb2Yyco=
|
||||
github.com/bep/imagemeta v0.8.0 h1:4lqI839akl6lR61D7hmvaw2LDOxiXFZ4D0VIyHyGpc4=
|
||||
github.com/bep/imagemeta v0.8.0/go.mod h1:5piPAq5Qomh07m/dPPCLN3mDJyFusvUG7VwdRD/vX0s=
|
||||
github.com/bep/helpers v0.5.0 h1:rneezhnG7GzLFlsEWO/EnleaBRuluBDGFimalO6Y50o=
|
||||
github.com/bep/helpers v0.5.0/go.mod h1:dSqCzIvHbzsk5YOesp1M7sKAq5xUcvANsRoKdawxH4Q=
|
||||
github.com/bep/imagemeta v0.8.1 h1:tjZLPRftjxU7PTI87o5e5WKOFQ4S9S0engiP1OTpJTI=
|
||||
github.com/bep/imagemeta v0.8.1/go.mod h1:5piPAq5Qomh07m/dPPCLN3mDJyFusvUG7VwdRD/vX0s=
|
||||
github.com/bep/lazycache v0.4.0 h1:X8yVyWNVupPd4e1jV7efi3zb7ZV/qcjKQgIQ5aPbkYI=
|
||||
@@ -191,8 +189,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanw/esbuild v0.23.0 h1:PLUwTn2pzQfIBRrMKcD3M0g1ALOKIHMDefdFCk7avwM=
|
||||
github.com/evanw/esbuild v0.23.0/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
||||
github.com/evanw/esbuild v0.23.1 h1:ociewhY6arjTarKLdrXfDTgy25oxhTZmzP8pfuBTfTA=
|
||||
github.com/evanw/esbuild v0.23.1/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
@@ -225,8 +223,8 @@ github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicb
|
||||
github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA=
|
||||
github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=
|
||||
github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4=
|
||||
github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e h1:QArsSubW7eDh8APMXkByjQWvuljwPGAGQpJEFn0F0wY=
|
||||
@@ -331,8 +329,8 @@ github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hairyhenderson/go-codeowners v0.5.0 h1:dpQB+hVHiRc2VVvc2BHxkuM+tmu9Qej/as3apqUbsWc=
|
||||
github.com/hairyhenderson/go-codeowners v0.5.0/go.mod h1:R3uW1OQXEj2Gu6/OvZ7bt6hr0qdkLvUWPiqNaWnexpo=
|
||||
github.com/hairyhenderson/go-codeowners v0.6.0 h1:cRCtmNf9Ni1GIeiAAlHX5IEEB2gr61813Kx5JmXxAAk=
|
||||
github.com/hairyhenderson/go-codeowners v0.6.0/go.mod h1:RFWbGcjlXhRKNezt7AQHmJucY0alk4osN0+RKOsIAa8=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
@@ -402,8 +400,8 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
@@ -444,7 +442,6 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
@@ -453,7 +450,6 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tdewolff/minify/v2 v2.20.37 h1:Q97cx4STXCh1dlWDlNHZniE8BJ2EBL0+2b0n92BJQhw=
|
||||
@@ -463,8 +459,6 @@ github.com/tdewolff/parse/v2 v2.7.15/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W
|
||||
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
|
||||
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||
github.com/tetratelabs/wazero v1.7.4-0.20240805170331-2b12e189eeec h1:KeQseLFSWb9qjW4PSWxciTBk1hbG7KsVx3rs1hIQnbQ=
|
||||
github.com/tetratelabs/wazero v1.7.4-0.20240805170331-2b12e189eeec/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
|
||||
github.com/tetratelabs/wazero v1.8.0 h1:iEKu0d4c2Pd+QSRieYbnQC9yiFlMS9D+Jr0LsRmcF4g=
|
||||
github.com/tetratelabs/wazero v1.8.0/go.mod h1:yAI0XTsMBhREkM/YDAK/zNou3GoiAce1P6+rp/wQhjs=
|
||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
@@ -515,8 +509,8 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -532,8 +526,8 @@ golang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZ
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ=
|
||||
golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys=
|
||||
golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
|
||||
golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -560,8 +554,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -601,8 +595,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -678,8 +672,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@@ -698,8 +692,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"html/template"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
@@ -165,75 +164,6 @@ func TotalWords(s string) int {
|
||||
return n
|
||||
}
|
||||
|
||||
// TruncateWordsByRune truncates words by runes.
|
||||
func (c *ContentSpec) TruncateWordsByRune(in []string) (string, bool) {
|
||||
words := make([]string, len(in))
|
||||
copy(words, in)
|
||||
|
||||
count := 0
|
||||
for index, word := range words {
|
||||
if count >= c.Cfg.SummaryLength() {
|
||||
return strings.Join(words[:index], " "), true
|
||||
}
|
||||
runeCount := utf8.RuneCountInString(word)
|
||||
if len(word) == runeCount {
|
||||
count++
|
||||
} else if count+runeCount < c.Cfg.SummaryLength() {
|
||||
count += runeCount
|
||||
} else {
|
||||
for ri := range word {
|
||||
if count >= c.Cfg.SummaryLength() {
|
||||
truncatedWords := append(words[:index], word[:ri])
|
||||
return strings.Join(truncatedWords, " "), true
|
||||
}
|
||||
count++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(words, " "), false
|
||||
}
|
||||
|
||||
// TruncateWordsToWholeSentence takes content and truncates to whole sentence
|
||||
// limited by max number of words. It also returns whether it is truncated.
|
||||
func (c *ContentSpec) TruncateWordsToWholeSentence(s string) (string, bool) {
|
||||
var (
|
||||
wordCount = 0
|
||||
lastWordIndex = -1
|
||||
)
|
||||
|
||||
for i, r := range s {
|
||||
if unicode.IsSpace(r) {
|
||||
wordCount++
|
||||
lastWordIndex = i
|
||||
|
||||
if wordCount >= c.Cfg.SummaryLength() {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if lastWordIndex == -1 {
|
||||
return s, false
|
||||
}
|
||||
|
||||
endIndex := -1
|
||||
|
||||
for j, r := range s[lastWordIndex:] {
|
||||
if isEndOfSentence(r) {
|
||||
endIndex = j + lastWordIndex + utf8.RuneLen(r)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if endIndex == -1 {
|
||||
return s, false
|
||||
}
|
||||
|
||||
return strings.TrimSpace(s[:endIndex]), endIndex < len(s)
|
||||
}
|
||||
|
||||
// TrimShortHTML removes the outer tags from HTML input where (a) the opening
|
||||
// tag is present only once with the input, and (b) the opening and closing
|
||||
// tags wrap the input after white space removal.
|
||||
@@ -256,7 +186,3 @@ func (c *ContentSpec) TrimShortHTML(input []byte, markup string) []byte {
|
||||
}
|
||||
return input
|
||||
}
|
||||
|
||||
func isEndOfSentence(r rune) bool {
|
||||
return r == '.' || r == '?' || r == '!' || r == '"' || r == '\n'
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
)
|
||||
|
||||
@@ -66,84 +65,6 @@ func TestBytesToHTML(t *testing.T) {
|
||||
c.Assert(helpers.BytesToHTML([]byte("dobedobedo")), qt.Equals, template.HTML("dobedobedo"))
|
||||
}
|
||||
|
||||
var benchmarkTruncateString = strings.Repeat("This is a sentence about nothing.", 20)
|
||||
|
||||
func BenchmarkTestTruncateWordsToWholeSentence(b *testing.B) {
|
||||
c := newTestContentSpec(nil)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
c.TruncateWordsToWholeSentence(benchmarkTruncateString)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncateWordsToWholeSentence(t *testing.T) {
|
||||
type test struct {
|
||||
input, expected string
|
||||
max int
|
||||
truncated bool
|
||||
}
|
||||
data := []test{
|
||||
{"a b c", "a b c", 12, false},
|
||||
{"a b c", "a b c", 3, false},
|
||||
{"a", "a", 1, false},
|
||||
{"This is a sentence.", "This is a sentence.", 5, false},
|
||||
{"This is also a sentence!", "This is also a sentence!", 1, false},
|
||||
{"To be. Or not to be. That's the question.", "To be.", 1, true},
|
||||
{" \nThis is not a sentence\nAnd this is another", "This is not a sentence", 4, true},
|
||||
{"", "", 10, false},
|
||||
{"This... is a more difficult test?", "This... is a more difficult test?", 1, false},
|
||||
}
|
||||
for i, d := range data {
|
||||
cfg := config.New()
|
||||
cfg.Set("summaryLength", d.max)
|
||||
c := newTestContentSpec(cfg)
|
||||
output, truncated := c.TruncateWordsToWholeSentence(d.input)
|
||||
if d.expected != output {
|
||||
t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
|
||||
}
|
||||
|
||||
if d.truncated != truncated {
|
||||
t.Errorf("Test %d failed. Expected truncated=%t got %t", i, d.truncated, truncated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncateWordsByRune(t *testing.T) {
|
||||
type test struct {
|
||||
input, expected string
|
||||
max int
|
||||
truncated bool
|
||||
}
|
||||
data := []test{
|
||||
{"", "", 1, false},
|
||||
{"a b c", "a b c", 12, false},
|
||||
{"a b c", "a b c", 3, false},
|
||||
{"a", "a", 1, false},
|
||||
{"Hello 中国", "", 0, true},
|
||||
{"这是中文,全中文。", "这是中文,", 5, true},
|
||||
{"Hello 中国", "Hello 中", 2, true},
|
||||
{"Hello 中国", "Hello 中国", 3, false},
|
||||
{"Hello中国 Good 好的", "Hello中国 Good 好", 9, true},
|
||||
{"This is a sentence.", "This is", 2, true},
|
||||
{"This is also a sentence!", "This", 1, true},
|
||||
{"To be. Or not to be. That's the question.", "To be. Or not", 4, true},
|
||||
{" \nThis is not a sentence\n ", "This is not", 3, true},
|
||||
}
|
||||
for i, d := range data {
|
||||
cfg := config.New()
|
||||
cfg.Set("summaryLength", d.max)
|
||||
c := newTestContentSpec(cfg)
|
||||
output, truncated := c.TruncateWordsByRune(strings.Fields(d.input))
|
||||
if d.expected != output {
|
||||
t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
|
||||
}
|
||||
|
||||
if d.truncated != truncated {
|
||||
t.Errorf("Test %d failed. Expected truncated=%t got %t", i, d.truncated, truncated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractTOCNormalContent(t *testing.T) {
|
||||
content := []byte("<nav>\n<ul>\nTOC<li><a href=\"#")
|
||||
|
||||
|
||||
+8
-18
@@ -276,11 +276,13 @@ func TestLoadMultiConfig(t *testing.T) {
|
||||
// Add a random config variable for testing.
|
||||
// side = page in Norwegian.
|
||||
configContentBase := `
|
||||
Paginate = 32
|
||||
PaginatePath = "side"
|
||||
[pagination]
|
||||
pagerSize = 32
|
||||
path = "side"
|
||||
`
|
||||
configContentSub := `
|
||||
PaginatePath = "top"
|
||||
[pagination]
|
||||
path = "top"
|
||||
`
|
||||
mm := afero.NewMemMapFs()
|
||||
|
||||
@@ -292,8 +294,8 @@ func TestLoadMultiConfig(t *testing.T) {
|
||||
c.Assert(err, qt.IsNil)
|
||||
cfg := all.Base
|
||||
|
||||
c.Assert(cfg.PaginatePath, qt.Equals, "top")
|
||||
c.Assert(cfg.Paginate, qt.Equals, 32)
|
||||
c.Assert(cfg.Pagination.Path, qt.Equals, "top")
|
||||
c.Assert(cfg.Pagination.PagerSize, qt.Equals, 32)
|
||||
}
|
||||
|
||||
func TestLoadConfigFromThemes(t *testing.T) {
|
||||
@@ -698,10 +700,6 @@ func TestHugoConfig(t *testing.T) {
|
||||
filesTemplate := `
|
||||
-- hugo.toml --
|
||||
theme = "mytheme"
|
||||
[social]
|
||||
twitter = "bepsays"
|
||||
[author]
|
||||
name = "bep"
|
||||
[params]
|
||||
rootparam = "rootvalue"
|
||||
-- config/_default/hugo.toml --
|
||||
@@ -718,9 +716,6 @@ rootparam: {{ site.Params.rootparam }}
|
||||
rootconfigparam: {{ site.Params.rootconfigparam }}
|
||||
themeparam: {{ site.Params.themeparam }}
|
||||
themeconfigdirparam: {{ site.Params.themeconfigdirparam }}
|
||||
social: {{ site.Social }}
|
||||
author: {{ site.Author }}
|
||||
|
||||
|
||||
`
|
||||
|
||||
@@ -744,8 +739,6 @@ author: {{ site.Author }}
|
||||
"rootconfigparam: rootconfigvalue",
|
||||
"themeparam: themevalue",
|
||||
"themeconfigdirparam: themeconfigdirvalue",
|
||||
"social: map[twitter:bepsays]",
|
||||
"author: map[name:bep]",
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -918,11 +911,9 @@ title: "My Swedish Section"
|
||||
-- layouts/index.html --
|
||||
LanguageCode: {{ eq site.LanguageCode site.Language.LanguageCode }}|{{ site.Language.LanguageCode }}|
|
||||
{{ range $i, $e := (slice site .Site) }}
|
||||
{{ $i }}|AllPages: {{ len .AllPages }}|Sections: {{ if .Sections }}true{{ end }}| Author: {{ .Authors }}|BuildDrafts: {{ .BuildDrafts }}|IsMultilingual: {{ .IsMultiLingual }}|Param: {{ .Language.Params.myparam }}|Language string: {{ .Language }}|Languages: {{ .Languages }}
|
||||
{{ $i }}|AllPages: {{ len .AllPages }}|Sections: {{ if .Sections }}true{{ end }}|BuildDrafts: {{ .BuildDrafts }}|Param: {{ .Language.Params.myparam }}|Language string: {{ .Language }}|Languages: {{ .Languages }}
|
||||
{{ end }}
|
||||
|
||||
|
||||
|
||||
`
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
@@ -940,7 +931,6 @@ AllPages: 4|
|
||||
Sections: true|
|
||||
Param: enParamValue
|
||||
Param: enParamValue
|
||||
IsMultilingual: true
|
||||
LanguageCode: true|en-US|
|
||||
`)
|
||||
|
||||
|
||||
@@ -560,7 +560,7 @@ func (m *pageMap) getOrCreateResourcesForPage(ps *pageState) resource.Resources
|
||||
for _, r := range res2 {
|
||||
var found bool
|
||||
for _, r2 := range res {
|
||||
if r2.(resource.NameNormalizedProvider).NameNormalized() == r.(resource.NameNormalizedProvider).NameNormalized() {
|
||||
if resource.NameNormalizedOrName(r2) == resource.NameNormalizedOrName(r) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
|
||||
+22
-23
@@ -28,8 +28,8 @@ func TestContentMapSite(t *testing.T) {
|
||||
pageTempl := `
|
||||
---
|
||||
title: "Page %d"
|
||||
date: "2019-06-0%d"
|
||||
lastMod: "2019-06-0%d"
|
||||
date: "2019-06-%02d"
|
||||
lastMod: "2019-06-%02d"
|
||||
categories: [%q]
|
||||
---
|
||||
|
||||
@@ -55,7 +55,6 @@ draft: true
|
||||
title: "Hugo Home"
|
||||
cascade:
|
||||
description: "Common Description"
|
||||
|
||||
---
|
||||
|
||||
Home Content.
|
||||
@@ -64,24 +63,24 @@ Home Content.
|
||||
b.WithContent("blog/page1.md", createPage(1))
|
||||
b.WithContent("blog/page2.md", createPage(2))
|
||||
b.WithContent("blog/page3.md", createPage(3))
|
||||
b.WithContent("blog/bundle/index.md", createPage(12))
|
||||
b.WithContent("blog/bundle/index.md", createPage(4))
|
||||
b.WithContent("blog/bundle/data.json", "data")
|
||||
b.WithContent("blog/bundle/page.md", createPage(99))
|
||||
b.WithContent("blog/subsection/_index.md", createPage(3))
|
||||
b.WithContent("blog/bundle/page.md", createPage(5))
|
||||
b.WithContent("blog/subsection/_index.md", createPage(6))
|
||||
b.WithContent("blog/subsection/subdata.json", "data")
|
||||
b.WithContent("blog/subsection/page4.md", createPage(8))
|
||||
b.WithContent("blog/subsection/page5.md", createPage(10))
|
||||
b.WithContent("blog/subsection/page4.md", createPage(7))
|
||||
b.WithContent("blog/subsection/page5.md", createPage(8))
|
||||
b.WithContent("blog/subsection/draft/index.md", draftTemplate)
|
||||
b.WithContent("blog/subsection/draft/data.json", "data")
|
||||
b.WithContent("blog/draftsection/_index.md", draftTemplate)
|
||||
b.WithContent("blog/draftsection/page/index.md", createPage(12))
|
||||
b.WithContent("blog/draftsection/page/index.md", createPage(9))
|
||||
b.WithContent("blog/draftsection/page/folder/data.json", "data")
|
||||
b.WithContent("blog/draftsection/sub/_index.md", createPage(12))
|
||||
b.WithContent("blog/draftsection/sub/page.md", createPage(13))
|
||||
b.WithContent("docs/page6.md", createPage(11))
|
||||
b.WithContent("tags/_index.md", createPageInCategory(32, "sad"))
|
||||
b.WithContent("overlap/_index.md", createPageInCategory(33, "sad"))
|
||||
b.WithContent("overlap2/_index.md", createPage(34))
|
||||
b.WithContent("blog/draftsection/sub/_index.md", createPage(10))
|
||||
b.WithContent("blog/draftsection/sub/page.md", createPage(11))
|
||||
b.WithContent("docs/page6.md", createPage(12))
|
||||
b.WithContent("tags/_index.md", createPageInCategory(13, "sad"))
|
||||
b.WithContent("overlap/_index.md", createPageInCategory(14, "sad"))
|
||||
b.WithContent("overlap2/_index.md", createPage(15))
|
||||
|
||||
b.WithTemplatesAdded("layouts/index.html", `
|
||||
Num Regular: {{ len .Site.RegularPages }}|{{ range .Site.RegularPages }}{{ .RelPermalink }}|{{ end }}$
|
||||
@@ -118,7 +117,7 @@ Pages: {{ range $blog.Pages }}{{ .RelPermalink }}|{{ end }}
|
||||
Sections: {{ range $home.Sections }}{{ .RelPermalink }}|{{ end }}:END
|
||||
Categories: {{ range .Site.Taxonomies.categories }}{{ .Page.RelPermalink }}; {{ .Page.Title }}; {{ .Count }}|{{ end }}:END
|
||||
Category Terms: {{ $categories.Kind}}: {{ range $categories.Data.Terms.Alphabetical }}{{ .Page.RelPermalink }}; {{ .Page.Title }}; {{ .Count }}|{{ end }}:END
|
||||
Category Funny: {{ $funny.Kind}}; {{ $funny.Data.Term }}: {{ range $funny.Pages }}{{ .RelPermalink }};|{{ end }}:END
|
||||
Category Funny: {{ $funny.Kind}}; {{ $funny.Data.Term }}: {{ range $funny.Pages }}{{ .RelPermalink }}|{{ end }}:END
|
||||
Pag Num Pages: {{ len .Paginator.Pages }}
|
||||
Pag Blog Num Pages: {{ len $blog.Paginator.Pages }}
|
||||
Blog Num RegularPages: {{ len $blog.RegularPages }}|{{ range $blog.RegularPages }}P: {{ .RelPermalink }}|{{ end }}
|
||||
@@ -142,11 +141,11 @@ Draft5: {{ if (.Site.GetPage "blog/draftsection/sub/page") }}FOUND{{ end }}|
|
||||
Main Sections: [blog]
|
||||
Pag Num Pages: 9
|
||||
|
||||
Home: Hugo Home|/|2019-06-08|Current Section: /|Resources:
|
||||
Blog Section: Blogs|/blog/|2019-06-08|Current Section: /blog|Resources:
|
||||
Blog Sub Section: Page 3|/blog/subsection/|2019-06-03|Current Section: /blog/subsection|Resources: application: /blog/subsection/subdata.json|
|
||||
Home: Hugo Home|/|2019-06-15|Current Section: /|Resources:
|
||||
Blog Section: Blogs|/blog/|2019-06-11|Current Section: /blog|Resources:
|
||||
Blog Sub Section: Page 6|/blog/subsection/|2019-06-06|Current Section: /blog/subsection|Resources: application: /blog/subsection/subdata.json|
|
||||
Page: Page 1|/blog/page1/|2019-06-01|Current Section: /blog|Resources:
|
||||
Bundle: Page 12|/blog/bundle/|0001-01-01|Current Section: /blog|Resources: application: /blog/bundle/data.json|page: |
|
||||
Bundle: Page 4|/blog/bundle/|2019-06-04|Current Section: /blog|Resources: application: /blog/bundle/data.json|page: |
|
||||
IsDescendant: true: true true: true true: true true: true true: true false: false false: false
|
||||
IsAncestor: true: true true: true true: true true: true true: true true: true false: false false: false false: false false: false
|
||||
IsDescendant overlap1: false: false
|
||||
@@ -157,11 +156,11 @@ Draft5: {{ if (.Site.GetPage "blog/draftsection/sub/page") }}FOUND{{ end }}|
|
||||
InSection: true: true false: false
|
||||
Next: /blog/page3/
|
||||
NextInSection: /blog/page3/
|
||||
Pages: /blog/page3/|/blog/subsection/|/blog/page2/|/blog/page1/|/blog/bundle/|
|
||||
Sections: /blog/|/docs/|/overlap/|/overlap2/|:END
|
||||
Pages: /blog/subsection/|/blog/bundle/|/blog/page3/|/blog/page2/|/blog/page1/|
|
||||
Sections: /overlap2/|/overlap/|/docs/|/blog/|:END
|
||||
Categories: /categories/funny/; Funny; 12|/categories/sad/; Sad; 2|:END
|
||||
Category Terms: taxonomy: /categories/funny/; Funny; 12|/categories/sad/; Sad; 2|:END
|
||||
Category Funny: term; funny: /blog/subsection/page4/;|/blog/page3/;|/blog/subsection/;|/blog/page2/;|/blog/page1/;|/blog/subsection/page5/;|/docs/page6/;|/blog/bundle/;|/blog/draftsection/page/;|/blog/draftsection/sub/;|/blog/draftsection/sub/page/;|/overlap2/;|:END
|
||||
Category Funny: term; funny: /overlap2/|/docs/page6/|/blog/draftsection/sub/page/|/blog/draftsection/sub/|/blog/draftsection/page/|/blog/subsection/page5/|/blog/subsection/page4/|/blog/subsection/|/blog/bundle/|/blog/page3/|/blog/page2/|/blog/page1/|:END
|
||||
Pag Num Pages: 9
|
||||
Pag Blog Num Pages: 4
|
||||
Blog Num RegularPages: 4
|
||||
|
||||
@@ -98,8 +98,8 @@ baseURL="https://example.org"
|
||||
P1: {{ $p.Content }}
|
||||
|
||||
`,
|
||||
"_default/_markup/render-link.html", `html-link: {{ .Destination | safeURL }}|Text: {{ .Text | safeHTML }}|Plain: {{ .PlainText | safeHTML }}`,
|
||||
"_default/_markup/render-image.html", `html-image: {{ .Destination | safeURL }}|Text: {{ .Text | safeHTML }}|Plain: {{ .PlainText | safeHTML }}`,
|
||||
"_default/_markup/render-link.html", `html-link: {{ .Destination | safeURL }}|Text: {{ .Text }}|Plain: {{ .PlainText | safeHTML }}`,
|
||||
"_default/_markup/render-image.html", `html-image: {{ .Destination | safeURL }}|Text: {{ .Text }}|Plain: {{ .PlainText | safeHTML }}`,
|
||||
)
|
||||
|
||||
b.WithContent("p1.md", `---
|
||||
|
||||
@@ -100,7 +100,7 @@ func TestEmbeddedPaginationTemplate(t *testing.T) {
|
||||
|
||||
test := func(variant string, expectedOutput string) {
|
||||
b := newTestSitesBuilder(t)
|
||||
b.WithConfigFile("toml", `paginate = 1`)
|
||||
b.WithConfigFile("toml", `pagination.pagerSize = 1`)
|
||||
b.WithContent(
|
||||
"s1/p01.md", "---\ntitle: p01\n---",
|
||||
"s1/p02.md", "---\ntitle: p02\n---",
|
||||
|
||||
@@ -11,12 +11,14 @@ func TestMultihost(t *testing.T) {
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
paginate = 1
|
||||
defaultContentLanguage = "fr"
|
||||
defaultContentLanguageInSubdir = false
|
||||
staticDir = ["s1", "s2"]
|
||||
enableRobotsTXT = true
|
||||
|
||||
[pagination]
|
||||
pagerSize = 1
|
||||
|
||||
[permalinks]
|
||||
other = "/somewhere/else/:filename"
|
||||
|
||||
|
||||
@@ -107,10 +107,13 @@ func TestSmoke(t *testing.T) {
|
||||
baseURL = "https://example.com"
|
||||
title = "Smoke Site"
|
||||
rssLimit = 3
|
||||
paginate = 1
|
||||
defaultContentLanguage = "en"
|
||||
defaultContentLanguageInSubdir = true
|
||||
enableRobotsTXT = true
|
||||
|
||||
[pagination]
|
||||
pagerSize = 1
|
||||
|
||||
[taxonomies]
|
||||
category = 'categories'
|
||||
tag = 'tags'
|
||||
@@ -434,10 +437,10 @@ func TestDataRace(t *testing.T) {
|
||||
---
|
||||
title: "The Page"
|
||||
outputs: ["HTML", "JSON"]
|
||||
---
|
||||
---
|
||||
|
||||
The content.
|
||||
|
||||
|
||||
|
||||
`
|
||||
|
||||
@@ -450,10 +453,10 @@ The content.
|
||||
---
|
||||
title: "The Home"
|
||||
outputs: ["HTML", "JSON", "CSV", "RSS"]
|
||||
---
|
||||
---
|
||||
|
||||
The content.
|
||||
|
||||
|
||||
|
||||
`)
|
||||
|
||||
|
||||
+4
-5
@@ -61,6 +61,7 @@ var (
|
||||
pageTypesProvider = resource.NewResourceTypesProvider(media.Builtin.OctetType, pageResourceType)
|
||||
nopPageOutput = &pageOutput{
|
||||
pagePerOutputProviders: nopPagePerOutput,
|
||||
MarkupProvider: page.NopPage,
|
||||
ContentProvider: page.NopPage,
|
||||
}
|
||||
)
|
||||
@@ -213,11 +214,8 @@ func (p *pageHeadingsFiltered) page() page.Page {
|
||||
|
||||
// For internal use by the related content feature.
|
||||
func (p *pageState) ApplyFilterToHeadings(ctx context.Context, fn func(*tableofcontents.Heading) bool) related.Document {
|
||||
r, err := p.m.content.contentToC(ctx, p.pageOutput.pco)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
headings := r.tableOfContents.Headings.FilterBy(fn)
|
||||
fragments := p.pageOutput.pco.c().Fragments(ctx)
|
||||
headings := fragments.Headings.FilterBy(fn)
|
||||
return &pageHeadingsFiltered{
|
||||
pageState: p,
|
||||
headings: headings,
|
||||
@@ -719,6 +717,7 @@ func (p *pageState) shiftToOutputFormat(isRenderingSite bool, idx int) error {
|
||||
})
|
||||
p.pageOutput.contentRenderer = lcp
|
||||
p.pageOutput.ContentProvider = lcp
|
||||
p.pageOutput.MarkupProvider = lcp
|
||||
p.pageOutput.PageRenderProvider = lcp
|
||||
p.pageOutput.TableOfContentsProvider = lcp
|
||||
}
|
||||
|
||||
+470
-117
@@ -14,7 +14,6 @@
|
||||
package hugolib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -29,15 +28,23 @@ import (
|
||||
"github.com/gohugoio/hugo/common/hcontext"
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/types/hstring"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/markup"
|
||||
"github.com/gohugoio/hugo/markup/converter"
|
||||
"github.com/gohugoio/hugo/markup/goldmark/hugocontext"
|
||||
"github.com/gohugoio/hugo/markup/tableofcontents"
|
||||
"github.com/gohugoio/hugo/parser/metadecoders"
|
||||
"github.com/gohugoio/hugo/parser/pageparser"
|
||||
"github.com/gohugoio/hugo/resources"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/gohugoio/hugo/resources/resource"
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -45,8 +52,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
internalSummaryDividerBaseBytes = []byte(internalSummaryDividerBase)
|
||||
internalSummaryDividerPre = []byte("\n\n" + internalSummaryDividerBase + "\n\n")
|
||||
internalSummaryDividerPreString = "\n\n" + internalSummaryDividerBase + "\n\n"
|
||||
internalSummaryDividerPre = []byte(internalSummaryDividerPreString)
|
||||
)
|
||||
|
||||
type pageContentReplacement struct {
|
||||
@@ -130,6 +137,7 @@ func (m *pageMeta) newCachedContent(h *HugoSites, pi *contentParseInfo) (*cached
|
||||
shortcodeState: newShortcodeHandler(filename, m.s),
|
||||
pi: pi,
|
||||
enableEmoji: m.s.conf.EnableEmoji,
|
||||
scopes: maps.NewCache[string, *cachedContentScope](),
|
||||
}
|
||||
|
||||
source, err := c.pi.contentSource(m)
|
||||
@@ -155,6 +163,20 @@ type cachedContent struct {
|
||||
pi *contentParseInfo
|
||||
|
||||
enableEmoji bool
|
||||
|
||||
scopes *maps.Cache[string, *cachedContentScope]
|
||||
}
|
||||
|
||||
func (c *cachedContent) getOrCreateScope(scope string, pco *pageContentOutput) *cachedContentScope {
|
||||
key := scope + pco.po.f.Name
|
||||
cs, _ := c.scopes.GetOrCreate(key, func() (*cachedContentScope, error) {
|
||||
return &cachedContentScope{
|
||||
cachedContent: c,
|
||||
pco: pco,
|
||||
scope: scope,
|
||||
}, nil
|
||||
})
|
||||
return cs
|
||||
}
|
||||
|
||||
type contentParseInfo struct {
|
||||
@@ -171,9 +193,6 @@ type contentParseInfo struct {
|
||||
// Whether the parsed content contains a summary separator.
|
||||
hasSummaryDivider bool
|
||||
|
||||
// Whether there are more content after the summary divider.
|
||||
summaryTruncated bool
|
||||
|
||||
// Returns the position in bytes after any front matter.
|
||||
posMainContent int
|
||||
|
||||
@@ -368,8 +387,6 @@ Loop:
|
||||
}
|
||||
|
||||
if item.IsNonWhitespace(source) {
|
||||
rn.summaryTruncated = true
|
||||
|
||||
// Done
|
||||
return false
|
||||
}
|
||||
@@ -487,26 +504,28 @@ type contentTableOfContents struct {
|
||||
}
|
||||
|
||||
type contentSummary struct {
|
||||
content template.HTML
|
||||
summary template.HTML
|
||||
summaryTruncated bool
|
||||
content template.HTML
|
||||
contentWithoutSummary template.HTML
|
||||
summary page.Summary
|
||||
}
|
||||
|
||||
type contentPlainPlainWords struct {
|
||||
plain string
|
||||
plainWords []string
|
||||
|
||||
summary template.HTML
|
||||
summaryTruncated bool
|
||||
|
||||
wordCount int
|
||||
fuzzyWordCount int
|
||||
readingTime int
|
||||
}
|
||||
|
||||
func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutput) (contentSummary, error) {
|
||||
func (c *cachedContentScope) keyScope(ctx context.Context) string {
|
||||
return hugo.GetMarkupScope(ctx) + c.pco.po.f.Name
|
||||
}
|
||||
|
||||
func (c *cachedContentScope) contentRendered(ctx context.Context) (contentSummary, error) {
|
||||
cp := c.pco
|
||||
ctx = tpl.Context.DependencyScope.Set(ctx, pageDependencyScopeGlobal)
|
||||
key := c.pi.sourceKey + "/" + cp.po.f.Name
|
||||
key := c.pi.sourceKey + "/" + c.keyScope(ctx)
|
||||
versionv := c.version(cp)
|
||||
|
||||
v, err := c.pm.cacheContentRendered.GetOrCreate(key, func(string) (*resources.StaleValue[contentSummary], error) {
|
||||
@@ -515,97 +534,122 @@ func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutp
|
||||
}))
|
||||
|
||||
cp.po.p.s.h.contentRenderCounter.Add(1)
|
||||
cp.contentRendered = true
|
||||
cp.contentRendered.Store(true)
|
||||
po := cp.po
|
||||
|
||||
ct, err := c.contentToC(ctx, cp)
|
||||
ct, err := c.contentToC(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rs := &resources.StaleValue[contentSummary]{
|
||||
StaleVersionFunc: func() uint32 {
|
||||
return c.version(cp) - versionv
|
||||
},
|
||||
}
|
||||
|
||||
if len(c.pi.itemsStep2) == 0 {
|
||||
// Nothing to do.
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
var b []byte
|
||||
|
||||
if ct.astDoc != nil {
|
||||
// The content is parsed, but not rendered.
|
||||
r, ok, err := po.contentRenderer.RenderContent(ctx, ct.contentToRender, ct.astDoc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
rs, err := func() (*resources.StaleValue[contentSummary], error) {
|
||||
rs := &resources.StaleValue[contentSummary]{
|
||||
StaleVersionFunc: func() uint32 {
|
||||
return c.version(cp) - versionv
|
||||
},
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return nil, errors.New("invalid state: astDoc is set but RenderContent returned false")
|
||||
if len(c.pi.itemsStep2) == 0 {
|
||||
// Nothing to do.
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
b = r.Bytes()
|
||||
var b []byte
|
||||
|
||||
} else {
|
||||
// Copy the content to be rendered.
|
||||
b = make([]byte, len(ct.contentToRender))
|
||||
copy(b, ct.contentToRender)
|
||||
}
|
||||
|
||||
// There are one or more replacement tokens to be replaced.
|
||||
var hasShortcodeVariants bool
|
||||
tokenHandler := func(ctx context.Context, token string) ([]byte, error) {
|
||||
if token == tocShortcodePlaceholder {
|
||||
return []byte(ct.tableOfContentsHTML), nil
|
||||
}
|
||||
renderer, found := ct.contentPlaceholders[token]
|
||||
if found {
|
||||
repl, more, err := renderer.renderShortcode(ctx)
|
||||
if ct.astDoc != nil {
|
||||
// The content is parsed, but not rendered.
|
||||
r, ok, err := po.contentRenderer.RenderContent(ctx, ct.contentToRender, ct.astDoc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hasShortcodeVariants = hasShortcodeVariants || more
|
||||
return repl, nil
|
||||
}
|
||||
// This should never happen.
|
||||
panic(fmt.Errorf("unknown shortcode token %q (number of tokens: %d)", token, len(ct.contentPlaceholders)))
|
||||
}
|
||||
|
||||
b, err = expandShortcodeTokens(ctx, b, tokenHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hasShortcodeVariants {
|
||||
cp.po.p.pageOutputTemplateVariationsState.Add(1)
|
||||
}
|
||||
if !ok {
|
||||
return nil, errors.New("invalid state: astDoc is set but RenderContent returned false")
|
||||
}
|
||||
|
||||
var result contentSummary // hasVariants bool
|
||||
|
||||
if c.pi.hasSummaryDivider {
|
||||
if cp.po.p.m.pageConfig.ContentMediaType.IsHTML() {
|
||||
// Use the summary sections as provided by the user.
|
||||
i := bytes.Index(b, internalSummaryDividerPre)
|
||||
result.summary = helpers.BytesToHTML(b[:i])
|
||||
b = b[i+len(internalSummaryDividerPre):]
|
||||
b = r.Bytes()
|
||||
|
||||
} else {
|
||||
summary, content, err := splitUserDefinedSummaryAndContent(cp.po.p.m.pageConfig.Content.Markup, b)
|
||||
if err != nil {
|
||||
cp.po.p.s.Log.Errorf("Failed to set user defined summary for page %q: %s", cp.po.p.pathOrTitle(), err)
|
||||
} else {
|
||||
b = content
|
||||
result.summary = helpers.BytesToHTML(summary)
|
||||
}
|
||||
// Copy the content to be rendered.
|
||||
b = make([]byte, len(ct.contentToRender))
|
||||
copy(b, ct.contentToRender)
|
||||
}
|
||||
result.summaryTruncated = c.pi.summaryTruncated
|
||||
}
|
||||
result.content = helpers.BytesToHTML(b)
|
||||
rs.Value = result
|
||||
|
||||
return rs, nil
|
||||
// There are one or more replacement tokens to be replaced.
|
||||
var hasShortcodeVariants bool
|
||||
tokenHandler := func(ctx context.Context, token string) ([]byte, error) {
|
||||
if token == tocShortcodePlaceholder {
|
||||
return []byte(ct.tableOfContentsHTML), nil
|
||||
}
|
||||
renderer, found := ct.contentPlaceholders[token]
|
||||
if found {
|
||||
repl, more, err := renderer.renderShortcode(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hasShortcodeVariants = hasShortcodeVariants || more
|
||||
return repl, nil
|
||||
}
|
||||
// This should never happen.
|
||||
panic(fmt.Errorf("unknown shortcode token %q (number of tokens: %d)", token, len(ct.contentPlaceholders)))
|
||||
}
|
||||
|
||||
b, err = expandShortcodeTokens(ctx, b, tokenHandler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hasShortcodeVariants {
|
||||
cp.po.p.pageOutputTemplateVariationsState.Add(1)
|
||||
}
|
||||
|
||||
var result contentSummary
|
||||
if c.pi.hasSummaryDivider {
|
||||
s := string(b)
|
||||
summarized := page.ExtractSummaryFromHTMLWithDivider(cp.po.p.m.pageConfig.ContentMediaType, s, internalSummaryDividerBase)
|
||||
result.summary = page.Summary{
|
||||
Text: template.HTML(summarized.Summary()),
|
||||
Type: page.SummaryTypeManual,
|
||||
Truncated: summarized.Truncated(),
|
||||
}
|
||||
result.contentWithoutSummary = template.HTML(summarized.ContentWithoutSummary())
|
||||
result.content = template.HTML(summarized.Content())
|
||||
} else {
|
||||
result.content = template.HTML(string(b))
|
||||
}
|
||||
|
||||
if !c.pi.hasSummaryDivider && cp.po.p.m.pageConfig.Summary == "" {
|
||||
numWords := cp.po.p.s.conf.SummaryLength
|
||||
isCJKLanguage := cp.po.p.m.pageConfig.IsCJKLanguage
|
||||
summary := page.ExtractSummaryFromHTML(cp.po.p.m.pageConfig.ContentMediaType, string(result.content), numWords, isCJKLanguage)
|
||||
result.summary = page.Summary{
|
||||
Text: template.HTML(summary.Summary()),
|
||||
Type: page.SummaryTypeAuto,
|
||||
Truncated: summary.Truncated(),
|
||||
}
|
||||
result.contentWithoutSummary = template.HTML(summary.ContentWithoutSummary())
|
||||
}
|
||||
rs.Value = result
|
||||
|
||||
return rs, nil
|
||||
}()
|
||||
if err != nil {
|
||||
return rs, cp.po.p.wrapError(err)
|
||||
}
|
||||
|
||||
if rs.Value.summary.IsZero() {
|
||||
b, err := cp.po.contentRenderer.ParseAndRenderContent(ctx, []byte(cp.po.p.m.pageConfig.Summary), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
html := cp.po.p.s.ContentSpec.TrimShortHTML(b.Bytes(), cp.po.p.m.pageConfig.Content.Markup)
|
||||
rs.Value.summary = page.Summary{
|
||||
Text: helpers.BytesToHTML(html),
|
||||
Type: page.SummaryTypeFrontMatter,
|
||||
}
|
||||
rs.Value.contentWithoutSummary = rs.Value.content
|
||||
}
|
||||
|
||||
return rs, err
|
||||
})
|
||||
if err != nil {
|
||||
return contentSummary{}, cp.po.p.wrapError(err)
|
||||
@@ -614,8 +658,8 @@ func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutp
|
||||
return v.Value, nil
|
||||
}
|
||||
|
||||
func (c *cachedContent) mustContentToC(ctx context.Context, cp *pageContentOutput) contentTableOfContents {
|
||||
ct, err := c.contentToC(ctx, cp)
|
||||
func (c *cachedContentScope) mustContentToC(ctx context.Context) contentTableOfContents {
|
||||
ct, err := c.contentToC(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -624,8 +668,9 @@ func (c *cachedContent) mustContentToC(ctx context.Context, cp *pageContentOutpu
|
||||
|
||||
var setGetContentCallbackInContext = hcontext.NewContextDispatcher[func(*pageContentOutput, contentTableOfContents)]("contentCallback")
|
||||
|
||||
func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (contentTableOfContents, error) {
|
||||
key := c.pi.sourceKey + "/" + cp.po.f.Name
|
||||
func (c *cachedContentScope) contentToC(ctx context.Context) (contentTableOfContents, error) {
|
||||
cp := c.pco
|
||||
key := c.pi.sourceKey + "/" + c.keyScope(ctx)
|
||||
versionv := c.version(cp)
|
||||
|
||||
v, err := c.pm.contentTableOfContents.GetOrCreate(key, func(string) (*resources.StaleValue[contentTableOfContents], error) {
|
||||
@@ -648,7 +693,7 @@ func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (
|
||||
|
||||
// Callback called from below (e.g. in .RenderString)
|
||||
ctxCallback := func(cp2 *pageContentOutput, ct2 contentTableOfContents) {
|
||||
cp.otherOutputs[cp2.po.p.pid] = cp2
|
||||
cp.otherOutputs.Set(cp2.po.p.pid, cp2)
|
||||
|
||||
// Merge content placeholders
|
||||
for k, v := range ct2.contentPlaceholders {
|
||||
@@ -749,8 +794,9 @@ func (c *cachedContent) version(cp *pageContentOutput) uint32 {
|
||||
return c.StaleVersion() + cp.contentRenderedVersion
|
||||
}
|
||||
|
||||
func (c *cachedContent) contentPlain(ctx context.Context, cp *pageContentOutput) (contentPlainPlainWords, error) {
|
||||
key := c.pi.sourceKey + "/" + cp.po.f.Name
|
||||
func (c *cachedContentScope) contentPlain(ctx context.Context) (contentPlainPlainWords, error) {
|
||||
cp := c.pco
|
||||
key := c.pi.sourceKey + "/" + c.keyScope(ctx)
|
||||
|
||||
versionv := c.version(cp)
|
||||
|
||||
@@ -762,7 +808,7 @@ func (c *cachedContent) contentPlain(ctx context.Context, cp *pageContentOutput)
|
||||
},
|
||||
}
|
||||
|
||||
rendered, err := c.contentRendered(ctx, cp)
|
||||
rendered, err := c.contentRendered(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -797,28 +843,6 @@ func (c *cachedContent) contentPlain(ctx context.Context, cp *pageContentOutput)
|
||||
result.readingTime = (result.wordCount + 212) / 213
|
||||
}
|
||||
|
||||
if c.pi.hasSummaryDivider || rendered.summary != "" {
|
||||
result.summary = rendered.summary
|
||||
result.summaryTruncated = rendered.summaryTruncated
|
||||
} else if cp.po.p.m.pageConfig.Summary != "" {
|
||||
b, err := cp.po.contentRenderer.ParseAndRenderContent(ctx, []byte(cp.po.p.m.pageConfig.Summary), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
html := cp.po.p.s.ContentSpec.TrimShortHTML(b.Bytes(), cp.po.p.m.pageConfig.Content.Markup)
|
||||
result.summary = helpers.BytesToHTML(html)
|
||||
} else {
|
||||
var summary string
|
||||
var truncated bool
|
||||
if isCJKLanguage {
|
||||
summary, truncated = cp.po.p.s.ContentSpec.TruncateWordsByRune(result.plainWords)
|
||||
} else {
|
||||
summary, truncated = cp.po.p.s.ContentSpec.TruncateWordsToWholeSentence(result.plain)
|
||||
}
|
||||
result.summary = template.HTML(summary)
|
||||
result.summaryTruncated = truncated
|
||||
}
|
||||
|
||||
rs.Value = result
|
||||
|
||||
return rs, nil
|
||||
@@ -831,3 +855,332 @@ func (c *cachedContent) contentPlain(ctx context.Context, cp *pageContentOutput)
|
||||
}
|
||||
return v.Value, nil
|
||||
}
|
||||
|
||||
type cachedContentScope struct {
|
||||
*cachedContent
|
||||
pco *pageContentOutput
|
||||
scope string
|
||||
}
|
||||
|
||||
func (c *cachedContentScope) prepareContext(ctx context.Context) context.Context {
|
||||
// The markup scope is recursive, so if already set to a non zero value, preserve that value.
|
||||
if s := hugo.GetMarkupScope(ctx); s != "" || s == c.scope {
|
||||
return ctx
|
||||
}
|
||||
return hugo.SetMarkupScope(ctx, c.scope)
|
||||
}
|
||||
|
||||
func (c *cachedContentScope) Render(ctx context.Context) (page.Content, error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *cachedContentScope) Content(ctx context.Context) (template.HTML, error) {
|
||||
ctx = c.prepareContext(ctx)
|
||||
cr, err := c.contentRendered(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cr.content, nil
|
||||
}
|
||||
|
||||
func (c *cachedContentScope) ContentWithoutSummary(ctx context.Context) (template.HTML, error) {
|
||||
ctx = c.prepareContext(ctx)
|
||||
cr, err := c.contentRendered(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cr.contentWithoutSummary, nil
|
||||
}
|
||||
|
||||
func (c *cachedContentScope) Summary(ctx context.Context) (page.Summary, error) {
|
||||
ctx = c.prepareContext(ctx)
|
||||
rendered, err := c.contentRendered(ctx)
|
||||
return rendered.summary, err
|
||||
}
|
||||
|
||||
func (c *cachedContentScope) RenderString(ctx context.Context, args ...any) (template.HTML, error) {
|
||||
ctx = c.prepareContext(ctx)
|
||||
|
||||
if len(args) < 1 || len(args) > 2 {
|
||||
return "", errors.New("want 1 or 2 arguments")
|
||||
}
|
||||
|
||||
pco := c.pco
|
||||
|
||||
var contentToRender string
|
||||
opts := defaultRenderStringOpts
|
||||
sidx := 1
|
||||
|
||||
if len(args) == 1 {
|
||||
sidx = 0
|
||||
} else {
|
||||
m, ok := args[0].(map[string]any)
|
||||
if !ok {
|
||||
return "", errors.New("first argument must be a map")
|
||||
}
|
||||
|
||||
if err := mapstructure.WeakDecode(m, &opts); err != nil {
|
||||
return "", fmt.Errorf("failed to decode options: %w", err)
|
||||
}
|
||||
if opts.Markup != "" {
|
||||
opts.Markup = markup.ResolveMarkup(opts.Markup)
|
||||
}
|
||||
}
|
||||
|
||||
contentToRenderv := args[sidx]
|
||||
|
||||
if _, ok := contentToRenderv.(hstring.HTML); ok {
|
||||
// This content is already rendered, this is potentially
|
||||
// a infinite recursion.
|
||||
return "", errors.New("text is already rendered, repeating it may cause infinite recursion")
|
||||
}
|
||||
|
||||
var err error
|
||||
contentToRender, err = cast.ToStringE(contentToRenderv)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = pco.initRenderHooks(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
conv := pco.po.p.getContentConverter()
|
||||
|
||||
if opts.Markup != "" && opts.Markup != pco.po.p.m.pageConfig.ContentMediaType.SubType {
|
||||
var err error
|
||||
conv, err = pco.po.p.m.newContentConverter(pco.po.p, opts.Markup)
|
||||
if err != nil {
|
||||
return "", pco.po.p.wrapError(err)
|
||||
}
|
||||
}
|
||||
|
||||
var rendered []byte
|
||||
|
||||
parseInfo := &contentParseInfo{
|
||||
h: pco.po.p.s.h,
|
||||
pid: pco.po.p.pid,
|
||||
}
|
||||
|
||||
if pageparser.HasShortcode(contentToRender) {
|
||||
contentToRenderb := []byte(contentToRender)
|
||||
// String contains a shortcode.
|
||||
parseInfo.itemsStep1, err = pageparser.ParseBytes(contentToRenderb, pageparser.Config{
|
||||
NoFrontMatter: true,
|
||||
NoSummaryDivider: true,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
s := newShortcodeHandler(pco.po.p.pathOrTitle(), pco.po.p.s)
|
||||
if err := parseInfo.mapItemsAfterFrontMatter(contentToRenderb, s); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
placeholders, err := s.prepareShortcodesForPage(ctx, pco.po.p, pco.po.f, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
contentToRender, hasVariants, err := parseInfo.contentToRender(ctx, contentToRenderb, placeholders)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if hasVariants {
|
||||
pco.po.p.pageOutputTemplateVariationsState.Add(1)
|
||||
}
|
||||
b, err := pco.renderContentWithConverter(ctx, conv, contentToRender, false)
|
||||
if err != nil {
|
||||
return "", pco.po.p.wrapError(err)
|
||||
}
|
||||
rendered = b.Bytes()
|
||||
|
||||
if parseInfo.hasNonMarkdownShortcode {
|
||||
var hasShortcodeVariants bool
|
||||
|
||||
tokenHandler := func(ctx context.Context, token string) ([]byte, error) {
|
||||
if token == tocShortcodePlaceholder {
|
||||
toc, err := c.contentToC(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The Page's TableOfContents was accessed in a shortcode.
|
||||
return []byte(toc.tableOfContentsHTML), nil
|
||||
}
|
||||
renderer, found := placeholders[token]
|
||||
if found {
|
||||
repl, more, err := renderer.renderShortcode(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hasShortcodeVariants = hasShortcodeVariants || more
|
||||
return repl, nil
|
||||
}
|
||||
// This should not happen.
|
||||
return nil, fmt.Errorf("unknown shortcode token %q", token)
|
||||
}
|
||||
|
||||
rendered, err = expandShortcodeTokens(ctx, rendered, tokenHandler)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if hasShortcodeVariants {
|
||||
pco.po.p.pageOutputTemplateVariationsState.Add(1)
|
||||
}
|
||||
}
|
||||
|
||||
// We need a consolidated view in $page.HasShortcode
|
||||
pco.po.p.m.content.shortcodeState.transferNames(s)
|
||||
|
||||
} else {
|
||||
c, err := pco.renderContentWithConverter(ctx, conv, []byte(contentToRender), false)
|
||||
if err != nil {
|
||||
return "", pco.po.p.wrapError(err)
|
||||
}
|
||||
|
||||
rendered = c.Bytes()
|
||||
}
|
||||
|
||||
if opts.Display == "inline" {
|
||||
markup := pco.po.p.m.pageConfig.Content.Markup
|
||||
if opts.Markup != "" {
|
||||
markup = pco.po.p.s.ContentSpec.ResolveMarkup(opts.Markup)
|
||||
}
|
||||
rendered = pco.po.p.s.ContentSpec.TrimShortHTML(rendered, markup)
|
||||
}
|
||||
|
||||
return template.HTML(string(rendered)), nil
|
||||
}
|
||||
|
||||
func (c *cachedContentScope) RenderShortcodes(ctx context.Context) (template.HTML, error) {
|
||||
ctx = c.prepareContext(ctx)
|
||||
|
||||
pco := c.pco
|
||||
content := pco.po.p.m.content
|
||||
|
||||
source, err := content.pi.contentSource(content)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ct, err := c.contentToC(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var insertPlaceholders bool
|
||||
var hasVariants bool
|
||||
cb := setGetContentCallbackInContext.Get(ctx)
|
||||
if cb != nil {
|
||||
insertPlaceholders = true
|
||||
}
|
||||
cc := make([]byte, 0, len(source)+(len(source)/10))
|
||||
for _, it := range content.pi.itemsStep2 {
|
||||
switch v := it.(type) {
|
||||
case pageparser.Item:
|
||||
cc = append(cc, source[v.Pos():v.Pos()+len(v.Val(source))]...)
|
||||
case pageContentReplacement:
|
||||
// Ignore.
|
||||
case *shortcode:
|
||||
if !insertPlaceholders || !v.insertPlaceholder() {
|
||||
// Insert the rendered shortcode.
|
||||
renderedShortcode, found := ct.contentPlaceholders[v.placeholder]
|
||||
if !found {
|
||||
// This should never happen.
|
||||
panic(fmt.Sprintf("rendered shortcode %q not found", v.placeholder))
|
||||
}
|
||||
|
||||
b, more, err := renderedShortcode.renderShortcode(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to render shortcode: %w", err)
|
||||
}
|
||||
hasVariants = hasVariants || more
|
||||
cc = append(cc, []byte(b)...)
|
||||
|
||||
} else {
|
||||
// Insert the placeholder so we can insert the content after
|
||||
// markdown processing.
|
||||
cc = append(cc, []byte(v.placeholder)...)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown item type %T", it))
|
||||
}
|
||||
}
|
||||
|
||||
if hasVariants {
|
||||
pco.po.p.pageOutputTemplateVariationsState.Add(1)
|
||||
}
|
||||
|
||||
if cb != nil {
|
||||
cb(pco, ct)
|
||||
}
|
||||
|
||||
if tpl.Context.IsInGoldmark.Get(ctx) {
|
||||
// This content will be parsed and rendered by Goldmark.
|
||||
// Wrap it in a special Hugo markup to assign the correct Page from
|
||||
// the stack.
|
||||
return template.HTML(hugocontext.Wrap(cc, pco.po.p.pid)), nil
|
||||
}
|
||||
|
||||
return helpers.BytesToHTML(cc), nil
|
||||
}
|
||||
|
||||
func (c *cachedContentScope) Plain(ctx context.Context) string {
|
||||
ctx = c.prepareContext(ctx)
|
||||
return c.mustContentPlain(ctx).plain
|
||||
}
|
||||
|
||||
func (c *cachedContentScope) PlainWords(ctx context.Context) []string {
|
||||
ctx = c.prepareContext(ctx)
|
||||
return c.mustContentPlain(ctx).plainWords
|
||||
}
|
||||
|
||||
func (c *cachedContentScope) WordCount(ctx context.Context) int {
|
||||
ctx = c.prepareContext(ctx)
|
||||
return c.mustContentPlain(ctx).wordCount
|
||||
}
|
||||
|
||||
func (c *cachedContentScope) FuzzyWordCount(ctx context.Context) int {
|
||||
ctx = c.prepareContext(ctx)
|
||||
return c.mustContentPlain(ctx).fuzzyWordCount
|
||||
}
|
||||
|
||||
func (c *cachedContentScope) ReadingTime(ctx context.Context) int {
|
||||
ctx = c.prepareContext(ctx)
|
||||
return c.mustContentPlain(ctx).readingTime
|
||||
}
|
||||
|
||||
func (c *cachedContentScope) Len(ctx context.Context) int {
|
||||
ctx = c.prepareContext(ctx)
|
||||
return len(c.mustContentRendered(ctx).content)
|
||||
}
|
||||
|
||||
func (c *cachedContentScope) Fragments(ctx context.Context) *tableofcontents.Fragments {
|
||||
ctx = c.prepareContext(ctx)
|
||||
toc := c.mustContentToC(ctx).tableOfContents
|
||||
if toc == nil {
|
||||
return nil
|
||||
}
|
||||
return toc
|
||||
}
|
||||
|
||||
func (c *cachedContentScope) fragmentsHTML(ctx context.Context) template.HTML {
|
||||
ctx = c.prepareContext(ctx)
|
||||
return c.mustContentToC(ctx).tableOfContentsHTML
|
||||
}
|
||||
|
||||
func (c *cachedContentScope) mustContentPlain(ctx context.Context) contentPlainPlainWords {
|
||||
r, err := c.contentPlain(ctx)
|
||||
if err != nil {
|
||||
c.pco.fail(err)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (c *cachedContentScope) mustContentRendered(ctx context.Context) contentSummary {
|
||||
r, err := c.contentRendered(ctx)
|
||||
if err != nil {
|
||||
c.pco.fail(err)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -425,6 +425,7 @@ func (p *pageState) setMetaPostParams() error {
|
||||
ModTime: mtime,
|
||||
GitAuthorDate: gitAuthorDate,
|
||||
Location: langs.GetLocation(pm.s.Language()),
|
||||
PathOrTitle: p.pathOrTitle(),
|
||||
}
|
||||
|
||||
// Handle the date separately
|
||||
@@ -821,7 +822,7 @@ func (p *pageMeta) newContentConverter(ps *pageState, markup string) (converter.
|
||||
// This prevents infinite recursion in some cases.
|
||||
return doc
|
||||
}
|
||||
if v, ok := ps.pageOutput.pco.otherOutputs[id]; ok {
|
||||
if v, ok := ps.pageOutput.pco.otherOutputs.Get(id); ok {
|
||||
return v.po.p
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -65,6 +65,7 @@ func newPageOutput(
|
||||
p: ps,
|
||||
f: f,
|
||||
pagePerOutputProviders: providers,
|
||||
MarkupProvider: page.NopPage,
|
||||
ContentProvider: page.NopPage,
|
||||
PageRenderProvider: page.NopPage,
|
||||
TableOfContentsProvider: page.NopPage,
|
||||
@@ -95,6 +96,7 @@ type pageOutput struct {
|
||||
// output format.
|
||||
contentRenderer page.ContentRenderer
|
||||
pagePerOutputProviders
|
||||
page.MarkupProvider
|
||||
page.ContentProvider
|
||||
page.PageRenderProvider
|
||||
page.TableOfContentsProvider
|
||||
@@ -119,7 +121,7 @@ func (po *pageOutput) isRendered() bool {
|
||||
if po.renderState > 0 {
|
||||
return true
|
||||
}
|
||||
if po.pco != nil && po.pco.contentRendered {
|
||||
if po.pco != nil && po.pco.contentRendered.Load() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -139,6 +141,7 @@ func (p *pageOutput) setContentProvider(cp *pageContentOutput) {
|
||||
}
|
||||
p.contentRenderer = cp
|
||||
p.ContentProvider = cp
|
||||
p.MarkupProvider = cp
|
||||
p.PageRenderProvider = cp
|
||||
p.TableOfContentsProvider = cp
|
||||
p.RenderShortcodesProvider = cp
|
||||
|
||||
+120
-360
@@ -21,18 +21,14 @@ import (
|
||||
"html/template"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/text"
|
||||
"github.com/gohugoio/hugo/common/types/hstring"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/markup"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/parser/pageparser"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/gohugoio/hugo/markup/converter/hooks"
|
||||
"github.com/gohugoio/hugo/markup/goldmark/hugocontext"
|
||||
"github.com/gohugoio/hugo/markup/highlight/chromalexers"
|
||||
"github.com/gohugoio/hugo/markup/tableofcontents"
|
||||
|
||||
@@ -41,7 +37,6 @@ import (
|
||||
bp "github.com/gohugoio/hugo/bufferpool"
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/output"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/gohugoio/hugo/resources/resource"
|
||||
@@ -73,7 +68,7 @@ func newPageContentOutput(po *pageOutput) (*pageContentOutput, error) {
|
||||
cp := &pageContentOutput{
|
||||
po: po,
|
||||
renderHooks: &renderHooks{},
|
||||
otherOutputs: make(map[uint64]*pageContentOutput),
|
||||
otherOutputs: maps.NewCache[uint64, *pageContentOutput](),
|
||||
}
|
||||
return cp, nil
|
||||
}
|
||||
@@ -89,10 +84,10 @@ type pageContentOutput struct {
|
||||
|
||||
// Other pages involved in rendering of this page,
|
||||
// typically included with .RenderShortcodes.
|
||||
otherOutputs map[uint64]*pageContentOutput
|
||||
otherOutputs *maps.Cache[uint64, *pageContentOutput]
|
||||
|
||||
contentRenderedVersion uint32 // Incremented on reset.
|
||||
contentRendered bool // Set on content render.
|
||||
contentRenderedVersion uint32 // Incremented on reset.
|
||||
contentRendered atomic.Bool // Set on content render.
|
||||
|
||||
// Renders Markdown hooks.
|
||||
renderHooks *renderHooks
|
||||
@@ -107,294 +102,10 @@ func (pco *pageContentOutput) Reset() {
|
||||
return
|
||||
}
|
||||
pco.contentRenderedVersion++
|
||||
pco.contentRendered = false
|
||||
pco.contentRendered.Store(false)
|
||||
pco.renderHooks = &renderHooks{}
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) Fragments(ctx context.Context) *tableofcontents.Fragments {
|
||||
return pco.po.p.m.content.mustContentToC(ctx, pco).tableOfContents
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) RenderShortcodes(ctx context.Context) (template.HTML, error) {
|
||||
content := pco.po.p.m.content
|
||||
source, err := content.pi.contentSource(content)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ct, err := content.contentToC(ctx, pco)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var insertPlaceholders bool
|
||||
var hasVariants bool
|
||||
cb := setGetContentCallbackInContext.Get(ctx)
|
||||
if cb != nil {
|
||||
insertPlaceholders = true
|
||||
}
|
||||
c := make([]byte, 0, len(source)+(len(source)/10))
|
||||
for _, it := range content.pi.itemsStep2 {
|
||||
switch v := it.(type) {
|
||||
case pageparser.Item:
|
||||
c = append(c, source[v.Pos():v.Pos()+len(v.Val(source))]...)
|
||||
case pageContentReplacement:
|
||||
// Ignore.
|
||||
case *shortcode:
|
||||
if !insertPlaceholders || !v.insertPlaceholder() {
|
||||
// Insert the rendered shortcode.
|
||||
renderedShortcode, found := ct.contentPlaceholders[v.placeholder]
|
||||
if !found {
|
||||
// This should never happen.
|
||||
panic(fmt.Sprintf("rendered shortcode %q not found", v.placeholder))
|
||||
}
|
||||
|
||||
b, more, err := renderedShortcode.renderShortcode(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to render shortcode: %w", err)
|
||||
}
|
||||
hasVariants = hasVariants || more
|
||||
c = append(c, []byte(b)...)
|
||||
|
||||
} else {
|
||||
// Insert the placeholder so we can insert the content after
|
||||
// markdown processing.
|
||||
c = append(c, []byte(v.placeholder)...)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown item type %T", it))
|
||||
}
|
||||
}
|
||||
|
||||
if hasVariants {
|
||||
pco.po.p.pageOutputTemplateVariationsState.Add(1)
|
||||
}
|
||||
|
||||
if cb != nil {
|
||||
cb(pco, ct)
|
||||
}
|
||||
|
||||
if tpl.Context.IsInGoldmark.Get(ctx) {
|
||||
// This content will be parsed and rendered by Goldmark.
|
||||
// Wrap it in a special Hugo markup to assign the correct Page from
|
||||
// the stack.
|
||||
return template.HTML(hugocontext.Wrap(c, pco.po.p.pid)), nil
|
||||
}
|
||||
|
||||
return helpers.BytesToHTML(c), nil
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) Content(ctx context.Context) (any, error) {
|
||||
r, err := pco.po.p.m.content.contentRendered(ctx, pco)
|
||||
return r.content, err
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) TableOfContents(ctx context.Context) template.HTML {
|
||||
return pco.po.p.m.content.mustContentToC(ctx, pco).tableOfContentsHTML
|
||||
}
|
||||
|
||||
func (p *pageContentOutput) Len(ctx context.Context) int {
|
||||
return len(p.mustContentRendered(ctx).content)
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) mustContentRendered(ctx context.Context) contentSummary {
|
||||
r, err := pco.po.p.m.content.contentRendered(ctx, pco)
|
||||
if err != nil {
|
||||
pco.fail(err)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) mustContentPlain(ctx context.Context) contentPlainPlainWords {
|
||||
r, err := pco.po.p.m.content.contentPlain(ctx, pco)
|
||||
if err != nil {
|
||||
pco.fail(err)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) fail(err error) {
|
||||
pco.po.p.s.h.FatalError(pco.po.p.wrapError(err))
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) Plain(ctx context.Context) string {
|
||||
return pco.mustContentPlain(ctx).plain
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) PlainWords(ctx context.Context) []string {
|
||||
return pco.mustContentPlain(ctx).plainWords
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) ReadingTime(ctx context.Context) int {
|
||||
return pco.mustContentPlain(ctx).readingTime
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) WordCount(ctx context.Context) int {
|
||||
return pco.mustContentPlain(ctx).wordCount
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) FuzzyWordCount(ctx context.Context) int {
|
||||
return pco.mustContentPlain(ctx).fuzzyWordCount
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) Summary(ctx context.Context) template.HTML {
|
||||
return pco.mustContentPlain(ctx).summary
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) Truncated(ctx context.Context) bool {
|
||||
return pco.mustContentPlain(ctx).summaryTruncated
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) RenderString(ctx context.Context, args ...any) (template.HTML, error) {
|
||||
if len(args) < 1 || len(args) > 2 {
|
||||
return "", errors.New("want 1 or 2 arguments")
|
||||
}
|
||||
|
||||
var contentToRender string
|
||||
opts := defaultRenderStringOpts
|
||||
sidx := 1
|
||||
|
||||
if len(args) == 1 {
|
||||
sidx = 0
|
||||
} else {
|
||||
m, ok := args[0].(map[string]any)
|
||||
if !ok {
|
||||
return "", errors.New("first argument must be a map")
|
||||
}
|
||||
|
||||
if err := mapstructure.WeakDecode(m, &opts); err != nil {
|
||||
return "", fmt.Errorf("failed to decode options: %w", err)
|
||||
}
|
||||
if opts.Markup != "" {
|
||||
opts.Markup = markup.ResolveMarkup(opts.Markup)
|
||||
}
|
||||
}
|
||||
|
||||
contentToRenderv := args[sidx]
|
||||
|
||||
if _, ok := contentToRenderv.(hstring.RenderedString); ok {
|
||||
// This content is already rendered, this is potentially
|
||||
// a infinite recursion.
|
||||
return "", errors.New("text is already rendered, repeating it may cause infinite recursion")
|
||||
}
|
||||
|
||||
var err error
|
||||
contentToRender, err = cast.ToStringE(contentToRenderv)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = pco.initRenderHooks(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
conv := pco.po.p.getContentConverter()
|
||||
|
||||
if opts.Markup != "" && opts.Markup != pco.po.p.m.pageConfig.ContentMediaType.SubType {
|
||||
var err error
|
||||
conv, err = pco.po.p.m.newContentConverter(pco.po.p, opts.Markup)
|
||||
if err != nil {
|
||||
return "", pco.po.p.wrapError(err)
|
||||
}
|
||||
}
|
||||
|
||||
var rendered []byte
|
||||
|
||||
parseInfo := &contentParseInfo{
|
||||
h: pco.po.p.s.h,
|
||||
pid: pco.po.p.pid,
|
||||
}
|
||||
|
||||
if pageparser.HasShortcode(contentToRender) {
|
||||
contentToRenderb := []byte(contentToRender)
|
||||
// String contains a shortcode.
|
||||
parseInfo.itemsStep1, err = pageparser.ParseBytes(contentToRenderb, pageparser.Config{
|
||||
NoFrontMatter: true,
|
||||
NoSummaryDivider: true,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
s := newShortcodeHandler(pco.po.p.pathOrTitle(), pco.po.p.s)
|
||||
if err := parseInfo.mapItemsAfterFrontMatter(contentToRenderb, s); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
placeholders, err := s.prepareShortcodesForPage(ctx, pco.po.p, pco.po.f, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
contentToRender, hasVariants, err := parseInfo.contentToRender(ctx, contentToRenderb, placeholders)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if hasVariants {
|
||||
pco.po.p.pageOutputTemplateVariationsState.Add(1)
|
||||
}
|
||||
b, err := pco.renderContentWithConverter(ctx, conv, contentToRender, false)
|
||||
if err != nil {
|
||||
return "", pco.po.p.wrapError(err)
|
||||
}
|
||||
rendered = b.Bytes()
|
||||
|
||||
if parseInfo.hasNonMarkdownShortcode {
|
||||
var hasShortcodeVariants bool
|
||||
|
||||
tokenHandler := func(ctx context.Context, token string) ([]byte, error) {
|
||||
if token == tocShortcodePlaceholder {
|
||||
toc, err := pco.po.p.m.content.contentToC(ctx, pco)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The Page's TableOfContents was accessed in a shortcode.
|
||||
return []byte(toc.tableOfContentsHTML), nil
|
||||
}
|
||||
renderer, found := placeholders[token]
|
||||
if found {
|
||||
repl, more, err := renderer.renderShortcode(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hasShortcodeVariants = hasShortcodeVariants || more
|
||||
return repl, nil
|
||||
}
|
||||
// This should not happen.
|
||||
return nil, fmt.Errorf("unknown shortcode token %q", token)
|
||||
}
|
||||
|
||||
rendered, err = expandShortcodeTokens(ctx, rendered, tokenHandler)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if hasShortcodeVariants {
|
||||
pco.po.p.pageOutputTemplateVariationsState.Add(1)
|
||||
}
|
||||
}
|
||||
|
||||
// We need a consolidated view in $page.HasShortcode
|
||||
pco.po.p.m.content.shortcodeState.transferNames(s)
|
||||
|
||||
} else {
|
||||
c, err := pco.renderContentWithConverter(ctx, conv, []byte(contentToRender), false)
|
||||
if err != nil {
|
||||
return "", pco.po.p.wrapError(err)
|
||||
}
|
||||
|
||||
rendered = c.Bytes()
|
||||
}
|
||||
|
||||
if opts.Display == "inline" {
|
||||
markup := pco.po.p.m.pageConfig.Content.Markup
|
||||
if opts.Markup != "" {
|
||||
markup = pco.po.p.s.ContentSpec.ResolveMarkup(opts.Markup)
|
||||
}
|
||||
rendered = pco.po.p.s.ContentSpec.TrimShortHTML(rendered, markup)
|
||||
}
|
||||
|
||||
return template.HTML(string(rendered)), nil
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) Render(ctx context.Context, layout ...string) (template.HTML, error) {
|
||||
if len(layout) == 0 {
|
||||
return "", errors.New("no layout given")
|
||||
@@ -416,6 +127,105 @@ func (pco *pageContentOutput) Render(ctx context.Context, layout ...string) (tem
|
||||
return template.HTML(res), nil
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) Fragments(ctx context.Context) *tableofcontents.Fragments {
|
||||
return pco.c().Fragments(ctx)
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) RenderShortcodes(ctx context.Context) (template.HTML, error) {
|
||||
return pco.c().RenderShortcodes(ctx)
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) Markup(opts ...any) page.Markup {
|
||||
if len(opts) > 1 {
|
||||
panic("too many arguments, expected 0 or 1")
|
||||
}
|
||||
var scope string
|
||||
if len(opts) == 1 {
|
||||
scope = cast.ToString(opts[0])
|
||||
}
|
||||
return pco.po.p.m.content.getOrCreateScope(scope, pco)
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) c() page.Markup {
|
||||
return pco.po.p.m.content.getOrCreateScope("", pco)
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) Content(ctx context.Context) (any, error) {
|
||||
r, err := pco.c().Render(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.Content(ctx)
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) ContentWithoutSummary(ctx context.Context) (template.HTML, error) {
|
||||
r, err := pco.c().Render(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return r.ContentWithoutSummary(ctx)
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) TableOfContents(ctx context.Context) template.HTML {
|
||||
return pco.c().(*cachedContentScope).fragmentsHTML(ctx)
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) Len(ctx context.Context) int {
|
||||
return pco.mustRender(ctx).Len(ctx)
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) mustRender(ctx context.Context) page.Content {
|
||||
c, err := pco.c().Render(ctx)
|
||||
if err != nil {
|
||||
pco.fail(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) fail(err error) {
|
||||
pco.po.p.s.h.FatalError(pco.po.p.wrapError(err))
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) Plain(ctx context.Context) string {
|
||||
return pco.mustRender(ctx).Plain(ctx)
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) PlainWords(ctx context.Context) []string {
|
||||
return pco.mustRender(ctx).PlainWords(ctx)
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) ReadingTime(ctx context.Context) int {
|
||||
return pco.mustRender(ctx).ReadingTime(ctx)
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) WordCount(ctx context.Context) int {
|
||||
return pco.mustRender(ctx).WordCount(ctx)
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) FuzzyWordCount(ctx context.Context) int {
|
||||
return pco.mustRender(ctx).FuzzyWordCount(ctx)
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) Summary(ctx context.Context) template.HTML {
|
||||
summary, err := pco.mustRender(ctx).Summary(ctx)
|
||||
if err != nil {
|
||||
pco.fail(err)
|
||||
}
|
||||
return summary.Text
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) Truncated(ctx context.Context) bool {
|
||||
summary, err := pco.mustRender(ctx).Summary(ctx)
|
||||
if err != nil {
|
||||
pco.fail(err)
|
||||
}
|
||||
return summary.Truncated
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) RenderString(ctx context.Context, args ...any) (template.HTML, error) {
|
||||
return pco.c().RenderString(ctx, args...)
|
||||
}
|
||||
|
||||
func (pco *pageContentOutput) initRenderHooks() error {
|
||||
if pco == nil {
|
||||
return nil
|
||||
@@ -486,6 +296,8 @@ func (pco *pageContentOutput) initRenderHooks() error {
|
||||
if id != nil {
|
||||
layoutDescriptor.KindVariants = id.(string)
|
||||
}
|
||||
case hooks.TableRendererType:
|
||||
layoutDescriptor.Kind = "render-table"
|
||||
case hooks.CodeBlockRendererType:
|
||||
layoutDescriptor.Kind = "render-codeblock"
|
||||
if id != nil {
|
||||
@@ -524,13 +336,23 @@ func (pco *pageContentOutput) initRenderHooks() error {
|
||||
|
||||
templ, found1 := getHookTemplate(pco.po.f)
|
||||
|
||||
if pco.po.p.reusePageOutputContent() {
|
||||
if !found1 || pco.po.p.reusePageOutputContent() {
|
||||
// Some hooks may only be available in HTML, and if
|
||||
// this site is configured to not have HTML output, we need to
|
||||
// make sure we have a fallback. This should be very rare.
|
||||
candidates := pco.po.p.s.renderFormats
|
||||
if pco.po.f.MediaType.FirstSuffix.Suffix != "html" {
|
||||
if _, found := candidates.GetBySuffix("html"); !found {
|
||||
candidates = append(candidates, output.HTMLFormat)
|
||||
}
|
||||
}
|
||||
// Check if some of the other output formats would give a different template.
|
||||
for _, f := range pco.po.p.s.renderFormats {
|
||||
for _, f := range candidates {
|
||||
if f.Name == pco.po.f.Name {
|
||||
continue
|
||||
}
|
||||
templ2, found2 := getHookTemplate(f)
|
||||
|
||||
if found2 {
|
||||
if !found1 {
|
||||
templ = templ2
|
||||
@@ -660,65 +482,3 @@ func executeToString(ctx context.Context, h tpl.TemplateHandler, templ tpl.Templ
|
||||
}
|
||||
return b.String(), nil
|
||||
}
|
||||
|
||||
func splitUserDefinedSummaryAndContent(markup string, c []byte) (summary []byte, content []byte, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("summary split failed: %s", r)
|
||||
}
|
||||
}()
|
||||
|
||||
startDivider := bytes.Index(c, internalSummaryDividerBaseBytes)
|
||||
|
||||
if startDivider == -1 {
|
||||
return
|
||||
}
|
||||
|
||||
startTag := "p"
|
||||
switch markup {
|
||||
case media.DefaultContentTypes.AsciiDoc.SubType:
|
||||
startTag = "div"
|
||||
}
|
||||
|
||||
// Walk back and forward to the surrounding tags.
|
||||
start := bytes.LastIndex(c[:startDivider], []byte("<"+startTag))
|
||||
end := bytes.Index(c[startDivider:], []byte("</"+startTag))
|
||||
|
||||
if start == -1 {
|
||||
start = startDivider
|
||||
} else {
|
||||
start = startDivider - (startDivider - start)
|
||||
}
|
||||
|
||||
if end == -1 {
|
||||
end = startDivider + len(internalSummaryDividerBase)
|
||||
} else {
|
||||
end = startDivider + end + len(startTag) + 3
|
||||
}
|
||||
|
||||
var addDiv bool
|
||||
|
||||
switch markup {
|
||||
case "rst":
|
||||
addDiv = true
|
||||
}
|
||||
|
||||
withoutDivider := append(c[:start], bytes.Trim(c[end:], "\n")...)
|
||||
|
||||
if len(withoutDivider) > 0 {
|
||||
summary = bytes.TrimSpace(withoutDivider[:start])
|
||||
}
|
||||
|
||||
if addDiv {
|
||||
// For the rst
|
||||
summary = append(append([]byte(nil), summary...), []byte("</div>")...)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
content = bytes.TrimSpace(withoutDivider)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
+5
-201
@@ -63,15 +63,6 @@ Summary Next Line
|
||||
|
||||
<!--more-->
|
||||
Some more text
|
||||
`
|
||||
|
||||
simplePageWithBlankSummary = `---
|
||||
title: SimpleWithBlankSummary
|
||||
---
|
||||
|
||||
<!--more-->
|
||||
|
||||
Some text.
|
||||
`
|
||||
|
||||
simplePageWithSummaryParameter = `---
|
||||
@@ -322,7 +313,8 @@ func checkPageTOC(t *testing.T, page page.Page, toc string) {
|
||||
}
|
||||
|
||||
func checkPageSummary(t *testing.T, page page.Page, summary string, msg ...any) {
|
||||
a := normalizeContent(string(page.Summary(context.Background())))
|
||||
s := string(page.Summary(context.Background()))
|
||||
a := normalizeContent(s)
|
||||
b := normalizeContent(summary)
|
||||
if a != b {
|
||||
t.Fatalf("Page summary is:\n%q.\nExpected\n%q (%q)", a, b, msg)
|
||||
@@ -531,7 +523,7 @@ categories: ["cool stuff"]
|
||||
for _, p := range s.Pages() {
|
||||
checkDated(p, p.Kind())
|
||||
}
|
||||
checkDate(s.LastChange(), "site")
|
||||
checkDate(s.Lastmod(), "site")
|
||||
}
|
||||
|
||||
func TestPageDatesSections(t *testing.T) {
|
||||
@@ -593,26 +585,6 @@ date: 2012-01-12
|
||||
b.Assert(s.Site().Lastmod().Year(), qt.Equals, 2018)
|
||||
}
|
||||
|
||||
func TestCreateNewPage(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
assertFunc := func(t *testing.T, ext string, pages page.Pages) {
|
||||
p := pages[0]
|
||||
|
||||
// issue #2290: Path is relative to the content dir and will continue to be so.
|
||||
c.Assert(p.File().Path(), qt.Equals, fmt.Sprintf("p0.%s", ext))
|
||||
c.Assert(p.IsHome(), qt.Equals, false)
|
||||
checkPageTitle(t, p, "Simple")
|
||||
checkPageContent(t, p, normalizeExpected(ext, "<p>Simple Page</p>\n"))
|
||||
checkPageSummary(t, p, "Simple Page")
|
||||
checkPageType(t, p, "page")
|
||||
}
|
||||
|
||||
settings := map[string]any{}
|
||||
|
||||
testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePage)
|
||||
}
|
||||
|
||||
func TestPageSummary(t *testing.T) {
|
||||
t.Parallel()
|
||||
assertFunc := func(t *testing.T, ext string, pages page.Pages) {
|
||||
@@ -621,7 +593,7 @@ func TestPageSummary(t *testing.T) {
|
||||
// Source is not Asciidoctor- or RST-compatible so don't test them
|
||||
if ext != "ad" && ext != "rst" {
|
||||
checkPageContent(t, p, normalizeExpected(ext, "<p><a href=\"https://lipsum.com/\">Lorem ipsum</a> dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>\n\n<p>Additional text.</p>\n\n<p>Further text.</p>\n"), ext)
|
||||
checkPageSummary(t, p, normalizeExpected(ext, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Additional text."), ext)
|
||||
checkPageSummary(t, p, normalizeExpected(ext, "<p><a href=\"https://lipsum.com/\">Lorem ipsum</a> dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p><p>Additional text.</p>"), ext)
|
||||
}
|
||||
checkPageType(t, p, "page")
|
||||
}
|
||||
@@ -642,19 +614,6 @@ func TestPageWithDelimiter(t *testing.T) {
|
||||
testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiter)
|
||||
}
|
||||
|
||||
func TestPageWithBlankSummary(t *testing.T) {
|
||||
t.Parallel()
|
||||
assertFunc := func(t *testing.T, ext string, pages page.Pages) {
|
||||
p := pages[0]
|
||||
checkPageTitle(t, p, "SimpleWithBlankSummary")
|
||||
checkPageContent(t, p, normalizeExpected(ext, "<p>Some text.</p>\n"), ext)
|
||||
checkPageSummary(t, p, normalizeExpected(ext, ""), ext)
|
||||
checkPageType(t, p, "page")
|
||||
}
|
||||
|
||||
testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithBlankSummary)
|
||||
}
|
||||
|
||||
func TestPageWithSummaryParameter(t *testing.T) {
|
||||
t.Parallel()
|
||||
assertFunc := func(t *testing.T, ext string, pages page.Pages) {
|
||||
@@ -729,19 +688,6 @@ title: "empty"
|
||||
b.AssertFileContent("public/empty/index.html", "! title")
|
||||
}
|
||||
|
||||
func TestPageWithShortCodeInSummary(t *testing.T) {
|
||||
t.Parallel()
|
||||
assertFunc := func(t *testing.T, ext string, pages page.Pages) {
|
||||
p := pages[0]
|
||||
checkPageTitle(t, p, "Simple")
|
||||
checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line. <figure><img src=\"/not/real\"> </figure> . More text here.</p><p>Some more text</p>"))
|
||||
checkPageSummary(t, p, "Summary Next Line. . More text here. Some more text")
|
||||
checkPageType(t, p, "page")
|
||||
}
|
||||
|
||||
testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithShortcodeInSummary)
|
||||
}
|
||||
|
||||
func TestTableOfContents(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
cfg, fs := newTestCfg()
|
||||
@@ -853,7 +799,7 @@ Summary: {{ .Summary }}|Truncated: {{ .Truncated }}|
|
||||
Content: {{ .Content }}|
|
||||
|
||||
`).AssertFileContent("public/simple/index.html",
|
||||
"Summary: This is summary. This is more summary. This is even more summary*.|",
|
||||
"Summary: <p>This is <strong>summary</strong>.\nThis is <strong>more summary</strong>.\nThis is <em>even more summary</em>*.\nThis is <strong>more summary</strong>.</p>|",
|
||||
"Truncated: true|",
|
||||
"Content: <p>This is <strong>summary</strong>.")
|
||||
}
|
||||
@@ -1242,11 +1188,6 @@ func TestWordCountWithMainEnglishWithCJKRunes(t *testing.T) {
|
||||
if p.WordCount(context.Background()) != 74 {
|
||||
t.Fatalf("[%s] incorrect word count, expected %v, got %v", ext, 74, p.WordCount(context.Background()))
|
||||
}
|
||||
|
||||
if p.Summary(context.Background()) != simplePageWithMainEnglishWithCJKRunesSummary {
|
||||
t.Fatalf("[%s] incorrect Summary for content '%s'. expected\n%v, got\n%v", ext, p.Plain(context.Background()),
|
||||
simplePageWithMainEnglishWithCJKRunesSummary, p.Summary(context.Background()))
|
||||
}
|
||||
}
|
||||
|
||||
testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithMainEnglishWithCJKRunes)
|
||||
@@ -1263,11 +1204,6 @@ func TestWordCountWithIsCJKLanguageFalse(t *testing.T) {
|
||||
if p.WordCount(context.Background()) != 75 {
|
||||
t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.Plain(context.Background()), 74, p.WordCount(context.Background()))
|
||||
}
|
||||
|
||||
if p.Summary(context.Background()) != simplePageWithIsCJKLanguageFalseSummary {
|
||||
t.Fatalf("[%s] incorrect Summary for content '%s'. expected %v, got %v", ext, p.Plain(context.Background()),
|
||||
simplePageWithIsCJKLanguageFalseSummary, p.Summary(context.Background()))
|
||||
}
|
||||
}
|
||||
|
||||
testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithIsCJKLanguageFalse)
|
||||
@@ -1485,42 +1421,6 @@ func TestChompBOM(t *testing.T) {
|
||||
checkPageTitle(t, p, "Simple")
|
||||
}
|
||||
|
||||
func TestPageHTMLContent(t *testing.T) {
|
||||
b := newTestSitesBuilder(t)
|
||||
b.WithSimpleConfigFile()
|
||||
|
||||
frontmatter := `---
|
||||
title: "HTML Content"
|
||||
---
|
||||
`
|
||||
b.WithContent("regular.html", frontmatter+`<h1>Hugo</h1>`)
|
||||
b.WithContent("nomarkdownforyou.html", frontmatter+`**Hugo!**`)
|
||||
b.WithContent("manualsummary.html", frontmatter+`
|
||||
<p>This is summary</p>
|
||||
<!--more-->
|
||||
<p>This is the main content.</p>`)
|
||||
|
||||
b.Build(BuildCfg{})
|
||||
|
||||
b.AssertFileContent(
|
||||
"public/regular/index.html",
|
||||
"Single: HTML Content|Hello|en|RelPermalink: /regular/|",
|
||||
"Summary: Hugo|Truncated: false")
|
||||
|
||||
b.AssertFileContent(
|
||||
"public/nomarkdownforyou/index.html",
|
||||
"Permalink: http://example.com/nomarkdownforyou/|**Hugo!**|",
|
||||
)
|
||||
|
||||
// https://github.com/gohugoio/hugo/issues/5723
|
||||
b.AssertFileContent(
|
||||
"public/manualsummary/index.html",
|
||||
"Single: HTML Content|Hello|en|RelPermalink: /manualsummary/|",
|
||||
"Summary: \n<p>This is summary</p>\n|Truncated: true",
|
||||
"|<p>This is the main content.</p>|",
|
||||
)
|
||||
}
|
||||
|
||||
// https://github.com/gohugoio/hugo/issues/5381
|
||||
func TestPageManualSummary(t *testing.T) {
|
||||
b := newTestSitesBuilder(t)
|
||||
@@ -1761,102 +1661,6 @@ Single: {{ .Title}}|{{ .RelPermalink }}|{{ .Path }}|
|
||||
b.AssertFileContent("public/sect3/Pag.E4/index.html", "Single: Pag.E4|/sect3/Pag.E4/|/sect3/p4|")
|
||||
}
|
||||
|
||||
// https://github.com/gohugoio/hugo/issues/4675
|
||||
func TestWordCountAndSimilarVsSummary(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
single := []string{"_default/single.html", `
|
||||
WordCount: {{ .WordCount }}
|
||||
FuzzyWordCount: {{ .FuzzyWordCount }}
|
||||
ReadingTime: {{ .ReadingTime }}
|
||||
Len Plain: {{ len .Plain }}
|
||||
Len PlainWords: {{ len .PlainWords }}
|
||||
Truncated: {{ .Truncated }}
|
||||
Len Summary: {{ len .Summary }}
|
||||
Len Content: {{ len .Content }}
|
||||
|
||||
SUMMARY:{{ .Summary }}:{{ len .Summary }}:END
|
||||
|
||||
`}
|
||||
|
||||
b := newTestSitesBuilder(t)
|
||||
b.WithSimpleConfigFile().WithTemplatesAdded(single...).WithContent("p1.md", fmt.Sprintf(`---
|
||||
title: p1
|
||||
---
|
||||
|
||||
%s
|
||||
|
||||
`, strings.Repeat("word ", 510)),
|
||||
|
||||
"p2.md", fmt.Sprintf(`---
|
||||
title: p2
|
||||
---
|
||||
This is a summary.
|
||||
|
||||
<!--more-->
|
||||
|
||||
%s
|
||||
|
||||
`, strings.Repeat("word ", 310)),
|
||||
"p3.md", fmt.Sprintf(`---
|
||||
title: p3
|
||||
isCJKLanguage: true
|
||||
---
|
||||
Summary: In Chinese, 好 means good.
|
||||
|
||||
<!--more-->
|
||||
|
||||
%s
|
||||
|
||||
`, strings.Repeat("好", 200)),
|
||||
"p4.md", fmt.Sprintf(`---
|
||||
title: p4
|
||||
isCJKLanguage: false
|
||||
---
|
||||
Summary: In Chinese, 好 means good.
|
||||
|
||||
<!--more-->
|
||||
|
||||
%s
|
||||
|
||||
`, strings.Repeat("好", 200)),
|
||||
|
||||
"p5.md", fmt.Sprintf(`---
|
||||
title: p4
|
||||
isCJKLanguage: true
|
||||
---
|
||||
Summary: In Chinese, 好 means good.
|
||||
|
||||
%s
|
||||
|
||||
`, strings.Repeat("好", 200)),
|
||||
"p6.md", fmt.Sprintf(`---
|
||||
title: p4
|
||||
isCJKLanguage: false
|
||||
---
|
||||
Summary: In Chinese, 好 means good.
|
||||
|
||||
%s
|
||||
|
||||
`, strings.Repeat("好", 200)),
|
||||
)
|
||||
|
||||
b.CreateSites().Build(BuildCfg{})
|
||||
|
||||
c.Assert(len(b.H.Sites), qt.Equals, 1)
|
||||
c.Assert(len(b.H.Sites[0].RegularPages()), qt.Equals, 6)
|
||||
|
||||
b.AssertFileContent("public/p1/index.html", "WordCount: 510\nFuzzyWordCount: 600\nReadingTime: 3\nLen Plain: 2550\nLen PlainWords: 510\nTruncated: false\nLen Summary: 2549\nLen Content: 2557")
|
||||
|
||||
b.AssertFileContent("public/p2/index.html", "WordCount: 314\nFuzzyWordCount: 400\nReadingTime: 2\nLen Plain: 1569\nLen PlainWords: 314\nTruncated: true\nLen Summary: 25\nLen Content: 1582")
|
||||
|
||||
b.AssertFileContent("public/p3/index.html", "WordCount: 206\nFuzzyWordCount: 300\nReadingTime: 1\nLen Plain: 638\nLen PlainWords: 7\nTruncated: true\nLen Summary: 43\nLen Content: 651")
|
||||
b.AssertFileContent("public/p4/index.html", "WordCount: 7\nFuzzyWordCount: 100\nReadingTime: 1\nLen Plain: 638\nLen PlainWords: 7\nTruncated: true\nLen Summary: 43\nLen Content: 651")
|
||||
b.AssertFileContent("public/p5/index.html", "WordCount: 206\nFuzzyWordCount: 300\nReadingTime: 1\nLen Plain: 638\nLen PlainWords: 7\nTruncated: true\nLen Summary: 229\nLen Content: 652")
|
||||
b.AssertFileContent("public/p6/index.html", "WordCount: 7\nFuzzyWordCount: 100\nReadingTime: 1\nLen Plain: 638\nLen PlainWords: 7\nTruncated: false\nLen Summary: 637\nLen Content: 652")
|
||||
}
|
||||
|
||||
func TestScratch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -24,8 +24,10 @@ import (
|
||||
func TestPaginator(t *testing.T) {
|
||||
configFile := `
|
||||
baseURL = "https://example.com/foo/"
|
||||
paginate = 3
|
||||
paginatepath = "thepage"
|
||||
|
||||
[pagination]
|
||||
pagerSize = 3
|
||||
path = "thepage"
|
||||
|
||||
[languages.en]
|
||||
weight = 1
|
||||
@@ -161,10 +163,11 @@ Len Pag: {{ len $pag.Pages }}
|
||||
func TestPaginatorNodePagesOnly(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
paginate = 1
|
||||
[pagination]
|
||||
pagerSize = 1
|
||||
-- content/p1.md --
|
||||
-- layouts/_default/single.html --
|
||||
Paginator: {{ .Paginator }}
|
||||
Paginator: {{ .Paginator }}
|
||||
`
|
||||
b, err := TestE(t, files)
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
|
||||
@@ -559,7 +559,8 @@ baseURL = "https://example.com"
|
||||
disableKinds = ["term", "taxonomy"]
|
||||
disableLiveReload = true
|
||||
defaultContentLanguage = "nn"
|
||||
paginate = 20
|
||||
[pagination]
|
||||
pagerSize = 20
|
||||
[security]
|
||||
enableInlineShortcodes = true
|
||||
[languages]
|
||||
@@ -1431,7 +1432,7 @@ title: "My Sect"
|
||||
---
|
||||
title: "P%d"
|
||||
---
|
||||
P%d Content.
|
||||
P%d Content.
|
||||
`
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
|
||||
@@ -123,7 +123,7 @@ func TestSecurityPolicies(t *testing.T) {
|
||||
c.Skip()
|
||||
}
|
||||
cb := func(b *sitesBuilder) {
|
||||
b.WithTemplatesAdded("index.html", `{{ $scss := "body { color: #333; }" | resources.FromString "foo.scss" | resources.ToCSS (dict "transpiler" "dartsass") }}`)
|
||||
b.WithTemplatesAdded("index.html", `{{ $scss := "body { color: #333; }" | resources.FromString "foo.scss" | css.Sass (dict "transpiler" "dartsass") }}`)
|
||||
}
|
||||
testVariant(c, cb, "")
|
||||
})
|
||||
@@ -137,10 +137,10 @@ func TestSecurityPolicies(t *testing.T) {
|
||||
b.WithConfigFile("toml", `
|
||||
[security]
|
||||
[security.exec]
|
||||
allow="none"
|
||||
|
||||
allow="none"
|
||||
|
||||
`)
|
||||
b.WithTemplatesAdded("index.html", `{{ $scss := "body { color: #333; }" | resources.FromString "foo.scss" | resources.ToCSS (dict "transpiler" "dartsass") }}`)
|
||||
b.WithTemplatesAdded("index.html", `{{ $scss := "body { color: #333; }" | resources.FromString "foo.scss" | css.Sass (dict "transpiler" "dartsass") }}`)
|
||||
}
|
||||
testVariant(c, cb, `(?s).*sass(-embedded)?" is not whitelisted in policy "security\.exec\.allow".*`)
|
||||
})
|
||||
@@ -160,7 +160,7 @@ allow="none"
|
||||
httpTestVariant(c, `{{ $json := resources.GetRemote "%[1]s/fruits.json" }}{{ $json.Content }}`, `(?s).*is not whitelisted in policy "security\.http\.urls".*`,
|
||||
func(b *sitesBuilder) {
|
||||
b.WithConfigFile("toml", `
|
||||
[security]
|
||||
[security]
|
||||
[security.http]
|
||||
urls="none"
|
||||
`)
|
||||
@@ -181,39 +181,10 @@ urls="none"
|
||||
httpTestVariant(c, `{{ $json := resources.GetRemote "%[1]s/fakejson.json" }}{{ $json.Content }}`, ``,
|
||||
func(b *sitesBuilder) {
|
||||
b.WithConfigFile("toml", `
|
||||
[security]
|
||||
[security]
|
||||
[security.http]
|
||||
mediaTypes=["application/json"]
|
||||
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
c.Run("getJSON, OK", func(c *qt.C) {
|
||||
c.Parallel()
|
||||
httpTestVariant(c, `{{ $json := getJSON "%[1]s/fruits.json" }}{{ $json.Content }}`, "", nil)
|
||||
})
|
||||
|
||||
c.Run("getJSON, denied URL", func(c *qt.C) {
|
||||
c.Parallel()
|
||||
httpTestVariant(c, `{{ $json := getJSON "%[1]s/fruits.json" }}{{ $json.Content }}`, `(?s).*is not whitelisted in policy "security\.http\.urls".*`,
|
||||
func(b *sitesBuilder) {
|
||||
b.WithConfigFile("toml", `
|
||||
[security]
|
||||
[security.http]
|
||||
urls="none"
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
c.Run("getCSV, denied URL", func(c *qt.C) {
|
||||
c.Parallel()
|
||||
httpTestVariant(c, `{{ $d := getCSV ";" "%[1]s/cities.csv" }}{{ $d.Content }}`, `(?s).*is not whitelisted in policy "security\.http\.urls".*`,
|
||||
func(b *sitesBuilder) {
|
||||
b.WithConfigFile("toml", `
|
||||
[security]
|
||||
[security.http]
|
||||
urls="none"
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -65,6 +65,7 @@ var zeroShortcode = prerenderedShortcode{}
|
||||
type pageForShortcode struct {
|
||||
page.PageWithoutContent
|
||||
page.TableOfContentsProvider
|
||||
page.MarkupProvider
|
||||
page.ContentProvider
|
||||
|
||||
// We need to replace it after we have rendered it, so provide a
|
||||
@@ -80,6 +81,7 @@ func newPageForShortcode(p *pageState) page.Page {
|
||||
return &pageForShortcode{
|
||||
PageWithoutContent: p,
|
||||
TableOfContentsProvider: p,
|
||||
MarkupProvider: page.NopPage,
|
||||
ContentProvider: page.NopPage,
|
||||
toc: template.HTML(tocShortcodePlaceholder),
|
||||
p: p,
|
||||
@@ -105,6 +107,7 @@ var _ types.Unwrapper = (*pageForRenderHooks)(nil)
|
||||
type pageForRenderHooks struct {
|
||||
page.PageWithoutContent
|
||||
page.TableOfContentsProvider
|
||||
page.MarkupProvider
|
||||
page.ContentProvider
|
||||
p *pageState
|
||||
}
|
||||
@@ -112,6 +115,7 @@ type pageForRenderHooks struct {
|
||||
func newPageForRenderHook(p *pageState) page.Page {
|
||||
return &pageForRenderHooks{
|
||||
PageWithoutContent: p,
|
||||
MarkupProvider: page.NopPage,
|
||||
ContentProvider: page.NopPage,
|
||||
TableOfContentsProvider: p,
|
||||
p: p,
|
||||
|
||||
+16
-20
@@ -126,10 +126,11 @@ func TestShortcodeMultipleOutputFormats(t *testing.T) {
|
||||
siteConfig := `
|
||||
baseURL = "http://example.com/blog"
|
||||
|
||||
paginate = 1
|
||||
|
||||
disableKinds = ["section", "term", "taxonomy", "RSS", "sitemap", "robotsTXT", "404"]
|
||||
|
||||
[pagination]
|
||||
pagerSize = 1
|
||||
|
||||
[outputs]
|
||||
home = [ "HTML", "AMP", "Calendar" ]
|
||||
page = [ "HTML", "AMP", "JSON" ]
|
||||
@@ -756,12 +757,15 @@ title: "Hugo Rocks!"
|
||||
|
||||
func TestShortcodeParams(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
builder := newTestSitesBuilder(t).WithSimpleConfigFile()
|
||||
|
||||
builder.WithContent("page.md", `---
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.org"
|
||||
-- layouts/shortcodes/hello.html --
|
||||
{{ range $i, $v := .Params }}{{ printf "- %v: %v (%T) " $i $v $v -}}{{ end }}
|
||||
-- content/page.md --
|
||||
title: "Hugo Rocks!"
|
||||
summary: "Foo"
|
||||
---
|
||||
|
||||
# doc
|
||||
@@ -770,23 +774,15 @@ types positional: {{< hello true false 33 3.14 >}}
|
||||
types named: {{< hello b1=true b2=false i1=33 f1=3.14 >}}
|
||||
types string: {{< hello "true" trues "33" "3.14" >}}
|
||||
escaped quoute: {{< hello "hello \"world\"." >}}
|
||||
-- layouts/_default/single.html --
|
||||
Content: {{ .Content }}|
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
`).WithTemplatesAdded(
|
||||
"layouts/shortcodes/hello.html",
|
||||
`{{ range $i, $v := .Params }}
|
||||
- {{ printf "%v: %v (%T)" $i $v $v }}
|
||||
{{ end }}
|
||||
{{ $b1 := .Get "b1" }}
|
||||
Get: {{ printf "%v (%T)" $b1 $b1 | safeHTML }}
|
||||
`).Build(BuildCfg{})
|
||||
|
||||
s := builder.H.Sites[0]
|
||||
c.Assert(len(s.RegularPages()), qt.Equals, 1)
|
||||
|
||||
builder.AssertFileContent("public/page/index.html",
|
||||
b.AssertFileContent("public/page/index.html",
|
||||
"types positional: - 0: true (bool) - 1: false (bool) - 2: 33 (int) - 3: 3.14 (float64)",
|
||||
"types named: - b1: true (bool) - b2: false (bool) - f1: 3.14 (float64) - i1: 33 (int) Get: true (bool) ",
|
||||
"types named: - b1: true (bool) - b2: false (bool) - f1: 3.14 (float64) - i1: 33 (int)",
|
||||
"types string: - 0: true (string) - 1: trues (string) - 2: 33 (string) - 3: 3.14 (string) ",
|
||||
"hello "world". (string)",
|
||||
)
|
||||
|
||||
+573
@@ -15,7 +15,9 @@ package hugolib
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"mime"
|
||||
"net/url"
|
||||
@@ -28,10 +30,24 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/bep/logg"
|
||||
"github.com/gohugoio/hugo/cache/dynacache"
|
||||
"github.com/gohugoio/hugo/common/htime"
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/para"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/config/allconfig"
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
"github.com/gohugoio/hugo/hugolib/doctree"
|
||||
"github.com/gohugoio/hugo/hugolib/pagesfromdata"
|
||||
"github.com/gohugoio/hugo/internal/warpc"
|
||||
"github.com/gohugoio/hugo/langs/i18n"
|
||||
"github.com/gohugoio/hugo/modules"
|
||||
"github.com/gohugoio/hugo/resources"
|
||||
"github.com/gohugoio/hugo/tpl/tplimpl"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
@@ -50,6 +66,9 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/resources/kinds"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/gohugoio/hugo/resources/page/pagemeta"
|
||||
"github.com/gohugoio/hugo/resources/page/siteidentities"
|
||||
"github.com/gohugoio/hugo/resources/resource"
|
||||
|
||||
"github.com/gohugoio/hugo/lazy"
|
||||
|
||||
@@ -61,6 +80,556 @@ import (
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
)
|
||||
|
||||
var _ page.Site = (*Site)(nil)
|
||||
|
||||
type siteState int
|
||||
|
||||
const (
|
||||
siteStateInit siteState = iota
|
||||
siteStateReady
|
||||
)
|
||||
|
||||
type Site struct {
|
||||
state siteState
|
||||
conf *allconfig.Config
|
||||
language *langs.Language
|
||||
languagei int
|
||||
pageMap *pageMap
|
||||
|
||||
// The owning container.
|
||||
h *HugoSites
|
||||
|
||||
*deps.Deps
|
||||
|
||||
// Page navigation.
|
||||
*pageFinder
|
||||
taxonomies page.TaxonomyList
|
||||
menus navigation.Menus
|
||||
|
||||
// Shortcut to the home page. Note that this may be nil if
|
||||
// home page, for some odd reason, is disabled.
|
||||
home *pageState
|
||||
|
||||
// The last modification date of this site.
|
||||
lastmod time.Time
|
||||
|
||||
relatedDocsHandler *page.RelatedDocsHandler
|
||||
siteRefLinker
|
||||
publisher publisher.Publisher
|
||||
frontmatterHandler pagemeta.FrontMatterHandler
|
||||
|
||||
// The output formats that we need to render this site in. This slice
|
||||
// will be fixed once set.
|
||||
// This will be the union of Site.Pages' outputFormats.
|
||||
// This slice will be sorted.
|
||||
renderFormats output.Formats
|
||||
|
||||
// Lazily loaded site dependencies
|
||||
init *siteInit
|
||||
}
|
||||
|
||||
func (s *Site) Debug() {
|
||||
fmt.Println("Debugging site", s.Lang(), "=>")
|
||||
// fmt.Println(s.pageMap.testDump())
|
||||
}
|
||||
|
||||
// NewHugoSites creates HugoSites from the given config.
|
||||
func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
|
||||
conf := cfg.Configs.GetFirstLanguageConfig()
|
||||
|
||||
var logger loggers.Logger
|
||||
if cfg.TestLogger != nil {
|
||||
logger = cfg.TestLogger
|
||||
} else {
|
||||
var logHookLast func(e *logg.Entry) error
|
||||
if cfg.Configs.Base.PanicOnWarning {
|
||||
logHookLast = loggers.PanicOnWarningHook
|
||||
}
|
||||
if cfg.LogOut == nil {
|
||||
cfg.LogOut = os.Stdout
|
||||
}
|
||||
if cfg.LogLevel == 0 {
|
||||
cfg.LogLevel = logg.LevelWarn
|
||||
}
|
||||
|
||||
logOpts := loggers.Options{
|
||||
Level: cfg.LogLevel,
|
||||
DistinctLevel: logg.LevelWarn, // This will drop duplicate log warning and errors.
|
||||
HandlerPost: logHookLast,
|
||||
Stdout: cfg.LogOut,
|
||||
Stderr: cfg.LogOut,
|
||||
StoreErrors: conf.Watching(),
|
||||
SuppressStatements: conf.IgnoredLogs(),
|
||||
}
|
||||
logger = loggers.New(logOpts)
|
||||
|
||||
}
|
||||
|
||||
memCache := dynacache.New(dynacache.Options{Watching: conf.Watching(), Log: logger})
|
||||
|
||||
var h *HugoSites
|
||||
onSignalRebuild := func(ids ...identity.Identity) {
|
||||
// This channel is buffered, but make sure we do this in a non-blocking way.
|
||||
if cfg.ChangesFromBuild != nil {
|
||||
go func() {
|
||||
cfg.ChangesFromBuild <- ids
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
firstSiteDeps := &deps.Deps{
|
||||
Fs: cfg.Fs,
|
||||
Log: logger,
|
||||
Conf: conf,
|
||||
BuildState: &deps.BuildState{
|
||||
OnSignalRebuild: onSignalRebuild,
|
||||
},
|
||||
MemCache: memCache,
|
||||
TemplateProvider: tplimpl.DefaultTemplateProvider,
|
||||
TranslationProvider: i18n.NewTranslationProvider(),
|
||||
WasmDispatchers: warpc.AllDispatchers(
|
||||
warpc.Options{
|
||||
CompilationCacheDir: filepath.Join(conf.Dirs().CacheDir, "_warpc"),
|
||||
|
||||
// Katex is relatively slow.
|
||||
PoolSize: 8,
|
||||
Infof: logger.InfoCommand("wasm").Logf,
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
if err := firstSiteDeps.Init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
confm := cfg.Configs
|
||||
if err := confm.Validate(logger); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var sites []*Site
|
||||
|
||||
ns := &contentNodeShifter{
|
||||
numLanguages: len(confm.Languages),
|
||||
}
|
||||
|
||||
treeConfig := doctree.Config[contentNodeI]{
|
||||
Shifter: ns,
|
||||
}
|
||||
|
||||
pageTrees := &pageTrees{
|
||||
treePages: doctree.New(
|
||||
treeConfig,
|
||||
),
|
||||
treeResources: doctree.New(
|
||||
treeConfig,
|
||||
),
|
||||
treeTaxonomyEntries: doctree.NewTreeShiftTree[*weightedContentNode](doctree.DimensionLanguage.Index(), len(confm.Languages)),
|
||||
treePagesFromTemplateAdapters: doctree.NewTreeShiftTree[*pagesfromdata.PagesFromTemplate](doctree.DimensionLanguage.Index(), len(confm.Languages)),
|
||||
}
|
||||
|
||||
pageTrees.createMutableTrees()
|
||||
|
||||
for i, confp := range confm.ConfigLangs() {
|
||||
language := confp.Language()
|
||||
if language.Disabled {
|
||||
continue
|
||||
}
|
||||
k := language.Lang
|
||||
conf := confm.LanguageConfigMap[k]
|
||||
frontmatterHandler, err := pagemeta.NewFrontmatterHandler(firstSiteDeps.Log, conf.Frontmatter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
langs.SetParams(language, conf.Params)
|
||||
|
||||
s := &Site{
|
||||
conf: conf,
|
||||
language: language,
|
||||
languagei: i,
|
||||
frontmatterHandler: frontmatterHandler,
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
firstSiteDeps.Site = s
|
||||
s.Deps = firstSiteDeps
|
||||
} else {
|
||||
d, err := firstSiteDeps.Clone(s, confp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Deps = d
|
||||
}
|
||||
|
||||
s.pageMap = newPageMap(i, s, memCache, pageTrees)
|
||||
|
||||
s.pageFinder = newPageFinder(s.pageMap)
|
||||
s.siteRefLinker, err = newSiteRefLinker(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Set up the main publishing chain.
|
||||
pub, err := publisher.NewDestinationPublisher(
|
||||
firstSiteDeps.ResourceSpec,
|
||||
s.conf.OutputFormats.Config,
|
||||
s.conf.MediaTypes.Config,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.publisher = pub
|
||||
s.relatedDocsHandler = page.NewRelatedDocsHandler(s.conf.Related)
|
||||
// Site deps end.
|
||||
|
||||
s.prepareInits()
|
||||
sites = append(sites, s)
|
||||
}
|
||||
|
||||
if len(sites) == 0 {
|
||||
return nil, errors.New("no sites to build")
|
||||
}
|
||||
|
||||
// Pull the default content language to the top, then sort the sites by language weight (if set) or lang.
|
||||
defaultContentLanguage := confm.Base.DefaultContentLanguage
|
||||
sort.Slice(sites, func(i, j int) bool {
|
||||
li := sites[i].language
|
||||
lj := sites[j].language
|
||||
if li.Lang == defaultContentLanguage {
|
||||
return true
|
||||
}
|
||||
|
||||
if lj.Lang == defaultContentLanguage {
|
||||
return false
|
||||
}
|
||||
|
||||
if li.Weight != lj.Weight {
|
||||
return li.Weight < lj.Weight
|
||||
}
|
||||
return li.Lang < lj.Lang
|
||||
})
|
||||
|
||||
var err error
|
||||
h, err = newHugoSites(cfg, firstSiteDeps, pageTrees, sites)
|
||||
if err == nil && h == nil {
|
||||
panic("hugo: newHugoSitesNew returned nil error and nil HugoSites")
|
||||
}
|
||||
|
||||
return h, err
|
||||
}
|
||||
|
||||
func newHugoSites(cfg deps.DepsCfg, d *deps.Deps, pageTrees *pageTrees, sites []*Site) (*HugoSites, error) {
|
||||
numWorkers := config.GetNumWorkerMultiplier()
|
||||
numWorkersSite := numWorkers
|
||||
if numWorkersSite > len(sites) {
|
||||
numWorkersSite = len(sites)
|
||||
}
|
||||
workersSite := para.New(numWorkersSite)
|
||||
|
||||
h := &HugoSites{
|
||||
Sites: sites,
|
||||
Deps: sites[0].Deps,
|
||||
Configs: cfg.Configs,
|
||||
workersSite: workersSite,
|
||||
numWorkersSites: numWorkers,
|
||||
numWorkers: numWorkers,
|
||||
pageTrees: pageTrees,
|
||||
cachePages: dynacache.GetOrCreatePartition[string,
|
||||
page.Pages](d.MemCache, "/pags/all",
|
||||
dynacache.OptionsPartition{Weight: 10, ClearWhen: dynacache.ClearOnRebuild},
|
||||
),
|
||||
cacheContentSource: dynacache.GetOrCreatePartition[string, *resources.StaleValue[[]byte]](d.MemCache, "/cont/src", dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
|
||||
translationKeyPages: maps.NewSliceCache[page.Page](),
|
||||
currentSite: sites[0],
|
||||
skipRebuildForFilenames: make(map[string]bool),
|
||||
init: &hugoSitesInit{
|
||||
data: lazy.New(),
|
||||
layouts: lazy.New(),
|
||||
gitInfo: lazy.New(),
|
||||
},
|
||||
}
|
||||
|
||||
// Assemble dependencies to be used in hugo.Deps.
|
||||
var dependencies []*hugo.Dependency
|
||||
var depFromMod func(m modules.Module) *hugo.Dependency
|
||||
depFromMod = func(m modules.Module) *hugo.Dependency {
|
||||
dep := &hugo.Dependency{
|
||||
Path: m.Path(),
|
||||
Version: m.Version(),
|
||||
Time: m.Time(),
|
||||
Vendor: m.Vendor(),
|
||||
}
|
||||
|
||||
// These are pointers, but this all came from JSON so there's no recursive navigation,
|
||||
// so just create new values.
|
||||
if m.Replace() != nil {
|
||||
dep.Replace = depFromMod(m.Replace())
|
||||
}
|
||||
if m.Owner() != nil {
|
||||
dep.Owner = depFromMod(m.Owner())
|
||||
}
|
||||
return dep
|
||||
}
|
||||
for _, m := range d.Paths.AllModules() {
|
||||
dependencies = append(dependencies, depFromMod(m))
|
||||
}
|
||||
|
||||
h.hugoInfo = hugo.NewInfo(h.Configs.GetFirstLanguageConfig(), dependencies)
|
||||
|
||||
var prototype *deps.Deps
|
||||
for i, s := range sites {
|
||||
s.h = h
|
||||
if err := s.Deps.Compile(prototype); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if i == 0 {
|
||||
prototype = s.Deps
|
||||
}
|
||||
}
|
||||
|
||||
h.fatalErrorHandler = &fatalErrorHandler{
|
||||
h: h,
|
||||
donec: make(chan bool),
|
||||
}
|
||||
|
||||
h.init.data.Add(func(context.Context) (any, error) {
|
||||
err := h.loadData()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load data: %w", err)
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
|
||||
h.init.layouts.Add(func(context.Context) (any, error) {
|
||||
for _, s := range h.Sites {
|
||||
if err := s.Tmpl().(tpl.TemplateManager).MarkReady(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
|
||||
h.init.gitInfo.Add(func(context.Context) (any, error) {
|
||||
err := h.loadGitInfo()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load Git info: %w", err)
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// Deprecated: Use hugo.IsServer instead.
|
||||
func (s *Site) IsServer() bool {
|
||||
hugo.Deprecate(".Site.IsServer", "Use hugo.IsServer instead.", "v0.120.0")
|
||||
return s.conf.Internal.Running
|
||||
}
|
||||
|
||||
// Returns the server port.
|
||||
func (s *Site) ServerPort() int {
|
||||
return s.conf.C.BaseURL.Port()
|
||||
}
|
||||
|
||||
// Returns the configured title for this Site.
|
||||
func (s *Site) Title() string {
|
||||
return s.conf.Title
|
||||
}
|
||||
|
||||
func (s *Site) Copyright() string {
|
||||
return s.conf.Copyright
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Home.OutputFormats.Get "rss" instead.
|
||||
func (s *Site) RSSLink() template.URL {
|
||||
hugo.Deprecate(".Site.RSSLink", "Use the Output Format's Permalink method instead, e.g. .OutputFormats.Get \"RSS\".Permalink", "v0.114.0")
|
||||
rssOutputFormat := s.home.OutputFormats().Get("rss")
|
||||
return template.URL(rssOutputFormat.Permalink())
|
||||
}
|
||||
|
||||
func (s *Site) Config() page.SiteConfig {
|
||||
return page.SiteConfig{
|
||||
Privacy: s.conf.Privacy,
|
||||
Services: s.conf.Services,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Site) LanguageCode() string {
|
||||
return s.Language().LanguageCode()
|
||||
}
|
||||
|
||||
// Returns all Sites for all languages.
|
||||
func (s *Site) Sites() page.Sites {
|
||||
sites := make(page.Sites, len(s.h.Sites))
|
||||
for i, s := range s.h.Sites {
|
||||
sites[i] = s.Site()
|
||||
}
|
||||
return sites
|
||||
}
|
||||
|
||||
// Returns Site currently rendering.
|
||||
func (s *Site) Current() page.Site {
|
||||
return s.h.currentSite
|
||||
}
|
||||
|
||||
// MainSections returns the list of main sections.
|
||||
func (s *Site) MainSections() []string {
|
||||
s.CheckReady()
|
||||
return s.conf.C.MainSections
|
||||
}
|
||||
|
||||
// Returns a struct with some information about the build.
|
||||
func (s *Site) Hugo() hugo.HugoInfo {
|
||||
if s.h == nil || s.h.hugoInfo.Environment == "" {
|
||||
panic("site: hugo: hugoInfo not initialized")
|
||||
}
|
||||
return s.h.hugoInfo
|
||||
}
|
||||
|
||||
// Returns the BaseURL for this Site.
|
||||
func (s *Site) BaseURL() string {
|
||||
return s.conf.C.BaseURL.WithPath
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Lastmod instead.
|
||||
func (s *Site) LastChange() time.Time {
|
||||
s.CheckReady()
|
||||
hugo.Deprecate(".Site.LastChange", "Use .Site.Lastmod instead.", "v0.123.0")
|
||||
return s.lastmod
|
||||
}
|
||||
|
||||
// Returns the last modification date of the content.
|
||||
func (s *Site) Lastmod() time.Time {
|
||||
return s.lastmod
|
||||
}
|
||||
|
||||
// Returns the Params configured for this site.
|
||||
func (s *Site) Params() maps.Params {
|
||||
return s.conf.Params
|
||||
}
|
||||
|
||||
// Deprecated: Use taxonomies instead.
|
||||
func (s *Site) Author() map[string]any {
|
||||
if len(s.conf.Author) != 0 {
|
||||
hugo.Deprecate(".Site.Author", "Use taxonomies instead.", "v0.124.0")
|
||||
}
|
||||
return s.conf.Author
|
||||
}
|
||||
|
||||
// Deprecated: Use taxonomies instead.
|
||||
func (s *Site) Authors() page.AuthorList {
|
||||
hugo.Deprecate(".Site.Authors", "Use taxonomies instead.", "v0.124.0")
|
||||
return page.AuthorList{}
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Params instead.
|
||||
func (s *Site) Social() map[string]string {
|
||||
hugo.Deprecate(".Site.Social", "Use .Site.Params instead.", "v0.124.0")
|
||||
return s.conf.Social
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Config.Services.Disqus.Shortname instead.
|
||||
func (s *Site) DisqusShortname() string {
|
||||
hugo.Deprecate(".Site.DisqusShortname", "Use .Site.Config.Services.Disqus.Shortname instead.", "v0.120.0")
|
||||
return s.Config().Services.Disqus.Shortname
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Config.Services.GoogleAnalytics.ID instead.
|
||||
func (s *Site) GoogleAnalytics() string {
|
||||
hugo.Deprecate(".Site.GoogleAnalytics", "Use .Site.Config.Services.GoogleAnalytics.ID instead.", "v0.120.0")
|
||||
return s.Config().Services.GoogleAnalytics.ID
|
||||
}
|
||||
|
||||
func (s *Site) Param(key any) (any, error) {
|
||||
return resource.Param(s, nil, key)
|
||||
}
|
||||
|
||||
// Returns a map of all the data inside /data.
|
||||
func (s *Site) Data() map[string]any {
|
||||
return s.s.h.Data()
|
||||
}
|
||||
|
||||
func (s *Site) BuildDrafts() bool {
|
||||
return s.conf.BuildDrafts
|
||||
}
|
||||
|
||||
// Deprecated: Use hugo.IsMultilingual instead.
|
||||
func (s *Site) IsMultiLingual() bool {
|
||||
hugo.Deprecate(".Site.IsMultiLingual", "Use hugo.IsMultilingual instead.", "v0.124.0")
|
||||
return s.h.isMultilingual()
|
||||
}
|
||||
|
||||
func (s *Site) LanguagePrefix() string {
|
||||
prefix := s.GetLanguagePrefix()
|
||||
if prefix == "" {
|
||||
return ""
|
||||
}
|
||||
return "/" + prefix
|
||||
}
|
||||
|
||||
func (s *Site) Site() page.Site {
|
||||
return page.WrapSite(s)
|
||||
}
|
||||
|
||||
func (s *Site) ForEeachIdentityByName(name string, f func(identity.Identity) bool) {
|
||||
if id, found := siteidentities.FromString(name); found {
|
||||
if f(id) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pages returns all pages.
|
||||
// This is for the current language only.
|
||||
func (s *Site) Pages() page.Pages {
|
||||
s.CheckReady()
|
||||
return s.pageMap.getPagesInSection(
|
||||
pageMapQueryPagesInSection{
|
||||
pageMapQueryPagesBelowPath: pageMapQueryPagesBelowPath{
|
||||
Path: "",
|
||||
KeyPart: "global",
|
||||
Include: pagePredicates.ShouldListGlobal,
|
||||
},
|
||||
Recursive: true,
|
||||
IncludeSelf: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// RegularPages returns all the regular pages.
|
||||
// This is for the current language only.
|
||||
func (s *Site) RegularPages() page.Pages {
|
||||
s.CheckReady()
|
||||
return s.pageMap.getPagesInSection(
|
||||
pageMapQueryPagesInSection{
|
||||
pageMapQueryPagesBelowPath: pageMapQueryPagesBelowPath{
|
||||
Path: "",
|
||||
KeyPart: "global",
|
||||
Include: pagePredicates.ShouldListGlobal.And(pagePredicates.KindPage),
|
||||
},
|
||||
Recursive: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// AllPages returns all pages for all sites.
|
||||
func (s *Site) AllPages() page.Pages {
|
||||
s.CheckReady()
|
||||
return s.h.Pages()
|
||||
}
|
||||
|
||||
// AllRegularPages returns all regular pages for all sites.
|
||||
func (s *Site) AllRegularPages() page.Pages {
|
||||
s.CheckReady()
|
||||
return s.h.RegularPages()
|
||||
}
|
||||
|
||||
func (s *Site) CheckReady() {
|
||||
if s.state != siteStateReady {
|
||||
panic("this method cannot be called before the site is fully initialized")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Site) Taxonomies() page.TaxonomyList {
|
||||
s.CheckReady()
|
||||
s.init.taxonomies.Do(context.Background())
|
||||
@@ -930,6 +1499,10 @@ func (hr hookRendererTemplate) RenderBlockquote(cctx context.Context, w hugio.Fl
|
||||
return hr.templateHandler.ExecuteWithContext(cctx, hr.templ, w, ctx)
|
||||
}
|
||||
|
||||
func (hr hookRendererTemplate) RenderTable(cctx context.Context, w hugio.FlexiWriter, ctx hooks.TableContext) error {
|
||||
return hr.templateHandler.ExecuteWithContext(cctx, hr.templ, w, ctx)
|
||||
}
|
||||
|
||||
func (hr hookRendererTemplate) ResolvePosition(ctx any) text.Position {
|
||||
return hr.resolvePosition(ctx)
|
||||
}
|
||||
|
||||
@@ -1,603 +0,0 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package hugolib
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/bep/logg"
|
||||
"github.com/gohugoio/hugo/cache/dynacache"
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/para"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/config/allconfig"
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
"github.com/gohugoio/hugo/hugolib/doctree"
|
||||
"github.com/gohugoio/hugo/hugolib/pagesfromdata"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/internal/warpc"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
"github.com/gohugoio/hugo/langs/i18n"
|
||||
"github.com/gohugoio/hugo/lazy"
|
||||
"github.com/gohugoio/hugo/modules"
|
||||
"github.com/gohugoio/hugo/navigation"
|
||||
"github.com/gohugoio/hugo/output"
|
||||
"github.com/gohugoio/hugo/publisher"
|
||||
"github.com/gohugoio/hugo/resources"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/gohugoio/hugo/resources/page/pagemeta"
|
||||
"github.com/gohugoio/hugo/resources/page/siteidentities"
|
||||
"github.com/gohugoio/hugo/resources/resource"
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
"github.com/gohugoio/hugo/tpl/tplimpl"
|
||||
)
|
||||
|
||||
var _ page.Site = (*Site)(nil)
|
||||
|
||||
type siteState int
|
||||
|
||||
const (
|
||||
siteStateInit siteState = iota
|
||||
siteStateReady
|
||||
)
|
||||
|
||||
type Site struct {
|
||||
state siteState
|
||||
conf *allconfig.Config
|
||||
language *langs.Language
|
||||
languagei int
|
||||
pageMap *pageMap
|
||||
|
||||
// The owning container.
|
||||
h *HugoSites
|
||||
|
||||
*deps.Deps
|
||||
|
||||
// Page navigation.
|
||||
*pageFinder
|
||||
taxonomies page.TaxonomyList
|
||||
menus navigation.Menus
|
||||
|
||||
// Shortcut to the home page. Note that this may be nil if
|
||||
// home page, for some odd reason, is disabled.
|
||||
home *pageState
|
||||
|
||||
// The last modification date of this site.
|
||||
lastmod time.Time
|
||||
|
||||
relatedDocsHandler *page.RelatedDocsHandler
|
||||
siteRefLinker
|
||||
publisher publisher.Publisher
|
||||
frontmatterHandler pagemeta.FrontMatterHandler
|
||||
|
||||
// The output formats that we need to render this site in. This slice
|
||||
// will be fixed once set.
|
||||
// This will be the union of Site.Pages' outputFormats.
|
||||
// This slice will be sorted.
|
||||
renderFormats output.Formats
|
||||
|
||||
// Lazily loaded site dependencies
|
||||
init *siteInit
|
||||
}
|
||||
|
||||
func (s *Site) Debug() {
|
||||
fmt.Println("Debugging site", s.Lang(), "=>")
|
||||
// fmt.Println(s.pageMap.testDump())
|
||||
}
|
||||
|
||||
// NewHugoSites creates HugoSites from the given config.
|
||||
func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
|
||||
conf := cfg.Configs.GetFirstLanguageConfig()
|
||||
|
||||
var logger loggers.Logger
|
||||
if cfg.TestLogger != nil {
|
||||
logger = cfg.TestLogger
|
||||
} else {
|
||||
var logHookLast func(e *logg.Entry) error
|
||||
if cfg.Configs.Base.PanicOnWarning {
|
||||
logHookLast = loggers.PanicOnWarningHook
|
||||
}
|
||||
if cfg.LogOut == nil {
|
||||
cfg.LogOut = os.Stdout
|
||||
}
|
||||
if cfg.LogLevel == 0 {
|
||||
cfg.LogLevel = logg.LevelWarn
|
||||
}
|
||||
|
||||
logOpts := loggers.Options{
|
||||
Level: cfg.LogLevel,
|
||||
DistinctLevel: logg.LevelWarn, // This will drop duplicate log warning and errors.
|
||||
HandlerPost: logHookLast,
|
||||
Stdout: cfg.LogOut,
|
||||
Stderr: cfg.LogOut,
|
||||
StoreErrors: conf.Watching(),
|
||||
SuppressStatements: conf.IgnoredLogs(),
|
||||
}
|
||||
logger = loggers.New(logOpts)
|
||||
|
||||
}
|
||||
|
||||
memCache := dynacache.New(dynacache.Options{Watching: conf.Watching(), Log: logger})
|
||||
|
||||
var h *HugoSites
|
||||
onSignalRebuild := func(ids ...identity.Identity) {
|
||||
// This channel is buffered, but make sure we do this in a non-blocking way.
|
||||
if cfg.ChangesFromBuild != nil {
|
||||
go func() {
|
||||
cfg.ChangesFromBuild <- ids
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
firstSiteDeps := &deps.Deps{
|
||||
Fs: cfg.Fs,
|
||||
Log: logger,
|
||||
Conf: conf,
|
||||
BuildState: &deps.BuildState{
|
||||
OnSignalRebuild: onSignalRebuild,
|
||||
},
|
||||
MemCache: memCache,
|
||||
TemplateProvider: tplimpl.DefaultTemplateProvider,
|
||||
TranslationProvider: i18n.NewTranslationProvider(),
|
||||
WasmDispatchers: warpc.AllDispatchers(
|
||||
warpc.Options{
|
||||
CompilationCacheDir: filepath.Join(conf.Dirs().CacheDir, "_warpc"),
|
||||
|
||||
// Katex is relatively slow.
|
||||
PoolSize: 8,
|
||||
Infof: logger.InfoCommand("wasm").Logf,
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
if err := firstSiteDeps.Init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
confm := cfg.Configs
|
||||
if err := confm.Validate(logger); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var sites []*Site
|
||||
|
||||
ns := &contentNodeShifter{
|
||||
numLanguages: len(confm.Languages),
|
||||
}
|
||||
|
||||
treeConfig := doctree.Config[contentNodeI]{
|
||||
Shifter: ns,
|
||||
}
|
||||
|
||||
pageTrees := &pageTrees{
|
||||
treePages: doctree.New(
|
||||
treeConfig,
|
||||
),
|
||||
treeResources: doctree.New(
|
||||
treeConfig,
|
||||
),
|
||||
treeTaxonomyEntries: doctree.NewTreeShiftTree[*weightedContentNode](doctree.DimensionLanguage.Index(), len(confm.Languages)),
|
||||
treePagesFromTemplateAdapters: doctree.NewTreeShiftTree[*pagesfromdata.PagesFromTemplate](doctree.DimensionLanguage.Index(), len(confm.Languages)),
|
||||
}
|
||||
|
||||
pageTrees.createMutableTrees()
|
||||
|
||||
for i, confp := range confm.ConfigLangs() {
|
||||
language := confp.Language()
|
||||
if language.Disabled {
|
||||
continue
|
||||
}
|
||||
k := language.Lang
|
||||
conf := confm.LanguageConfigMap[k]
|
||||
frontmatterHandler, err := pagemeta.NewFrontmatterHandler(firstSiteDeps.Log, conf.Frontmatter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
langs.SetParams(language, conf.Params)
|
||||
|
||||
s := &Site{
|
||||
conf: conf,
|
||||
language: language,
|
||||
languagei: i,
|
||||
frontmatterHandler: frontmatterHandler,
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
firstSiteDeps.Site = s
|
||||
s.Deps = firstSiteDeps
|
||||
} else {
|
||||
d, err := firstSiteDeps.Clone(s, confp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Deps = d
|
||||
}
|
||||
|
||||
s.pageMap = newPageMap(i, s, memCache, pageTrees)
|
||||
|
||||
s.pageFinder = newPageFinder(s.pageMap)
|
||||
s.siteRefLinker, err = newSiteRefLinker(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Set up the main publishing chain.
|
||||
pub, err := publisher.NewDestinationPublisher(
|
||||
firstSiteDeps.ResourceSpec,
|
||||
s.conf.OutputFormats.Config,
|
||||
s.conf.MediaTypes.Config,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.publisher = pub
|
||||
s.relatedDocsHandler = page.NewRelatedDocsHandler(s.conf.Related)
|
||||
// Site deps end.
|
||||
|
||||
s.prepareInits()
|
||||
sites = append(sites, s)
|
||||
}
|
||||
|
||||
if len(sites) == 0 {
|
||||
return nil, errors.New("no sites to build")
|
||||
}
|
||||
|
||||
// Pull the default content language to the top, then sort the sites by language weight (if set) or lang.
|
||||
defaultContentLanguage := confm.Base.DefaultContentLanguage
|
||||
sort.Slice(sites, func(i, j int) bool {
|
||||
li := sites[i].language
|
||||
lj := sites[j].language
|
||||
if li.Lang == defaultContentLanguage {
|
||||
return true
|
||||
}
|
||||
|
||||
if lj.Lang == defaultContentLanguage {
|
||||
return false
|
||||
}
|
||||
|
||||
if li.Weight != lj.Weight {
|
||||
return li.Weight < lj.Weight
|
||||
}
|
||||
return li.Lang < lj.Lang
|
||||
})
|
||||
|
||||
var err error
|
||||
h, err = newHugoSites(cfg, firstSiteDeps, pageTrees, sites)
|
||||
if err == nil && h == nil {
|
||||
panic("hugo: newHugoSitesNew returned nil error and nil HugoSites")
|
||||
}
|
||||
|
||||
return h, err
|
||||
}
|
||||
|
||||
func newHugoSites(cfg deps.DepsCfg, d *deps.Deps, pageTrees *pageTrees, sites []*Site) (*HugoSites, error) {
|
||||
numWorkers := config.GetNumWorkerMultiplier()
|
||||
numWorkersSite := numWorkers
|
||||
if numWorkersSite > len(sites) {
|
||||
numWorkersSite = len(sites)
|
||||
}
|
||||
workersSite := para.New(numWorkersSite)
|
||||
|
||||
h := &HugoSites{
|
||||
Sites: sites,
|
||||
Deps: sites[0].Deps,
|
||||
Configs: cfg.Configs,
|
||||
workersSite: workersSite,
|
||||
numWorkersSites: numWorkers,
|
||||
numWorkers: numWorkers,
|
||||
pageTrees: pageTrees,
|
||||
cachePages: dynacache.GetOrCreatePartition[string,
|
||||
page.Pages](d.MemCache, "/pags/all",
|
||||
dynacache.OptionsPartition{Weight: 10, ClearWhen: dynacache.ClearOnRebuild},
|
||||
),
|
||||
cacheContentSource: dynacache.GetOrCreatePartition[string, *resources.StaleValue[[]byte]](d.MemCache, "/cont/src", dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
|
||||
translationKeyPages: maps.NewSliceCache[page.Page](),
|
||||
currentSite: sites[0],
|
||||
skipRebuildForFilenames: make(map[string]bool),
|
||||
init: &hugoSitesInit{
|
||||
data: lazy.New(),
|
||||
layouts: lazy.New(),
|
||||
gitInfo: lazy.New(),
|
||||
},
|
||||
}
|
||||
|
||||
// Assemble dependencies to be used in hugo.Deps.
|
||||
var dependencies []*hugo.Dependency
|
||||
var depFromMod func(m modules.Module) *hugo.Dependency
|
||||
depFromMod = func(m modules.Module) *hugo.Dependency {
|
||||
dep := &hugo.Dependency{
|
||||
Path: m.Path(),
|
||||
Version: m.Version(),
|
||||
Time: m.Time(),
|
||||
Vendor: m.Vendor(),
|
||||
}
|
||||
|
||||
// These are pointers, but this all came from JSON so there's no recursive navigation,
|
||||
// so just create new values.
|
||||
if m.Replace() != nil {
|
||||
dep.Replace = depFromMod(m.Replace())
|
||||
}
|
||||
if m.Owner() != nil {
|
||||
dep.Owner = depFromMod(m.Owner())
|
||||
}
|
||||
return dep
|
||||
}
|
||||
for _, m := range d.Paths.AllModules() {
|
||||
dependencies = append(dependencies, depFromMod(m))
|
||||
}
|
||||
|
||||
h.hugoInfo = hugo.NewInfo(h.Configs.GetFirstLanguageConfig(), dependencies)
|
||||
|
||||
var prototype *deps.Deps
|
||||
for i, s := range sites {
|
||||
s.h = h
|
||||
if err := s.Deps.Compile(prototype); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if i == 0 {
|
||||
prototype = s.Deps
|
||||
}
|
||||
}
|
||||
|
||||
h.fatalErrorHandler = &fatalErrorHandler{
|
||||
h: h,
|
||||
donec: make(chan bool),
|
||||
}
|
||||
|
||||
h.init.data.Add(func(context.Context) (any, error) {
|
||||
err := h.loadData()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load data: %w", err)
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
|
||||
h.init.layouts.Add(func(context.Context) (any, error) {
|
||||
for _, s := range h.Sites {
|
||||
if err := s.Tmpl().(tpl.TemplateManager).MarkReady(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
|
||||
h.init.gitInfo.Add(func(context.Context) (any, error) {
|
||||
err := h.loadGitInfo()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load Git info: %w", err)
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// Deprecated: Use hugo.IsServer instead.
|
||||
func (s *Site) IsServer() bool {
|
||||
hugo.Deprecate(".Site.IsServer", "Use hugo.IsServer instead.", "v0.120.0")
|
||||
return s.conf.Internal.Running
|
||||
}
|
||||
|
||||
// Returns the server port.
|
||||
func (s *Site) ServerPort() int {
|
||||
return s.conf.C.BaseURL.Port()
|
||||
}
|
||||
|
||||
// Returns the configured title for this Site.
|
||||
func (s *Site) Title() string {
|
||||
return s.conf.Title
|
||||
}
|
||||
|
||||
func (s *Site) Copyright() string {
|
||||
return s.conf.Copyright
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Home.OutputFormats.Get "rss" instead.
|
||||
func (s *Site) RSSLink() template.URL {
|
||||
hugo.Deprecate(".Site.RSSLink", "Use the Output Format's Permalink method instead, e.g. .OutputFormats.Get \"RSS\".Permalink", "v0.114.0")
|
||||
rssOutputFormat := s.home.OutputFormats().Get("rss")
|
||||
return template.URL(rssOutputFormat.Permalink())
|
||||
}
|
||||
|
||||
func (s *Site) Config() page.SiteConfig {
|
||||
return page.SiteConfig{
|
||||
Privacy: s.conf.Privacy,
|
||||
Services: s.conf.Services,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Site) LanguageCode() string {
|
||||
return s.Language().LanguageCode()
|
||||
}
|
||||
|
||||
// Returns all Sites for all languages.
|
||||
func (s *Site) Sites() page.Sites {
|
||||
sites := make(page.Sites, len(s.h.Sites))
|
||||
for i, s := range s.h.Sites {
|
||||
sites[i] = s.Site()
|
||||
}
|
||||
return sites
|
||||
}
|
||||
|
||||
// Returns Site currently rendering.
|
||||
func (s *Site) Current() page.Site {
|
||||
return s.h.currentSite
|
||||
}
|
||||
|
||||
// MainSections returns the list of main sections.
|
||||
func (s *Site) MainSections() []string {
|
||||
s.CheckReady()
|
||||
return s.conf.C.MainSections
|
||||
}
|
||||
|
||||
// Returns a struct with some information about the build.
|
||||
func (s *Site) Hugo() hugo.HugoInfo {
|
||||
if s.h == nil || s.h.hugoInfo.Environment == "" {
|
||||
panic("site: hugo: hugoInfo not initialized")
|
||||
}
|
||||
return s.h.hugoInfo
|
||||
}
|
||||
|
||||
// Returns the BaseURL for this Site.
|
||||
func (s *Site) BaseURL() string {
|
||||
return s.conf.C.BaseURL.WithPath
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Lastmod instead.
|
||||
func (s *Site) LastChange() time.Time {
|
||||
s.CheckReady()
|
||||
hugo.Deprecate(".Site.LastChange", "Use .Site.Lastmod instead.", "v0.123.0")
|
||||
return s.lastmod
|
||||
}
|
||||
|
||||
// Returns the last modification date of the content.
|
||||
func (s *Site) Lastmod() time.Time {
|
||||
return s.lastmod
|
||||
}
|
||||
|
||||
// Returns the Params configured for this site.
|
||||
func (s *Site) Params() maps.Params {
|
||||
return s.conf.Params
|
||||
}
|
||||
|
||||
// Deprecated: Use taxonomies instead.
|
||||
func (s *Site) Author() map[string]any {
|
||||
if len(s.conf.Author) != 0 {
|
||||
hugo.Deprecate(".Site.Author", "Use taxonomies instead.", "v0.124.0")
|
||||
}
|
||||
return s.conf.Author
|
||||
}
|
||||
|
||||
// Deprecated: Use taxonomies instead.
|
||||
func (s *Site) Authors() page.AuthorList {
|
||||
hugo.Deprecate(".Site.Authors", "Use taxonomies instead.", "v0.124.0")
|
||||
return page.AuthorList{}
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Params instead.
|
||||
func (s *Site) Social() map[string]string {
|
||||
hugo.Deprecate(".Site.Social", "Use .Site.Params instead.", "v0.124.0")
|
||||
return s.conf.Social
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Config.Services.Disqus.Shortname instead.
|
||||
func (s *Site) DisqusShortname() string {
|
||||
hugo.Deprecate(".Site.DisqusShortname", "Use .Site.Config.Services.Disqus.Shortname instead.", "v0.120.0")
|
||||
return s.Config().Services.Disqus.Shortname
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Config.Services.GoogleAnalytics.ID instead.
|
||||
func (s *Site) GoogleAnalytics() string {
|
||||
hugo.Deprecate(".Site.GoogleAnalytics", "Use .Site.Config.Services.GoogleAnalytics.ID instead.", "v0.120.0")
|
||||
return s.Config().Services.GoogleAnalytics.ID
|
||||
}
|
||||
|
||||
func (s *Site) Param(key any) (any, error) {
|
||||
return resource.Param(s, nil, key)
|
||||
}
|
||||
|
||||
// Returns a map of all the data inside /data.
|
||||
func (s *Site) Data() map[string]any {
|
||||
return s.s.h.Data()
|
||||
}
|
||||
|
||||
func (s *Site) BuildDrafts() bool {
|
||||
return s.conf.BuildDrafts
|
||||
}
|
||||
|
||||
// Deprecated: Use hugo.IsMultilingual instead.
|
||||
func (s *Site) IsMultiLingual() bool {
|
||||
hugo.Deprecate(".Site.IsMultiLingual", "Use hugo.IsMultilingual instead.", "v0.124.0")
|
||||
return s.h.isMultilingual()
|
||||
}
|
||||
|
||||
func (s *Site) LanguagePrefix() string {
|
||||
prefix := s.GetLanguagePrefix()
|
||||
if prefix == "" {
|
||||
return ""
|
||||
}
|
||||
return "/" + prefix
|
||||
}
|
||||
|
||||
func (s *Site) Site() page.Site {
|
||||
return page.WrapSite(s)
|
||||
}
|
||||
|
||||
func (s *Site) ForEeachIdentityByName(name string, f func(identity.Identity) bool) {
|
||||
if id, found := siteidentities.FromString(name); found {
|
||||
if f(id) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pages returns all pages.
|
||||
// This is for the current language only.
|
||||
func (s *Site) Pages() page.Pages {
|
||||
s.CheckReady()
|
||||
return s.pageMap.getPagesInSection(
|
||||
pageMapQueryPagesInSection{
|
||||
pageMapQueryPagesBelowPath: pageMapQueryPagesBelowPath{
|
||||
Path: "",
|
||||
KeyPart: "global",
|
||||
Include: pagePredicates.ShouldListGlobal,
|
||||
},
|
||||
Recursive: true,
|
||||
IncludeSelf: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// RegularPages returns all the regular pages.
|
||||
// This is for the current language only.
|
||||
func (s *Site) RegularPages() page.Pages {
|
||||
s.CheckReady()
|
||||
return s.pageMap.getPagesInSection(
|
||||
pageMapQueryPagesInSection{
|
||||
pageMapQueryPagesBelowPath: pageMapQueryPagesBelowPath{
|
||||
Path: "",
|
||||
KeyPart: "global",
|
||||
Include: pagePredicates.ShouldListGlobal.And(pagePredicates.KindPage),
|
||||
},
|
||||
Recursive: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// AllPages returns all pages for all sites.
|
||||
func (s *Site) AllPages() page.Pages {
|
||||
s.CheckReady()
|
||||
return s.h.Pages()
|
||||
}
|
||||
|
||||
// AllRegularPages returns all regular pages for all sites.
|
||||
func (s *Site) AllRegularPages() page.Pages {
|
||||
s.CheckReady()
|
||||
return s.h.RegularPages()
|
||||
}
|
||||
|
||||
func (s *Site) CheckReady() {
|
||||
if s.state != siteStateReady {
|
||||
panic("this method cannot be called before the site is fully initialized")
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user