strikethrough.go

  1package extension
  2
  3import (
  4	"github.com/yuin/goldmark"
  5	gast "github.com/yuin/goldmark/ast"
  6	"github.com/yuin/goldmark/extension/ast"
  7	"github.com/yuin/goldmark/parser"
  8	"github.com/yuin/goldmark/renderer"
  9	"github.com/yuin/goldmark/renderer/html"
 10	"github.com/yuin/goldmark/text"
 11	"github.com/yuin/goldmark/util"
 12)
 13
 14type strikethroughDelimiterProcessor struct {
 15}
 16
 17func (p *strikethroughDelimiterProcessor) IsDelimiter(b byte) bool {
 18	return b == '~'
 19}
 20
 21func (p *strikethroughDelimiterProcessor) CanOpenCloser(opener, closer *parser.Delimiter) bool {
 22	return opener.Char == closer.Char
 23}
 24
 25func (p *strikethroughDelimiterProcessor) OnMatch(consumes int) gast.Node {
 26	return ast.NewStrikethrough()
 27}
 28
 29var defaultStrikethroughDelimiterProcessor = &strikethroughDelimiterProcessor{}
 30
 31type strikethroughParser struct {
 32}
 33
 34var defaultStrikethroughParser = &strikethroughParser{}
 35
 36// NewStrikethroughParser return a new InlineParser that parses
 37// strikethrough expressions.
 38func NewStrikethroughParser() parser.InlineParser {
 39	return defaultStrikethroughParser
 40}
 41
 42func (s *strikethroughParser) Trigger() []byte {
 43	return []byte{'~'}
 44}
 45
 46func (s *strikethroughParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
 47	before := block.PrecendingCharacter()
 48	line, segment := block.PeekLine()
 49	node := parser.ScanDelimiter(line, before, 1, defaultStrikethroughDelimiterProcessor)
 50	if node == nil || node.OriginalLength > 2 || before == '~' {
 51		return nil
 52	}
 53
 54	node.Segment = segment.WithStop(segment.Start + node.OriginalLength)
 55	block.Advance(node.OriginalLength)
 56	pc.PushDelimiter(node)
 57	return node
 58}
 59
 60func (s *strikethroughParser) CloseBlock(parent gast.Node, pc parser.Context) {
 61	// nothing to do
 62}
 63
 64// StrikethroughHTMLRenderer is a renderer.NodeRenderer implementation that
 65// renders Strikethrough nodes.
 66type StrikethroughHTMLRenderer struct {
 67	html.Config
 68}
 69
 70// NewStrikethroughHTMLRenderer returns a new StrikethroughHTMLRenderer.
 71func NewStrikethroughHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
 72	r := &StrikethroughHTMLRenderer{
 73		Config: html.NewConfig(),
 74	}
 75	for _, opt := range opts {
 76		opt.SetHTMLOption(&r.Config)
 77	}
 78	return r
 79}
 80
 81// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
 82func (r *StrikethroughHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
 83	reg.Register(ast.KindStrikethrough, r.renderStrikethrough)
 84}
 85
 86// StrikethroughAttributeFilter defines attribute names which dd elements can have.
 87var StrikethroughAttributeFilter = html.GlobalAttributeFilter
 88
 89func (r *StrikethroughHTMLRenderer) renderStrikethrough(
 90	w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
 91	if entering {
 92		if n.Attributes() != nil {
 93			_, _ = w.WriteString("<del")
 94			html.RenderAttributes(w, n, StrikethroughAttributeFilter)
 95			_ = w.WriteByte('>')
 96		} else {
 97			_, _ = w.WriteString("<del>")
 98		}
 99	} else {
100		_, _ = w.WriteString("</del>")
101	}
102	return gast.WalkContinue, nil
103}
104
105type strikethrough struct {
106}
107
108// Strikethrough is an extension that allow you to use strikethrough expression like '~~text~~' .
109var Strikethrough = &strikethrough{}
110
111func (e *strikethrough) Extend(m goldmark.Markdown) {
112	m.Parser().AddOptions(parser.WithInlineParsers(
113		util.Prioritized(NewStrikethroughParser(), 500),
114	))
115	m.Renderer().AddOptions(renderer.WithNodeRenderers(
116		util.Prioritized(NewStrikethroughHTMLRenderer(), 500),
117	))
118}