mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-27 22:09:53 +02:00
Pass .RenderShortcodes' Page to render hooks as .PageInner
The main use case for this is to resolve links and resources (e.g. images) relative to the included `Page`. A typical `include` would similar to this: ```handlebars {{ with site.GetPage (.Get 0) }} {{ .RenderShortcodes }} {{ end }} ``` And when used in a Markdown file: ```markdown {{% include "/posts/p1" %}} ``` Any render hook triggered while rendering `/posts/p1` will get `/posts/p1` when calling `.PageInner`. Note that * This is only relevant for shortcodes included with `{{%` that calls `.RenderShortcodes`. * `.PageInner` is available in all render hooks that, before this commit, received `.Page`. * `.PageInner` will fall back to the value of `.Page` if not relevant and will always have a value. Fixes #12356
This commit is contained in:
165
markup/goldmark/hugocontext/hugocontext.go
Normal file
165
markup/goldmark/hugocontext/hugocontext.go
Normal file
@@ -0,0 +1,165 @@
|
||||
// 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 hugocontext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/gohugoio/hugo/bufferpool"
|
||||
"github.com/gohugoio/hugo/markup/goldmark/internal/render"
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
"github.com/yuin/goldmark/renderer"
|
||||
"github.com/yuin/goldmark/text"
|
||||
"github.com/yuin/goldmark/util"
|
||||
)
|
||||
|
||||
func New() goldmark.Extender {
|
||||
return &hugoContextExtension{}
|
||||
}
|
||||
|
||||
// Wrap wraps the given byte slice in a Hugo context that used to determine the correct Page
|
||||
// in .RenderShortcodes.
|
||||
func Wrap(b []byte, pid uint64) []byte {
|
||||
buf := bufferpool.GetBuffer()
|
||||
defer bufferpool.PutBuffer(buf)
|
||||
buf.Write(prefix)
|
||||
buf.WriteString(" pid=")
|
||||
buf.WriteString(strconv.FormatUint(pid, 10))
|
||||
buf.Write(endDelim)
|
||||
buf.WriteByte('\n')
|
||||
buf.Write(b)
|
||||
buf.Write(prefix)
|
||||
buf.Write(closingDelimAndNewline)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
var kindHugoContext = ast.NewNodeKind("HugoContext")
|
||||
|
||||
// HugoContext is a node that represents a Hugo context.
|
||||
type HugoContext struct {
|
||||
ast.BaseInline
|
||||
|
||||
Closing bool
|
||||
|
||||
// Internal page ID. Not persisted.
|
||||
Pid uint64
|
||||
}
|
||||
|
||||
// Dump implements Node.Dump.
|
||||
func (n *HugoContext) Dump(source []byte, level int) {
|
||||
m := map[string]string{}
|
||||
m["Pid"] = fmt.Sprintf("%v", n.Pid)
|
||||
ast.DumpHelper(n, source, level, m, nil)
|
||||
}
|
||||
|
||||
func (n *HugoContext) parseAttrs(attrBytes []byte) {
|
||||
keyPairs := bytes.Split(attrBytes, []byte(" "))
|
||||
for _, keyPair := range keyPairs {
|
||||
kv := bytes.Split(keyPair, []byte("="))
|
||||
if len(kv) != 2 {
|
||||
continue
|
||||
}
|
||||
key := string(kv[0])
|
||||
val := string(kv[1])
|
||||
switch key {
|
||||
case "pid":
|
||||
pid, _ := strconv.ParseUint(val, 10, 64)
|
||||
n.Pid = pid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HugoContext) Kind() ast.NodeKind {
|
||||
return kindHugoContext
|
||||
}
|
||||
|
||||
var (
|
||||
prefix = []byte("{{__hugo_ctx")
|
||||
endDelim = []byte("}}")
|
||||
closingDelimAndNewline = []byte("/}}\n")
|
||||
)
|
||||
|
||||
var _ parser.InlineParser = (*hugoContextParser)(nil)
|
||||
|
||||
type hugoContextParser struct{}
|
||||
|
||||
func (s *hugoContextParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
|
||||
line, _ := block.PeekLine()
|
||||
if !bytes.HasPrefix(line, prefix) {
|
||||
return nil
|
||||
}
|
||||
end := bytes.Index(line, endDelim)
|
||||
if end == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
block.Advance(end + len(endDelim) + 1) // +1 for the newline
|
||||
|
||||
if line[end-1] == '/' {
|
||||
return &HugoContext{Closing: true}
|
||||
}
|
||||
|
||||
attrBytes := line[len(prefix)+1 : end]
|
||||
h := &HugoContext{}
|
||||
h.parseAttrs(attrBytes)
|
||||
return h
|
||||
}
|
||||
|
||||
func (a *hugoContextParser) Trigger() []byte {
|
||||
return []byte{'{'}
|
||||
}
|
||||
|
||||
type hugoContextRenderer struct{}
|
||||
|
||||
func (r *hugoContextRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||
reg.Register(kindHugoContext, r.handleHugoContext)
|
||||
}
|
||||
|
||||
func (r *hugoContextRenderer) handleHugoContext(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||
if !entering {
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
hctx := node.(*HugoContext)
|
||||
ctx, ok := w.(*render.Context)
|
||||
if !ok {
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
if hctx.Closing {
|
||||
_ = ctx.PopPid()
|
||||
} else {
|
||||
ctx.PushPid(hctx.Pid)
|
||||
}
|
||||
return ast.WalkContinue, nil
|
||||
}
|
||||
|
||||
type hugoContextExtension struct{}
|
||||
|
||||
func (a *hugoContextExtension) Extend(m goldmark.Markdown) {
|
||||
m.Parser().AddOptions(
|
||||
parser.WithInlineParsers(
|
||||
util.Prioritized(&hugoContextParser{}, 50),
|
||||
),
|
||||
)
|
||||
|
||||
m.Renderer().AddOptions(
|
||||
renderer.WithNodeRenderers(
|
||||
util.Prioritized(&hugoContextRenderer{}, 50),
|
||||
),
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user