mirror of
https://github.com/gohugoio/hugo.git
synced 2024-12-01 15:39:12 -05:00
df11327ba9
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
165 lines
3.9 KiB
Go
165 lines
3.9 KiB
Go
// 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),
|
|
),
|
|
)
|
|
}
|