Skip to content

Instantly share code, notes, and snippets.

@cbednarski
Created July 1, 2024 05:40
Show Gist options
  • Save cbednarski/8dcc0c90890613f2b74008dc264bd14f to your computer and use it in GitHub Desktop.
Save cbednarski/8dcc0c90890613f2b74008dc264bd14f to your computer and use it in GitHub Desktop.
package turbine
import (
"bytes"
"io"
"regexp"
"strings"
"github.com/russross/blackfriday/v2"
)
var reParseHeadingID = regexp.MustCompile(`id="(.+)"`)
type AutoHeadingLinkRenderer struct {
Base blackfriday.Renderer
// LinkText will be inserted for the anchor's content. This is inserted
// verbatim so it could be a special character like # or an image tag.
LinkText string
// Outside will write the anchor outside the header tag. This can be useful
// to avoid mixing formatting.
Outside bool
// CSSClass will be added to the anchor tag (optional)
CSSClass string
}
func (r *AutoHeadingLinkRenderer) renderLink(id string) *bytes.Buffer {
s := strings.Builder{}
s.WriteString(`<a href="#`)
s.WriteString(id)
s.WriteString(`"`)
if r.CSSClass != "" {
s.WriteString(` class="`)
s.WriteString(r.CSSClass)
s.WriteString(`"`)
}
s.WriteString(">")
s.WriteString(r.LinkText)
s.WriteString("</a>")
buf := &bytes.Buffer{}
buf.WriteString(s.String())
return buf
}
func (r *AutoHeadingLinkRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
switch node.Type {
case blackfriday.Heading:
heading := &bytes.Buffer{}
// Render the node to a buffer so we can tamper with it
status := r.Base.RenderNode(heading, node, entering)
// entering is the opening tag, such as <h3 ... >
if entering {
id := reParseHeadingID.FindStringSubmatch(heading.String())
if len(id) == 2 {
if r.Outside {
io.Copy(w, r.renderLink(id[1]))
} else {
heading.Write(r.renderLink(id[1]).Bytes())
}
}
}
if _, err := io.Copy(w, heading); err != nil {
panic(err)
}
return status
default:
return r.Base.RenderNode(w, node, entering)
}
}
func (r *AutoHeadingLinkRenderer) RenderHeader(w io.Writer, ast *blackfriday.Node) {
r.Base.RenderHeader(w, ast)
}
func (r *AutoHeadingLinkRenderer) RenderFooter(w io.Writer, ast *blackfriday.Node) {
r.Base.RenderFooter(w, ast)
}
func Render(data []byte) []byte {
extensions := blackfriday.FencedCode | blackfriday.Strikethrough | blackfriday.AutoHeadingIDs
var renderer blackfriday.Renderer = blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{})
renderer = &AutoHeadingLinkRenderer{
Base: renderer,
LinkText: "#",
Outside: true,
CSSClass: "heading-link",
}
return blackfriday.Run(data, blackfriday.WithExtensions(extensions), blackfriday.WithRenderer(renderer))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment