Add page fragments support to Related

The main topic of this commit is that you can now index fragments (content heading identifiers) when calling `.Related`.

You can do this by:

* Configure one or more indices with type `fragments`
* The name of those index configurations maps to an (optional) front matter slice with fragment references. This allows you to link
page<->fragment and page<->page.
* This also will index all the fragments (heading identifiers) of the pages.

It's also possible to use type `fragments` indices in shortcode, e.g.:

```
{{ $related := site.RegularPages.Related .Page }}
```

But, and this is important, you need to include the shortcode using the `{{<` delimiter. Not doing so will create infinite loops and timeouts.

This commit also:

* Adds two new methods to Page: Fragments (can also be used to build ToC) and HeadingsFiltered (this is only used in Related Content with
index type `fragments` and `enableFilter` set to true.
* Consolidates all `.Related*` methods into one, which takes either a `Page` or an options map as its only argument.
* Add `context.Context` to all of the content related Page API. Turns out it wasn't strictly needed for this particular feature, but it will
soon become usefil, e.g. in #9339.

Closes #10711
Updates #9339
Updates #10725
This commit is contained in:
Bjørn Erik Pedersen
2023-02-11 16:20:24 +01:00
parent 0afec0a9f4
commit 90da7664bf
66 changed files with 1363 additions and 829 deletions

View File

@@ -14,162 +14,9 @@
package hugolib
import (
"io"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/htesting"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/hugofs"
)
// We have many tests for the different resize operations etc. in the resource package,
// this is an integration test.
func TestImageOps(t *testing.T) {
c := qt.New(t)
// Make this a real as possible.
workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "image-resize")
c.Assert(err, qt.IsNil)
defer clean()
newBuilder := func(timeout any) *sitesBuilder {
v := config.NewWithTestDefaults()
v.Set("workingDir", workDir)
v.Set("baseURL", "https://example.org")
v.Set("timeout", timeout)
b := newTestSitesBuilder(t).WithWorkingDir(workDir)
b.Fs = hugofs.NewDefault(v)
b.WithViper(v)
b.WithContent("mybundle/index.md", `
---
title: "My bundle"
---
{{< imgproc >}}
`)
b.WithTemplatesAdded(
"shortcodes/imgproc.html", `
{{ $img := resources.Get "images/sunset.jpg" }}
{{ $r := $img.Resize "129x239" }}
IMG SHORTCODE: {{ $r.RelPermalink }}/{{ $r.Width }}
`,
"index.html", `
{{ $p := .Site.GetPage "mybundle" }}
{{ $img1 := resources.Get "images/sunset.jpg" }}
{{ $img2 := $p.Resources.GetMatch "sunset.jpg" }}
{{ $img3 := resources.GetMatch "images/*.jpg" }}
{{ $r := $img1.Resize "123x234" }}
{{ $r2 := $r.Resize "12x23" }}
{{ $b := $img2.Resize "345x678" }}
{{ $b2 := $b.Resize "34x67" }}
{{ $c := $img3.Resize "456x789" }}
{{ $fingerprinted := $img1.Resize "350x" | fingerprint }}
{{ $images := slice $r $r2 $b $b2 $c $fingerprinted }}
{{ range $i, $r := $images }}
{{ printf "Resized%d:" (add $i 1) }} {{ $r.Name }}|{{ $r.Width }}|{{ $r.Height }}|{{ $r.MediaType }}|{{ $r.RelPermalink }}|
{{ end }}
{{ $blurryGrayscale1 := $r | images.Filter images.Grayscale (images.GaussianBlur 8) }}
BG1: {{ $blurryGrayscale1.RelPermalink }}/{{ $blurryGrayscale1.Width }}
{{ $blurryGrayscale2 := $r.Filter images.Grayscale (images.GaussianBlur 8) }}
BG2: {{ $blurryGrayscale2.RelPermalink }}/{{ $blurryGrayscale2.Width }}
{{ $blurryGrayscale2_2 := $r.Filter images.Grayscale (images.GaussianBlur 8) }}
BG2_2: {{ $blurryGrayscale2_2.RelPermalink }}/{{ $blurryGrayscale2_2.Width }}
{{ $filters := slice images.Grayscale (images.GaussianBlur 9) }}
{{ $blurryGrayscale3 := $r | images.Filter $filters }}
BG3: {{ $blurryGrayscale3.RelPermalink }}/{{ $blurryGrayscale3.Width }}
{{ $blurryGrayscale4 := $r.Filter $filters }}
BG4: {{ $blurryGrayscale4.RelPermalink }}/{{ $blurryGrayscale4.Width }}
{{ $p.Content }}
`)
return b
}
imageDir := filepath.Join(workDir, "assets", "images")
bundleDir := filepath.Join(workDir, "content", "mybundle")
c.Assert(os.MkdirAll(imageDir, 0777), qt.IsNil)
c.Assert(os.MkdirAll(bundleDir, 0777), qt.IsNil)
src, err := os.Open("testdata/sunset.jpg")
c.Assert(err, qt.IsNil)
out, err := os.Create(filepath.Join(imageDir, "sunset.jpg"))
c.Assert(err, qt.IsNil)
_, err = io.Copy(out, src)
c.Assert(err, qt.IsNil)
out.Close()
src.Seek(0, 0)
out, err = os.Create(filepath.Join(bundleDir, "sunset.jpg"))
c.Assert(err, qt.IsNil)
_, err = io.Copy(out, src)
c.Assert(err, qt.IsNil)
out.Close()
src.Close()
// First build it with a very short timeout to trigger errors.
b := newBuilder("10ns")
imgExpect := `
Resized1: images/sunset.jpg|123|234|image/jpeg|/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg|
Resized2: images/sunset.jpg|12|23|image/jpeg|/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_ada4bb1a57f77a63306e3bd67286248e.jpg|
Resized3: sunset.jpg|345|678|image/jpeg|/mybundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_345x678_resize_q75_box.jpg|
Resized4: sunset.jpg|34|67|image/jpeg|/mybundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_44d8c928664d7c5a67377c6ec58425ce.jpg|
Resized5: images/sunset.jpg|456|789|image/jpeg|/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_456x789_resize_q75_box.jpg|
Resized6: images/sunset.jpg|350|219|image/jpeg|/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_350x0_resize_q75_box.a86fe88d894e5db613f6aa8a80538fefc25b20fa24ba0d782c057adcef616f56.jpg|
BG1: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_2ae8bb993431ec1aec40fe59927b46b4.jpg/123
BG2: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_2ae8bb993431ec1aec40fe59927b46b4.jpg/123
BG3: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_ed7740a90b82802261c2fbdb98bc8082.jpg/123
BG4: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_ed7740a90b82802261c2fbdb98bc8082.jpg/123
IMG SHORTCODE: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_129x239_resize_q75_box.jpg/129
`
assertImages := func() {
b.Helper()
b.AssertFileContent("public/index.html", imgExpect)
b.AssertImage(350, 219, "public/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_350x0_resize_q75_box.a86fe88d894e5db613f6aa8a80538fefc25b20fa24ba0d782c057adcef616f56.jpg")
b.AssertImage(129, 239, "public/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_129x239_resize_q75_box.jpg")
}
err = b.BuildE(BuildCfg{})
if runtime.GOOS != "windows" && !strings.Contains(runtime.GOARCH, "arm") && !htesting.IsGitHubAction() {
// TODO(bep)
c.Assert(err, qt.Not(qt.IsNil))
}
b = newBuilder(29000)
b.Build(BuildCfg{})
assertImages()
// Truncate one image.
imgInCache := filepath.Join(workDir, "resources/_gen/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_ed7740a90b82802261c2fbdb98bc8082.jpg")
f, err := os.Create(imgInCache)
c.Assert(err, qt.IsNil)
f.Close()
// Build it again to make sure we read images from file cache.
b = newBuilder("30s")
b.Build(BuildCfg{})
assertImages()
}
func TestImageResizeMultilingual(t *testing.T) {
b := newTestSitesBuilder(t).WithConfigFile("toml", `
baseURL="https://example.org"