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