mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-29 22:29:56 +02:00
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
This commit is contained in:
@@ -17,12 +17,12 @@ package goldmark
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
|
||||
"github.com/gohugoio/hugo/markup/goldmark/codeblocks"
|
||||
"github.com/gohugoio/hugo/markup/goldmark/internal/extensions/attributes"
|
||||
"github.com/yuin/goldmark/ast"
|
||||
"github.com/gohugoio/hugo/markup/goldmark/internal/render"
|
||||
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
|
||||
@@ -32,16 +32,13 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/markup/converter"
|
||||
"github.com/gohugoio/hugo/markup/highlight"
|
||||
"github.com/gohugoio/hugo/markup/tableofcontents"
|
||||
"github.com/yuin/goldmark"
|
||||
hl "github.com/yuin/goldmark-highlighting"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
"github.com/yuin/goldmark/parser"
|
||||
"github.com/yuin/goldmark/renderer"
|
||||
"github.com/yuin/goldmark/renderer/html"
|
||||
"github.com/yuin/goldmark/text"
|
||||
"github.com/yuin/goldmark/util"
|
||||
)
|
||||
|
||||
// Provider is the package entry point.
|
||||
@@ -104,7 +101,7 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
|
||||
)
|
||||
|
||||
if mcfg.Highlight.CodeFences {
|
||||
extensions = append(extensions, newHighlighting(mcfg.Highlight))
|
||||
extensions = append(extensions, codeblocks.New())
|
||||
}
|
||||
|
||||
if cfg.Extensions.Table {
|
||||
@@ -178,65 +175,6 @@ func (c converterResult) GetIdentities() identity.Identities {
|
||||
return c.ids
|
||||
}
|
||||
|
||||
type bufWriter struct {
|
||||
*bytes.Buffer
|
||||
}
|
||||
|
||||
const maxInt = 1<<(bits.UintSize-1) - 1
|
||||
|
||||
func (b *bufWriter) Available() int {
|
||||
return maxInt
|
||||
}
|
||||
|
||||
func (b *bufWriter) Buffered() int {
|
||||
return b.Len()
|
||||
}
|
||||
|
||||
func (b *bufWriter) Flush() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type renderContext struct {
|
||||
*bufWriter
|
||||
positions []int
|
||||
renderContextData
|
||||
}
|
||||
|
||||
func (ctx *renderContext) pushPos(n int) {
|
||||
ctx.positions = append(ctx.positions, n)
|
||||
}
|
||||
|
||||
func (ctx *renderContext) popPos() int {
|
||||
i := len(ctx.positions) - 1
|
||||
p := ctx.positions[i]
|
||||
ctx.positions = ctx.positions[:i]
|
||||
return p
|
||||
}
|
||||
|
||||
type renderContextData interface {
|
||||
RenderContext() converter.RenderContext
|
||||
DocumentContext() converter.DocumentContext
|
||||
AddIdentity(id identity.Provider)
|
||||
}
|
||||
|
||||
type renderContextDataHolder struct {
|
||||
rctx converter.RenderContext
|
||||
dctx converter.DocumentContext
|
||||
ids identity.Manager
|
||||
}
|
||||
|
||||
func (ctx *renderContextDataHolder) RenderContext() converter.RenderContext {
|
||||
return ctx.rctx
|
||||
}
|
||||
|
||||
func (ctx *renderContextDataHolder) DocumentContext() converter.DocumentContext {
|
||||
return ctx.dctx
|
||||
}
|
||||
|
||||
func (ctx *renderContextDataHolder) AddIdentity(id identity.Provider) {
|
||||
ctx.ids.Add(id)
|
||||
}
|
||||
|
||||
var converterIdentity = identity.KeyValueIdentity{Key: "goldmark", Value: "converter"}
|
||||
|
||||
func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result converter.Result, err error) {
|
||||
@@ -251,7 +189,7 @@ func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result convert
|
||||
}
|
||||
}()
|
||||
|
||||
buf := &bufWriter{Buffer: &bytes.Buffer{}}
|
||||
buf := &render.BufWriter{Buffer: &bytes.Buffer{}}
|
||||
result = buf
|
||||
pctx := c.newParserContext(ctx)
|
||||
reader := text.NewReader(ctx.Src)
|
||||
@@ -261,15 +199,15 @@ func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result convert
|
||||
parser.WithContext(pctx),
|
||||
)
|
||||
|
||||
rcx := &renderContextDataHolder{
|
||||
rctx: ctx,
|
||||
dctx: c.ctx,
|
||||
ids: identity.NewManager(converterIdentity),
|
||||
rcx := &render.RenderContextDataHolder{
|
||||
Rctx: ctx,
|
||||
Dctx: c.ctx,
|
||||
IDs: identity.NewManager(converterIdentity),
|
||||
}
|
||||
|
||||
w := &renderContext{
|
||||
bufWriter: buf,
|
||||
renderContextData: rcx,
|
||||
w := &render.Context{
|
||||
BufWriter: buf,
|
||||
ContextData: rcx,
|
||||
}
|
||||
|
||||
if err := c.md.Renderer().Render(w, ctx.Src, doc); err != nil {
|
||||
@@ -278,7 +216,7 @@ func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result convert
|
||||
|
||||
return converterResult{
|
||||
Result: buf,
|
||||
ids: rcx.ids.GetIdentities(),
|
||||
ids: rcx.IDs.GetIdentities(),
|
||||
toc: pctx.TableOfContents(),
|
||||
}, nil
|
||||
}
|
||||
@@ -309,63 +247,3 @@ func (p *parserContext) TableOfContents() tableofcontents.Root {
|
||||
}
|
||||
return tableofcontents.Root{}
|
||||
}
|
||||
|
||||
func newHighlighting(cfg highlight.Config) goldmark.Extender {
|
||||
return hl.NewHighlighting(
|
||||
hl.WithStyle(cfg.Style),
|
||||
hl.WithGuessLanguage(cfg.GuessSyntax),
|
||||
hl.WithCodeBlockOptions(highlight.GetCodeBlockOptions()),
|
||||
hl.WithFormatOptions(
|
||||
cfg.ToHTMLOptions()...,
|
||||
),
|
||||
|
||||
hl.WithWrapperRenderer(func(w util.BufWriter, ctx hl.CodeBlockContext, entering bool) {
|
||||
var language string
|
||||
if l, hasLang := ctx.Language(); hasLang {
|
||||
language = string(l)
|
||||
}
|
||||
|
||||
if ctx.Highlighted() {
|
||||
if entering {
|
||||
writeDivStart(w, ctx)
|
||||
} else {
|
||||
writeDivEnd(w)
|
||||
}
|
||||
} else {
|
||||
if entering {
|
||||
highlight.WritePreStart(w, language, "")
|
||||
} else {
|
||||
highlight.WritePreEnd(w)
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func writeDivStart(w util.BufWriter, ctx hl.CodeBlockContext) {
|
||||
w.WriteString(`<div class="highlight`)
|
||||
|
||||
var attributes []ast.Attribute
|
||||
if ctx.Attributes() != nil {
|
||||
attributes = ctx.Attributes().All()
|
||||
}
|
||||
|
||||
if attributes != nil {
|
||||
class, found := ctx.Attributes().GetString("class")
|
||||
if found {
|
||||
w.WriteString(" ")
|
||||
w.Write(util.EscapeHTML(class.([]byte)))
|
||||
|
||||
}
|
||||
_, _ = w.WriteString("\"")
|
||||
renderAttributes(w, true, attributes...)
|
||||
} else {
|
||||
_, _ = w.WriteString("\"")
|
||||
}
|
||||
|
||||
w.WriteString(">")
|
||||
}
|
||||
|
||||
func writeDivEnd(w util.BufWriter) {
|
||||
w.WriteString("</div>")
|
||||
}
|
||||
|
Reference in New Issue
Block a user