diff options
author | Eli W. Hunter <elihunter173@gmail.com> | 2020-03-14 10:43:10 -0400 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2020-05-15 21:12:43 +0200 |
commit | 423b8f2fb834139cf31514b14b1c1bf28e43b384 (patch) | |
tree | dcd2d3c2dfcc5e2960056462c2d048906b1acc55 /markup | |
parent | 991934497e88dcd4134a369a213bb5072c51c139 (diff) |
Add render template hooks for headings
This commit also
* Renames previous types to be non-specific. (e.g. hookedRenderer rather
than linkRenderer)
Resolves #6713
Diffstat (limited to 'markup')
-rw-r--r-- | markup/converter/converter.go | 2 | ||||
-rw-r--r-- | markup/converter/hooks/hooks.go | 47 | ||||
-rw-r--r-- | markup/goldmark/render_hooks.go (renamed from markup/goldmark/render_link.go) | 156 |
3 files changed, 166 insertions, 39 deletions
diff --git a/markup/converter/converter.go b/markup/converter/converter.go index 353775826..df4bad95c 100644 --- a/markup/converter/converter.go +++ b/markup/converter/converter.go @@ -126,7 +126,7 @@ type DocumentContext struct { type RenderContext struct { Src []byte RenderTOC bool - RenderHooks *hooks.Render + RenderHooks *hooks.Renderers } var ( diff --git a/markup/converter/hooks/hooks.go b/markup/converter/hooks/hooks.go index 5dfb09e2d..ab26a6f1a 100644 --- a/markup/converter/hooks/hooks.go +++ b/markup/converter/hooks/hooks.go @@ -27,13 +27,41 @@ type LinkContext interface { PlainText() string } -type Render struct { - LinkRenderer LinkRenderer - ImageRenderer LinkRenderer +type LinkRenderer interface { + RenderLink(w io.Writer, ctx LinkContext) error + identity.Provider +} + +// HeadingContext contains accessors to all attributes that a HeadingRenderer +// can use to render a heading. +type HeadingContext interface { + // Page is the page containing the heading. + Page() interface{} + // Level is the level of the header (i.e. 1 for top-level, 2 for sub-level, etc.). + Level() int + // Anchor is the HTML id assigned to the heading. + Anchor() string + // Text is the rendered (HTML) heading text, excluding the heading marker. + Text() string + // PlainText is the unrendered version of Text. + PlainText() string +} + +// HeadingRenderer describes a uniquely identifiable rendering hook. +type HeadingRenderer interface { + // Render writes the renderered content to w using the data in w. + RenderHeading(w io.Writer, ctx HeadingContext) error + identity.Provider } -func (r *Render) Eq(other interface{}) bool { - ro, ok := other.(*Render) +type Renderers struct { + LinkRenderer LinkRenderer + ImageRenderer LinkRenderer + HeadingRenderer HeadingRenderer +} + +func (r *Renderers) Eq(other interface{}) bool { + ro, ok := other.(*Renderers) if !ok { return false } @@ -49,10 +77,9 @@ func (r *Render) Eq(other interface{}) bool { return false } - return true -} + if r.HeadingRenderer.GetIdentity() != ro.HeadingRenderer.GetIdentity() { + return false + } -type LinkRenderer interface { - Render(w io.Writer, ctx LinkContext) error - identity.Provider + return true } diff --git a/markup/goldmark/render_link.go b/markup/goldmark/render_hooks.go index c0269bedf..aaae68e7f 100644 --- a/markup/goldmark/render_link.go +++ b/markup/goldmark/render_hooks.go @@ -23,10 +23,10 @@ import ( "github.com/yuin/goldmark/util" ) -var _ renderer.SetOptioner = (*linkRenderer)(nil) +var _ renderer.SetOptioner = (*hookedRenderer)(nil) func newLinkRenderer() renderer.NodeRenderer { - r := &linkRenderer{ + r := &hookedRenderer{ Config: html.Config{ Writer: html.DefaultWriter, }, @@ -70,23 +70,64 @@ func (ctx linkContext) Title() string { return ctx.title } -type linkRenderer struct { +type headingContext struct { + page interface{} + level int + anchor string + text string + plainText string +} + +func (ctx headingContext) Page() interface{} { + return ctx.page +} + +func (ctx headingContext) Level() int { + return ctx.level +} + +func (ctx headingContext) Anchor() string { + return ctx.anchor +} + +func (ctx headingContext) Text() string { + return ctx.text +} + +func (ctx headingContext) PlainText() string { + return ctx.plainText +} + +type hookedRenderer struct { html.Config } -func (r *linkRenderer) SetOption(name renderer.OptionName, value interface{}) { +func (r *hookedRenderer) SetOption(name renderer.OptionName, value interface{}) { r.Config.SetOption(name, value) } // RegisterFuncs implements NodeRenderer.RegisterFuncs. -func (r *linkRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { +func (r *hookedRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { reg.Register(ast.KindLink, r.renderLink) reg.Register(ast.KindImage, r.renderImage) + reg.Register(ast.KindHeading, r.renderHeading) +} + +// https://github.com/yuin/goldmark/blob/b611cd333a492416b56aa8d94b04a67bf0096ab2/renderer/html/html.go#L404 +func (r *hookedRenderer) RenderAttributes(w util.BufWriter, node ast.Node) { + + for _, attr := range node.Attributes() { + _, _ = w.WriteString(" ") + _, _ = w.Write(attr.Name) + _, _ = w.WriteString(`="`) + _, _ = w.Write(util.EscapeHTML(attr.Value.([]byte))) + _ = w.WriteByte('"') + } } // Fall back to the default Goldmark render funcs. Method below borrowed from: // https://github.com/yuin/goldmark/blob/b611cd333a492416b56aa8d94b04a67bf0096ab2/renderer/html/html.go#L404 -func (r *linkRenderer) renderDefaultImage(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { +func (r *hookedRenderer) renderDefaultImage(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { if !entering { return ast.WalkContinue, nil } @@ -111,9 +152,49 @@ func (r *linkRenderer) renderDefaultImage(w util.BufWriter, source []byte, node return ast.WalkSkipChildren, nil } +func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { + n := node.(*ast.Image) + var h *hooks.Renderers + + ctx, ok := w.(*renderContext) + if ok { + h = ctx.RenderContext().RenderHooks + ok = h != nil && h.ImageRenderer != nil + } + + if !ok { + return r.renderDefaultImage(w, source, node, entering) + } + + if entering { + // Store the current pos so we can capture the rendered text. + ctx.pos = ctx.Buffer.Len() + return ast.WalkContinue, nil + } + + text := ctx.Buffer.Bytes()[ctx.pos:] + ctx.Buffer.Truncate(ctx.pos) + + err := h.ImageRenderer.RenderLink( + w, + linkContext{ + page: ctx.DocumentContext().Document, + destination: string(n.Destination), + title: string(n.Title), + text: string(text), + plainText: string(n.Text(source)), + }, + ) + + ctx.AddIdentity(h.ImageRenderer.GetIdentity()) + + return ast.WalkContinue, err + +} + // Fall back to the default Goldmark render funcs. Method below borrowed from: // https://github.com/yuin/goldmark/blob/b611cd333a492416b56aa8d94b04a67bf0096ab2/renderer/html/html.go#L404 -func (r *linkRenderer) renderDefaultLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { +func (r *hookedRenderer) renderDefaultLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { n := node.(*ast.Link) if entering { _, _ = w.WriteString("<a href=\"") @@ -133,18 +214,18 @@ func (r *linkRenderer) renderDefaultLink(w util.BufWriter, source []byte, node a return ast.WalkContinue, nil } -func (r *linkRenderer) renderImage(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { - n := node.(*ast.Image) - var h *hooks.Render +func (r *hookedRenderer) renderLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { + n := node.(*ast.Link) + var h *hooks.Renderers ctx, ok := w.(*renderContext) if ok { h = ctx.RenderContext().RenderHooks - ok = h != nil && h.ImageRenderer != nil + ok = h != nil && h.LinkRenderer != nil } if !ok { - return r.renderDefaultImage(w, source, node, entering) + return r.renderDefaultLink(w, source, node, entering) } if entering { @@ -156,7 +237,7 @@ func (r *linkRenderer) renderImage(w util.BufWriter, source []byte, node ast.Nod text := ctx.Buffer.Bytes()[ctx.pos:] ctx.Buffer.Truncate(ctx.pos) - err := h.ImageRenderer.Render( + err := h.LinkRenderer.RenderLink( w, linkContext{ page: ctx.DocumentContext().Document, @@ -167,24 +248,40 @@ func (r *linkRenderer) renderImage(w util.BufWriter, source []byte, node ast.Nod }, ) - ctx.AddIdentity(h.ImageRenderer.GetIdentity()) + ctx.AddIdentity(h.LinkRenderer.GetIdentity()) return ast.WalkContinue, err +} +func (r *hookedRenderer) renderDefaultHeading(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { + n := node.(*ast.Heading) + if entering { + _, _ = w.WriteString("<h") + _ = w.WriteByte("0123456"[n.Level]) + if n.Attributes() != nil { + r.RenderAttributes(w, node) + } + _ = w.WriteByte('>') + } else { + _, _ = w.WriteString("</h") + _ = w.WriteByte("0123456"[n.Level]) + _, _ = w.WriteString(">\n") + } + return ast.WalkContinue, nil } -func (r *linkRenderer) renderLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { - n := node.(*ast.Link) - var h *hooks.Render +func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { + n := node.(*ast.Heading) + var h *hooks.Renderers ctx, ok := w.(*renderContext) if ok { h = ctx.RenderContext().RenderHooks - ok = h != nil && h.LinkRenderer != nil + ok = h != nil && h.HeadingRenderer != nil } if !ok { - return r.renderDefaultLink(w, source, node, entering) + return r.renderDefaultHeading(w, source, node, entering) } if entering { @@ -195,22 +292,25 @@ func (r *linkRenderer) renderLink(w util.BufWriter, source []byte, node ast.Node text := ctx.Buffer.Bytes()[ctx.pos:] ctx.Buffer.Truncate(ctx.pos) + // All ast.Heading nodes are guaranteed to have an attribute called "id" + // that is an array of bytes that encode a valid string. + anchori, _ := n.AttributeString("id") + anchor := anchori.([]byte) - err := h.LinkRenderer.Render( + err := h.HeadingRenderer.RenderHeading( w, - linkContext{ - page: ctx.DocumentContext().Document, - destination: string(n.Destination), - title: string(n.Title), - text: string(text), - plainText: string(n.Text(source)), + headingContext{ + page: ctx.DocumentContext().Document, + level: n.Level, + anchor: string(anchor), + text: string(text), + plainText: string(n.Text(source)), }, ) - ctx.AddIdentity(h.LinkRenderer.GetIdentity()) + ctx.AddIdentity(h.HeadingRenderer.GetIdentity()) return ast.WalkContinue, err - } type links struct { |