mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-24 15:28:54 +00:00
+33
-10
@@ -457,6 +457,17 @@ type contentTableOfContents struct {
|
|||||||
contentPlaceholders map[string]shortcodeRenderer
|
contentPlaceholders map[string]shortcodeRenderer
|
||||||
|
|
||||||
contentToRender []byte
|
contentToRender []byte
|
||||||
|
|
||||||
|
sourceInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type sourceInfo struct {
|
||||||
|
// Optional override for the filename used in position reporting.
|
||||||
|
filename string
|
||||||
|
// The original source bytes.
|
||||||
|
source []byte
|
||||||
|
// Maps positions in the content sent to Goldmark back to the original source.
|
||||||
|
sourceMap []sourceMapEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
type contentSummary struct {
|
type contentSummary struct {
|
||||||
@@ -515,7 +526,7 @@ func (c *cachedContentScope) contentRendered(ctx context.Context) (contentSummar
|
|||||||
|
|
||||||
if ct.astDoc != nil {
|
if ct.astDoc != nil {
|
||||||
// The content is parsed, but not rendered.
|
// The content is parsed, but not rendered.
|
||||||
r, ok, err := po.contentRenderer.RenderContent(ctx, ct.contentToRender, ct.astDoc)
|
r, ok, err := po.contentRenderer.RenderContent(ctx, ct.contentToRender, ct.sourceInfo, ct.astDoc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -638,12 +649,13 @@ func (c *cachedContentScope) contentToC(ctx context.Context) (contentTableOfCont
|
|||||||
versionv := c.version(cp)
|
versionv := c.version(cp)
|
||||||
|
|
||||||
v, err := c.pm.contentTableOfContents.GetOrCreate(key, func(string) (*resources.StaleValue[contentTableOfContents], error) {
|
v, err := c.pm.contentTableOfContents.GetOrCreate(key, func(string) (*resources.StaleValue[contentTableOfContents], error) {
|
||||||
source, err := c.pi.contentSource(c)
|
var err error
|
||||||
|
var ct contentTableOfContents
|
||||||
|
ct.source, err = c.pi.contentSource(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var ct contentTableOfContents
|
|
||||||
if err := cp.initRenderHooks(); err != nil {
|
if err := cp.initRenderHooks(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -678,7 +690,7 @@ func (c *cachedContentScope) contentToC(ctx context.Context) (contentTableOfCont
|
|||||||
ctx = setGetContentCallbackInContext.Set(ctx, ctxCallback)
|
ctx = setGetContentCallbackInContext.Set(ctx, ctxCallback)
|
||||||
|
|
||||||
var hasVariants bool
|
var hasVariants bool
|
||||||
ct.contentToRender, cp.sourceMap, hasVariants, err = c.pi.contentToRender(ctx, source, ct.contentPlaceholders)
|
ct.contentToRender, ct.sourceMap, hasVariants, err = c.pi.contentToRender(ctx, ct.source, ct.contentPlaceholders)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -926,9 +938,15 @@ func (c *cachedContentScope) RenderString(ctx context.Context, args ...any) (tem
|
|||||||
}
|
}
|
||||||
|
|
||||||
if pageparser.HasShortcode(contentToRender) {
|
if pageparser.HasShortcode(contentToRender) {
|
||||||
contentToRenderb := []byte(contentToRender)
|
ct := contentTableOfContents{
|
||||||
|
sourceInfo: sourceInfo{
|
||||||
|
filename: pco.po.p.pathOrTitle() + " (rendered from string)",
|
||||||
|
source: []byte(contentToRender),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ct.contentToRender = ct.source
|
||||||
// String contains a shortcode.
|
// String contains a shortcode.
|
||||||
parseInfo.itemsStep1, err = pageparser.ParseBytes(contentToRenderb, pageparser.Config{
|
parseInfo.itemsStep1, err = pageparser.ParseBytes(ct.source, pageparser.Config{
|
||||||
NoFrontMatter: true,
|
NoFrontMatter: true,
|
||||||
NoSummaryDivider: true,
|
NoSummaryDivider: true,
|
||||||
})
|
})
|
||||||
@@ -937,7 +955,7 @@ func (c *cachedContentScope) RenderString(ctx context.Context, args ...any) (tem
|
|||||||
}
|
}
|
||||||
|
|
||||||
parseInfo.shortcodeParseInfo = newShortcodeHandler(pco.po.p.pathOrTitle(), pco.po.p.s.h.Deps)
|
parseInfo.shortcodeParseInfo = newShortcodeHandler(pco.po.p.pathOrTitle(), pco.po.p.s.h.Deps)
|
||||||
if err := parseInfo.parseSource(contentToRenderb, true); err != nil {
|
if err := parseInfo.parseSource(ct.source, true); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -946,14 +964,15 @@ func (c *cachedContentScope) RenderString(ctx context.Context, args ...any) (tem
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
contentToRender, _, hasVariants, err := parseInfo.contentToRender(ctx, contentToRenderb, placeholders)
|
var hasVariants bool
|
||||||
|
ct.contentToRender, ct.sourceMap, hasVariants, err = parseInfo.contentToRender(ctx, ct.source, placeholders)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if hasVariants {
|
if hasVariants {
|
||||||
pco.po.p.incrPageOutputTemplateVariation()
|
pco.po.p.incrPageOutputTemplateVariation()
|
||||||
}
|
}
|
||||||
b, err := pco.renderContentWithConverter(ctx, conv, contentToRender, false)
|
b, err := pco.renderContentWithConverter(ctx, conv, ct.contentToRender, ct.sourceInfo, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", pco.po.p.wrapError(err)
|
return "", pco.po.p.wrapError(err)
|
||||||
}
|
}
|
||||||
@@ -998,7 +1017,11 @@ func (c *cachedContentScope) RenderString(ctx context.Context, args ...any) (tem
|
|||||||
pco.po.p.m.content.hasShortcode.Store(&combined)
|
pco.po.p.m.content.hasShortcode.Store(&combined)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
c, err := pco.renderContentWithConverter(ctx, conv, []byte(contentToRender), false)
|
si := sourceInfo{
|
||||||
|
filename: pco.po.p.pathOrTitle() + " (rendered from string)",
|
||||||
|
source: []byte(contentToRender),
|
||||||
|
}
|
||||||
|
c, err := pco.renderContentWithConverter(ctx, conv, si.source, si, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", pco.po.p.wrapError(err)
|
return "", pco.po.p.wrapError(err)
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-16
@@ -89,9 +89,6 @@ type pageContentOutput struct {
|
|||||||
|
|
||||||
// Renders Markdown hooks.
|
// Renders Markdown hooks.
|
||||||
renderHooks *renderHooks
|
renderHooks *renderHooks
|
||||||
|
|
||||||
// Maps positions in the content sent to Goldmark back to the original source.
|
|
||||||
sourceMap []sourceMapEntry
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pco *pageContentOutput) trackDependency(idp identity.IdentityProvider) {
|
func (pco *pageContentOutput) trackDependency(idp identity.IdentityProvider) {
|
||||||
@@ -246,22 +243,25 @@ func (pco *pageContentOutput) initRenderHooks() error {
|
|||||||
renderCache := make(map[cacheKey]any)
|
renderCache := make(map[cacheKey]any)
|
||||||
var renderCacheMu sync.Mutex
|
var renderCacheMu sync.Mutex
|
||||||
|
|
||||||
resolvePosition := func(_ any, _ []byte, pos int) text.Position {
|
resolvePosition := func(renderContext any, pos int) text.Position {
|
||||||
if pos == -1 {
|
rc, ok := renderContext.(converter.RenderContext)
|
||||||
|
var si sourceInfo
|
||||||
|
if ok {
|
||||||
|
si, ok = rc.SourceInfo.(sourceInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pos == -1 || !ok {
|
||||||
return text.Position{
|
return text.Position{
|
||||||
Filename: pco.po.p.pathOrTitle(),
|
Filename: pco.po.p.pathOrTitle(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sourceOrig := pco.po.p.m.content.mustSource()
|
offset := resolveSourceOffset(si.sourceMap, pos)
|
||||||
var offset int
|
filename := si.filename
|
||||||
|
if filename == "" {
|
||||||
if sm := pco.sourceMap; len(sm) > 0 {
|
filename = pco.po.p.pathOrTitle()
|
||||||
offset = resolveSourceOffset(sm, pos)
|
|
||||||
} else {
|
|
||||||
offset = pos + pco.po.p.m.content.pi.posMainContent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pco.po.p.posFromInput(sourceOrig, offset)
|
return posFromInput(filename, si.source, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
pco.renderHooks.getRenderer = func(tp hooks.RendererType, id any) any {
|
pco.renderHooks.getRenderer = func(tp hooks.RendererType, id any) any {
|
||||||
@@ -411,7 +411,10 @@ func (cp *pageContentOutput) ParseAndRenderContent(ctx context.Context, content
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return cp.renderContentWithConverter(ctx, c, content, renderTOC)
|
si := sourceInfo{
|
||||||
|
source: content,
|
||||||
|
}
|
||||||
|
return cp.renderContentWithConverter(ctx, c, content, si, renderTOC)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pco *pageContentOutput) ParseContent(ctx context.Context, content []byte) (converter.ResultParse, bool, error) {
|
func (pco *pageContentOutput) ParseContent(ctx context.Context, content []byte) (converter.ResultParse, bool, error) {
|
||||||
@@ -433,7 +436,7 @@ func (pco *pageContentOutput) ParseContent(ctx context.Context, content []byte)
|
|||||||
return r, ok, err
|
return r, ok, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pco *pageContentOutput) RenderContent(ctx context.Context, content []byte, doc any) (converter.ResultRender, bool, error) {
|
func (pco *pageContentOutput) RenderContent(ctx context.Context, content []byte, sourceInfo, doc any) (converter.ResultRender, bool, error) {
|
||||||
c, err := pco.getContentConverter()
|
c, err := pco.getContentConverter()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
@@ -445,6 +448,7 @@ func (pco *pageContentOutput) RenderContent(ctx context.Context, content []byte,
|
|||||||
rctx := converter.RenderContext{
|
rctx := converter.RenderContext{
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
Src: content,
|
Src: content,
|
||||||
|
SourceInfo: sourceInfo,
|
||||||
RenderTOC: true,
|
RenderTOC: true,
|
||||||
GetRenderer: pco.renderHooks.getRenderer,
|
GetRenderer: pco.renderHooks.getRenderer,
|
||||||
}
|
}
|
||||||
@@ -452,11 +456,12 @@ func (pco *pageContentOutput) RenderContent(ctx context.Context, content []byte,
|
|||||||
return r, ok, err
|
return r, ok, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pco *pageContentOutput) renderContentWithConverter(ctx context.Context, c converter.Converter, content []byte, renderTOC bool) (converter.ResultRender, error) {
|
func (pco *pageContentOutput) renderContentWithConverter(ctx context.Context, c converter.Converter, content []byte, sourceInfo any, renderTOC bool) (converter.ResultRender, error) {
|
||||||
r, err := c.Convert(
|
r, err := c.Convert(
|
||||||
converter.RenderContext{
|
converter.RenderContext{
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
Src: content,
|
Src: content,
|
||||||
|
SourceInfo: sourceInfo,
|
||||||
RenderTOC: renderTOC,
|
RenderTOC: renderTOC,
|
||||||
GetRenderer: pco.renderHooks.getRenderer,
|
GetRenderer: pco.renderHooks.getRenderer,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -553,7 +553,7 @@ func (s *shortcodeParseInfo) prepareShortcodesForPage(po *pageOutput, isRenderSt
|
|||||||
}
|
}
|
||||||
|
|
||||||
func posFromInput(filename string, input []byte, offset int) text.Position {
|
func posFromInput(filename string, input []byte, offset int) text.Position {
|
||||||
if offset < 0 {
|
if offset < 0 || offset > len(input) {
|
||||||
return text.Position{
|
return text.Position{
|
||||||
Filename: filename,
|
Filename: filename,
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -1682,7 +1682,7 @@ var infoOnMissingLayout = map[string]bool{
|
|||||||
type hookRendererTemplate struct {
|
type hookRendererTemplate struct {
|
||||||
templateHandler *tplimpl.TemplateStore
|
templateHandler *tplimpl.TemplateStore
|
||||||
templ *tplimpl.TemplInfo
|
templ *tplimpl.TemplInfo
|
||||||
resolvePosition func(ctx any, srcRender []byte, pos int) text.Position
|
resolvePosition func(renderContext any, pos int) text.Position
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hr hookRendererTemplate) RenderLink(cctx context.Context, w io.Writer, ctx hooks.LinkContext) error {
|
func (hr hookRendererTemplate) RenderLink(cctx context.Context, w io.Writer, ctx hooks.LinkContext) error {
|
||||||
@@ -1709,8 +1709,8 @@ func (hr hookRendererTemplate) RenderTable(cctx context.Context, w hugio.FlexiWr
|
|||||||
return hr.templateHandler.ExecuteWithContext(cctx, hr.templ, w, ctx)
|
return hr.templateHandler.ExecuteWithContext(cctx, hr.templ, w, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hr hookRendererTemplate) ResolvePosition(ctx any, srcRender []byte, pos int) text.Position {
|
func (hr hookRendererTemplate) ResolvePosition(renderContext any, pos int) text.Position {
|
||||||
return hr.resolvePosition(ctx, srcRender, pos)
|
return hr.resolvePosition(renderContext, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hr hookRendererTemplate) IsDefaultCodeBlockRenderer() bool {
|
func (hr hookRendererTemplate) IsDefaultCodeBlockRenderer() bool {
|
||||||
|
|||||||
@@ -150,6 +150,9 @@ type RenderContext struct {
|
|||||||
// Src is the content to render.
|
// Src is the content to render.
|
||||||
Src []byte
|
Src []byte
|
||||||
|
|
||||||
|
// SourceInfo holds optional information about the source of the content to render.
|
||||||
|
SourceInfo any
|
||||||
|
|
||||||
// Whether to render TableOfContents.
|
// Whether to render TableOfContents.
|
||||||
RenderTOC bool
|
RenderTOC bool
|
||||||
|
|
||||||
|
|||||||
@@ -200,11 +200,8 @@ type HeadingRenderer interface {
|
|||||||
// This may be both slow and approximate, so should only be
|
// This may be both slow and approximate, so should only be
|
||||||
// used for error logging.
|
// used for error logging.
|
||||||
type ElementPositionResolver interface {
|
type ElementPositionResolver interface {
|
||||||
// ResolvePosition returns the position of the element in the original source document.
|
// ResolvePosition returns the position of the element in the original source document, -1 if it's not defined.
|
||||||
// ctx is the context passed to the render hook, and srcPos is the zero-based byte offset of the element
|
ResolvePosition(renderContext any, srcPos int) text.Position
|
||||||
// in srcRender (the rendered source buffer),
|
|
||||||
// -1 if it's not defined.
|
|
||||||
ResolvePosition(ctx any, srcRender []byte, srcPos int) text.Position
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RendererType int
|
type RendererType int
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ func NewBaseContext(rctx *Context, renderer any, n ast.Node, src []byte, ordinal
|
|||||||
|
|
||||||
b.createPos = func() htext.Position {
|
b.createPos = func() htext.Position {
|
||||||
if resolver, ok := renderer.(hooks.ElementPositionResolver); ok {
|
if resolver, ok := renderer.(hooks.ElementPositionResolver); ok {
|
||||||
return resolver.ResolvePosition(b, src, n.Pos())
|
return resolver.ResolvePosition(rctx.RenderContext(), n.Pos())
|
||||||
}
|
}
|
||||||
|
|
||||||
return htext.Position{
|
return htext.Position{
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ type ContentRenderer interface {
|
|||||||
// For internal use only.
|
// For internal use only.
|
||||||
ParseContent(ctx context.Context, content []byte) (converter.ResultParse, bool, error)
|
ParseContent(ctx context.Context, content []byte) (converter.ResultParse, bool, error)
|
||||||
// For internal use only.
|
// For internal use only.
|
||||||
RenderContent(ctx context.Context, content []byte, doc any) (converter.ResultRender, bool, error)
|
RenderContent(ctx context.Context, content []byte, sourceInfo, doc any) (converter.ResultRender, bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileProvider provides the source file.
|
// FileProvider provides the source file.
|
||||||
|
|||||||
@@ -133,6 +133,6 @@ func (lcp *LazyContentProvider) ParseContent(ctx context.Context, content []byte
|
|||||||
return lcp.init.Value(ctx).ParseContent(ctx, content)
|
return lcp.init.Value(ctx).ParseContent(ctx, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lcp *LazyContentProvider) RenderContent(ctx context.Context, content []byte, doc any) (converter.ResultRender, bool, error) {
|
func (lcp *LazyContentProvider) RenderContent(ctx context.Context, content []byte, sourceInfo, doc any) (converter.ResultRender, bool, error) {
|
||||||
return lcp.init.Value(ctx).RenderContent(ctx, content, doc)
|
return lcp.init.Value(ctx).RenderContent(ctx, content, sourceInfo, doc)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -522,7 +522,7 @@ func (r *nopContentRenderer) ParseContent(ctx context.Context, content []byte) (
|
|||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *nopContentRenderer) RenderContent(ctx context.Context, content []byte, doc any) (converter.ResultRender, bool, error) {
|
func (r *nopContentRenderer) RenderContent(ctx context.Context, content []byte, sourceInfo, doc any) (converter.ResultRender, bool, error) {
|
||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -466,3 +466,44 @@ This is an \(inline\) passthrough element with opening and closing inline delimi
|
|||||||
"passthrough.passthroughContext|p1.md|46:12|2|",
|
"passthrough.passthroughContext|p1.md|46:12|2|",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderHooksPositionRenderString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
files := `
|
||||||
|
-- hugo.toml --
|
||||||
|
-- assets/a.txt --
|
||||||
|
|
||||||
|
## Heading
|
||||||
|
|
||||||
|
[link](b.txt)
|
||||||
|
|
||||||
|
-- assets/b.txt --
|
||||||
|
{{% myshortcode %}}
|
||||||
|
{{< myshortcode >}}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[link](a.txt)
|
||||||
|
-- layouts/shortcodes/myshortcode.html --
|
||||||
|
My Shortcode.
|
||||||
|
# This is a heading in the shortcode.
|
||||||
|
Some text.
|
||||||
|
-- layouts/_markup/render-link.html --
|
||||||
|
{{ $pos := .Position }}
|
||||||
|
{{ printf "%T" . }}|{{ path.Join $pos.Filename }}|{{ printf "%d:%d" $pos.LineNumber $pos.ColumnNumber }}|{{ $.Ordinal }}|
|
||||||
|
-- layouts/all.html --
|
||||||
|
{{ $a := resources.Get "a.txt" }}
|
||||||
|
a: {{ .RenderString $a.Content }}
|
||||||
|
b: {{ .RenderString (resources.Get "b.txt").Content }}
|
||||||
|
-- content/p1.md --
|
||||||
|
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
b := hugolib.Test(t, files)
|
||||||
|
b.AssertFileContent("public/p1/index.html",
|
||||||
|
"/content/p1.md (rendered from string)|4:1|0|",
|
||||||
|
"/content/p1.md (rendered from string)|6:3|0|",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user