mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-23 21:53:09 +02:00
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:
@@ -15,6 +15,7 @@ package hugolib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"path"
|
||||
@@ -302,13 +303,44 @@ const (
|
||||
innerCleanupExpand = "$1"
|
||||
)
|
||||
|
||||
func renderShortcode(
|
||||
func prepareShortcode(
|
||||
ctx context.Context,
|
||||
level int,
|
||||
s *Site,
|
||||
tplVariants tpl.TemplateVariants,
|
||||
sc *shortcode,
|
||||
parent *ShortcodeWithPage,
|
||||
p *pageState) (string, bool, error) {
|
||||
p *pageState) (shortcodeRenderer, error) {
|
||||
|
||||
toParseErr := func(err error) error {
|
||||
return p.parseError(fmt.Errorf("failed to render shortcode %q: %w", sc.name, err), p.source.parsed.Input(), sc.pos)
|
||||
}
|
||||
|
||||
// Allow the caller to delay the rendering of the shortcode if needed.
|
||||
var fn shortcodeRenderFunc = func(ctx context.Context) ([]byte, bool, error) {
|
||||
r, err := doRenderShortcode(ctx, level, s, tplVariants, sc, parent, p)
|
||||
if err != nil {
|
||||
return nil, false, toParseErr(err)
|
||||
}
|
||||
b, hasVariants, err := r.renderShortcode(ctx)
|
||||
if err != nil {
|
||||
return nil, false, toParseErr(err)
|
||||
}
|
||||
return b, hasVariants, nil
|
||||
}
|
||||
|
||||
return fn, nil
|
||||
|
||||
}
|
||||
|
||||
func doRenderShortcode(
|
||||
ctx context.Context,
|
||||
level int,
|
||||
s *Site,
|
||||
tplVariants tpl.TemplateVariants,
|
||||
sc *shortcode,
|
||||
parent *ShortcodeWithPage,
|
||||
p *pageState) (shortcodeRenderer, error) {
|
||||
var tmpl tpl.Template
|
||||
|
||||
// Tracks whether this shortcode or any of its children has template variations
|
||||
@@ -319,7 +351,7 @@ func renderShortcode(
|
||||
|
||||
if sc.isInline {
|
||||
if !p.s.ExecHelper.Sec().EnableInlineShortcodes {
|
||||
return "", false, nil
|
||||
return zeroShortcode, nil
|
||||
}
|
||||
templName := path.Join("_inline_shortcode", p.File().Path(), sc.name)
|
||||
if sc.isClosing {
|
||||
@@ -332,7 +364,7 @@ func renderShortcode(
|
||||
pos := fe.Position()
|
||||
pos.LineNumber += p.posOffset(sc.pos).LineNumber
|
||||
fe = fe.UpdatePosition(pos)
|
||||
return "", false, p.wrapError(fe)
|
||||
return zeroShortcode, p.wrapError(fe)
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -340,7 +372,7 @@ func renderShortcode(
|
||||
var found bool
|
||||
tmpl, found = s.TextTmpl().Lookup(templName)
|
||||
if !found {
|
||||
return "", false, fmt.Errorf("no earlier definition of shortcode %q found", sc.name)
|
||||
return zeroShortcode, fmt.Errorf("no earlier definition of shortcode %q found", sc.name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -348,7 +380,7 @@ func renderShortcode(
|
||||
tmpl, found, more = s.Tmpl().LookupVariant(sc.name, tplVariants)
|
||||
if !found {
|
||||
s.Log.Errorf("Unable to locate template for shortcode %q in page %q", sc.name, p.File().Path())
|
||||
return "", false, nil
|
||||
return zeroShortcode, nil
|
||||
}
|
||||
hasVariants = hasVariants || more
|
||||
}
|
||||
@@ -365,16 +397,20 @@ func renderShortcode(
|
||||
case string:
|
||||
inner += innerData
|
||||
case *shortcode:
|
||||
s, more, err := renderShortcode(level+1, s, tplVariants, innerData, data, p)
|
||||
s, err := prepareShortcode(ctx, level+1, s, tplVariants, innerData, data, p)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
return zeroShortcode, err
|
||||
}
|
||||
ss, more, err := s.renderShortcodeString(ctx)
|
||||
hasVariants = hasVariants || more
|
||||
inner += s
|
||||
if err != nil {
|
||||
return zeroShortcode, err
|
||||
}
|
||||
inner += ss
|
||||
default:
|
||||
s.Log.Errorf("Illegal state on shortcode rendering of %q in page %q. Illegal type in inner data: %s ",
|
||||
sc.name, p.File().Path(), reflect.TypeOf(innerData))
|
||||
return "", false, nil
|
||||
return zeroShortcode, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,9 +418,9 @@ func renderShortcode(
|
||||
// shortcode.
|
||||
if sc.doMarkup && (level > 0 || sc.configVersion() == 1) {
|
||||
var err error
|
||||
b, err := p.pageOutput.contentRenderer.RenderContent([]byte(inner), false)
|
||||
b, err := p.pageOutput.contentRenderer.RenderContent(ctx, []byte(inner), false)
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
return zeroShortcode, err
|
||||
}
|
||||
|
||||
newInner := b.Bytes()
|
||||
@@ -418,14 +454,14 @@ func renderShortcode(
|
||||
|
||||
}
|
||||
|
||||
result, err := renderShortcodeWithPage(s.Tmpl(), tmpl, data)
|
||||
result, err := renderShortcodeWithPage(ctx, s.Tmpl(), tmpl, data)
|
||||
|
||||
if err != nil && sc.isInline {
|
||||
fe := herrors.NewFileErrorFromName(err, p.File().Filename())
|
||||
pos := fe.Position()
|
||||
pos.LineNumber += p.posOffset(sc.pos).LineNumber
|
||||
fe = fe.UpdatePosition(pos)
|
||||
return "", false, fe
|
||||
return zeroShortcode, fe
|
||||
}
|
||||
|
||||
if len(sc.inner) == 0 && len(sc.indentation) > 0 {
|
||||
@@ -444,7 +480,7 @@ func renderShortcode(
|
||||
bp.PutBuffer(b)
|
||||
}
|
||||
|
||||
return result, hasVariants, err
|
||||
return prerenderedShortcode{s: result, hasVariants: hasVariants}, err
|
||||
}
|
||||
|
||||
func (s *shortcodeHandler) hasShortcodes() bool {
|
||||
@@ -473,28 +509,24 @@ func (s *shortcodeHandler) hasName(name string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *shortcodeHandler) renderShortcodesForPage(p *pageState, f output.Format) (map[string]string, bool, error) {
|
||||
rendered := make(map[string]string)
|
||||
func (s *shortcodeHandler) prepareShortcodesForPage(ctx context.Context, p *pageState, f output.Format) (map[string]shortcodeRenderer, error) {
|
||||
rendered := make(map[string]shortcodeRenderer)
|
||||
|
||||
tplVariants := tpl.TemplateVariants{
|
||||
Language: p.Language().Lang,
|
||||
OutputFormat: f,
|
||||
}
|
||||
|
||||
var hasVariants bool
|
||||
|
||||
for _, v := range s.shortcodes {
|
||||
s, more, err := renderShortcode(0, s.s, tplVariants, v, nil, p)
|
||||
s, err := prepareShortcode(ctx, 0, s.s, tplVariants, v, nil, p)
|
||||
if err != nil {
|
||||
err = p.parseError(fmt.Errorf("failed to render shortcode %q: %w", v.name, err), p.source.parsed.Input(), v.pos)
|
||||
return nil, false, err
|
||||
return nil, err
|
||||
}
|
||||
hasVariants = hasVariants || more
|
||||
rendered[v.placeholder] = s
|
||||
|
||||
}
|
||||
|
||||
return rendered, hasVariants, nil
|
||||
return rendered, nil
|
||||
}
|
||||
|
||||
func (s *shortcodeHandler) parseError(err error, input []byte, pos int) error {
|
||||
@@ -668,11 +700,11 @@ Loop:
|
||||
|
||||
// Replace prefixed shortcode tokens with the real content.
|
||||
// Note: This function will rewrite the input slice.
|
||||
func replaceShortcodeTokens(source []byte, replacements map[string]string) ([]byte, error) {
|
||||
if len(replacements) == 0 {
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func expandShortcodeTokens(
|
||||
ctx context.Context,
|
||||
source []byte,
|
||||
tokenHandler func(ctx context.Context, token string) ([]byte, error),
|
||||
) ([]byte, error) {
|
||||
start := 0
|
||||
|
||||
pre := []byte(shortcodePlaceholderPrefix)
|
||||
@@ -691,8 +723,11 @@ func replaceShortcodeTokens(source []byte, replacements map[string]string) ([]by
|
||||
}
|
||||
|
||||
end := j + postIdx + 4
|
||||
|
||||
newVal := []byte(replacements[string(source[j:end])])
|
||||
key := string(source[j:end])
|
||||
newVal, err := tokenHandler(ctx, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Issue #1148: Check for wrapping p-tags <p>
|
||||
if j >= 3 && bytes.Equal(source[j-3:j], pStart) {
|
||||
@@ -712,11 +747,11 @@ func replaceShortcodeTokens(source []byte, replacements map[string]string) ([]by
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func renderShortcodeWithPage(h tpl.TemplateHandler, tmpl tpl.Template, data *ShortcodeWithPage) (string, error) {
|
||||
func renderShortcodeWithPage(ctx context.Context, h tpl.TemplateHandler, tmpl tpl.Template, data *ShortcodeWithPage) (string, error) {
|
||||
buffer := bp.GetBuffer()
|
||||
defer bp.PutBuffer(buffer)
|
||||
|
||||
err := h.Execute(tmpl, buffer, data)
|
||||
err := h.ExecuteWithContext(ctx, tmpl, buffer, data)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to process shortcode: %w", err)
|
||||
}
|
||||
|
Reference in New Issue
Block a user