diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2022-02-17 13:04:00 +0100 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2022-02-24 18:59:50 +0100 |
commit | 08fdca9d9365eaf1e496a12e2af5e18617bd0e66 (patch) | |
tree | 6c6942d1b74a4160d93a997860bafd52b92025f5 /hugolib/page__per_output.go | |
parent | 2c20f5bc00b604e72b3b7e401fbdbf9447fe3470 (diff) |
Add Markdown diagrams and render hooks for code blocks
You can now create custom hook templates for code blocks, either one for all (`render-codeblock.html`) or for a given code language (e.g. `render-codeblock-go.html`).
We also used this new hook to add support for diagrams in Hugo:
* Goat (Go ASCII Tool) is built-in and enabled by default; just create a fenced code block with the language `goat` and start draw your Ascii diagrams.
* Another popular alternative for diagrams in Markdown, Mermaid (supported by GitHub), can also be implemented with a simple template. See the Hugo documentation for more information.
Updates #7765
Closes #9538
Fixes #9553
Fixes #8520
Fixes #6702
Fixes #9558
Diffstat (limited to 'hugolib/page__per_output.go')
-rw-r--r-- | hugolib/page__per_output.go | 173 |
1 files changed, 99 insertions, 74 deletions
diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go index bd4e35a5b..29beb672e 100644 --- a/hugolib/page__per_output.go +++ b/hugolib/page__per_output.go @@ -32,6 +32,7 @@ import ( "github.com/gohugoio/hugo/markup/converter" + "github.com/alecthomas/chroma/lexers" "github.com/gohugoio/hugo/lazy" bp "github.com/gohugoio/hugo/bufferpool" @@ -109,16 +110,8 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err return err } - enableReuse := !(hasShortcodeVariants || cp.renderHooksHaveVariants) - - if enableReuse { - // Reuse this for the other output formats. - // We may improve on this, but we really want to avoid re-rendering the content - // to all output formats. - // The current rule is that if you need output format-aware shortcodes or - // content rendering hooks, create a output format-specific template, e.g. - // myshortcode.amp.html. - cp.enableReuse() + if hasShortcodeVariants { + p.pageOutputTemplateVariationsState.Store(2) } cp.workContent = p.contentToRender(cp.contentPlaceholders) @@ -199,19 +192,10 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err return nil } - // Recursive loops can only happen in content files with template code (shortcodes etc.) - // Avoid creating new goroutines if we don't have to. - needTimeout := p.shortcodeState.hasShortcodes() || cp.renderHooks != nil - - if needTimeout { - cp.initMain = parent.BranchWithTimeout(p.s.siteCfg.timeout, func(ctx context.Context) (interface{}, error) { - return nil, initContent() - }) - } else { - cp.initMain = parent.Branch(func() (interface{}, error) { - return nil, initContent() - }) - } + // There may be recursive loops in shortcodes and render hooks. + cp.initMain = parent.BranchWithTimeout(p.s.siteCfg.timeout, func(ctx context.Context) (interface{}, error) { + return nil, initContent() + }) cp.initPlain = cp.initMain.Branch(func() (interface{}, error) { cp.plain = helpers.StripHTML(string(cp.content)) @@ -229,18 +213,14 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err } type renderHooks struct { - hooks hooks.Renderers - init sync.Once + getRenderer hooks.GetRendererFunc + init sync.Once } // pageContentOutput represents the Page content for a given output format. type pageContentOutput struct { f output.Format - // If we can reuse this for other output formats. - reuse bool - reuseInit sync.Once - p *pageState // Lazy load dependencies @@ -250,13 +230,9 @@ type pageContentOutput struct { placeholdersEnabled bool placeholdersEnabledInit sync.Once + // Renders Markdown hooks. renderHooks *renderHooks - // Set if there are more than one output format variant - renderHooksHaveVariants bool // TODO(bep) reimplement this in another way, consolidate with shortcodes - - // Content state - workContent []byte dependencyTracker identity.Manager // Set in server mode. @@ -440,55 +416,107 @@ func (p *pageContentOutput) initRenderHooks() error { return nil } - var initErr error - p.renderHooks.init.Do(func() { - ps := p.p - - c := ps.getContentConverter() - if c == nil || !c.Supports(converter.FeatureRenderHooks) { - return + if p.p.pageOutputTemplateVariationsState.Load() == 0 { + p.p.pageOutputTemplateVariationsState.Store(1) } - h, err := ps.createRenderHooks(p.f) - if err != nil { - initErr = err - return + type cacheKey struct { + tp hooks.RendererType + id interface{} + f output.Format } - p.renderHooks.hooks = h - - if !p.renderHooksHaveVariants || h.IsZero() { - // Check if there is a different render hooks template - // for any of the other page output formats. - // If not, we can reuse this. - for _, po := range ps.pageOutputs { - if po.f.Name != p.f.Name { - h2, err := ps.createRenderHooks(po.f) - if err != nil { - initErr = err - return - } - if h2.IsZero() { - continue - } + renderCache := make(map[cacheKey]interface{}) + var renderCacheMu sync.Mutex + + p.renderHooks.getRenderer = func(tp hooks.RendererType, id interface{}) interface{} { + renderCacheMu.Lock() + defer renderCacheMu.Unlock() + + key := cacheKey{tp: tp, id: id, f: p.f} + if r, ok := renderCache[key]; ok { + return r + } - if p.renderHooks.hooks.IsZero() { - p.renderHooks.hooks = h2 + layoutDescriptor := p.p.getLayoutDescriptor() + layoutDescriptor.RenderingHook = true + layoutDescriptor.LayoutOverride = false + layoutDescriptor.Layout = "" + + switch tp { + case hooks.LinkRendererType: + layoutDescriptor.Kind = "render-link" + case hooks.ImageRendererType: + layoutDescriptor.Kind = "render-image" + case hooks.HeadingRendererType: + layoutDescriptor.Kind = "render-heading" + case hooks.CodeBlockRendererType: + layoutDescriptor.Kind = "render-codeblock" + if id != nil { + lang := id.(string) + lexer := lexers.Get(lang) + if lexer != nil { + layoutDescriptor.KindVariants = strings.Join(lexer.Config().Aliases, ",") + } else { + layoutDescriptor.KindVariants = lang } + } + } - p.renderHooksHaveVariants = !h2.Eq(p.renderHooks.hooks) + getHookTemplate := func(f output.Format) (tpl.Template, bool) { + templ, found, err := p.p.s.Tmpl().LookupLayout(layoutDescriptor, f) + if err != nil { + panic(err) + } + return templ, found + } + + templ, found1 := getHookTemplate(p.f) - if p.renderHooksHaveVariants { - break + if p.p.reusePageOutputContent() { + // Check if some of the other output formats would give a different template. + for _, f := range p.p.s.renderFormats { + if f.Name == p.f.Name { + continue + } + templ2, found2 := getHookTemplate(f) + if found2 { + if !found1 { + templ = templ2 + found1 = true + break + } + + if templ != templ2 { + p.p.pageOutputTemplateVariationsState.Store(2) + break + } } + } + } + if !found1 { + if tp == hooks.CodeBlockRendererType { + // No user provided tempplate for code blocks, so we use the native Go code version -- which is also faster. + r := p.p.s.ContentSpec.Converters.GetHighlighter() + renderCache[key] = r + return r } + return nil } + + r := hookRendererTemplate{ + templateHandler: p.p.s.Tmpl(), + SearchProvider: templ.(identity.SearchProvider), + templ: templ, + } + renderCache[key] = r + return r } }) - return initErr + return nil } func (p *pageContentOutput) setAutoSummary() error { @@ -512,6 +540,9 @@ func (p *pageContentOutput) setAutoSummary() error { } func (cp *pageContentOutput) renderContent(content []byte, renderTOC bool) (converter.Result, error) { + if err := cp.initRenderHooks(); err != nil { + return nil, err + } c := cp.p.getContentConverter() return cp.renderContentWithConverter(c, content, renderTOC) } @@ -521,7 +552,7 @@ func (cp *pageContentOutput) renderContentWithConverter(c converter.Converter, c converter.RenderContext{ Src: content, RenderTOC: renderTOC, - RenderHooks: cp.renderHooks.hooks, + GetRenderer: cp.renderHooks.getRenderer, }) if err == nil { @@ -570,12 +601,6 @@ func (p *pageContentOutput) enablePlaceholders() { }) } -func (p *pageContentOutput) enableReuse() { - p.reuseInit.Do(func() { - p.reuse = true - }) -} - // these will be shifted out when rendering a given output format. type pagePerOutputProviders interface { targetPather |