You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tasklist.go 3.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. package extension
  2. import (
  3. "github.com/yuin/goldmark"
  4. gast "github.com/yuin/goldmark/ast"
  5. "github.com/yuin/goldmark/extension/ast"
  6. "github.com/yuin/goldmark/parser"
  7. "github.com/yuin/goldmark/renderer"
  8. "github.com/yuin/goldmark/renderer/html"
  9. "github.com/yuin/goldmark/text"
  10. "github.com/yuin/goldmark/util"
  11. "regexp"
  12. )
  13. var taskListRegexp = regexp.MustCompile(`^\[([\sxX])\]\s*`)
  14. type taskCheckBoxParser struct {
  15. }
  16. var defaultTaskCheckBoxParser = &taskCheckBoxParser{}
  17. // NewTaskCheckBoxParser returns a new InlineParser that can parse
  18. // checkboxes in list items.
  19. // This parser must take precedence over the parser.LinkParser.
  20. func NewTaskCheckBoxParser() parser.InlineParser {
  21. return defaultTaskCheckBoxParser
  22. }
  23. func (s *taskCheckBoxParser) Trigger() []byte {
  24. return []byte{'['}
  25. }
  26. func (s *taskCheckBoxParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
  27. // Given AST structure must be like
  28. // - List
  29. // - ListItem : parent.Parent
  30. // - TextBlock : parent
  31. // (current line)
  32. if parent.Parent() == nil || parent.Parent().FirstChild() != parent {
  33. return nil
  34. }
  35. if _, ok := parent.Parent().(*gast.ListItem); !ok {
  36. return nil
  37. }
  38. line, _ := block.PeekLine()
  39. m := taskListRegexp.FindSubmatchIndex(line)
  40. if m == nil {
  41. return nil
  42. }
  43. value := line[m[2]:m[3]][0]
  44. block.Advance(m[1])
  45. checked := value == 'x' || value == 'X'
  46. return ast.NewTaskCheckBox(checked)
  47. }
  48. func (s *taskCheckBoxParser) CloseBlock(parent gast.Node, pc parser.Context) {
  49. // nothing to do
  50. }
  51. // TaskCheckBoxHTMLRenderer is a renderer.NodeRenderer implementation that
  52. // renders checkboxes in list items.
  53. type TaskCheckBoxHTMLRenderer struct {
  54. html.Config
  55. }
  56. // NewTaskCheckBoxHTMLRenderer returns a new TaskCheckBoxHTMLRenderer.
  57. func NewTaskCheckBoxHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
  58. r := &TaskCheckBoxHTMLRenderer{
  59. Config: html.NewConfig(),
  60. }
  61. for _, opt := range opts {
  62. opt.SetHTMLOption(&r.Config)
  63. }
  64. return r
  65. }
  66. // RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
  67. func (r *TaskCheckBoxHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
  68. reg.Register(ast.KindTaskCheckBox, r.renderTaskCheckBox)
  69. }
  70. func (r *TaskCheckBoxHTMLRenderer) renderTaskCheckBox(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
  71. if !entering {
  72. return gast.WalkContinue, nil
  73. }
  74. n := node.(*ast.TaskCheckBox)
  75. if n.IsChecked {
  76. w.WriteString(`<input checked="" disabled="" type="checkbox"`)
  77. } else {
  78. w.WriteString(`<input disabled="" type="checkbox"`)
  79. }
  80. if r.XHTML {
  81. w.WriteString(" /> ")
  82. } else {
  83. w.WriteString("> ")
  84. }
  85. return gast.WalkContinue, nil
  86. }
  87. type taskList struct {
  88. }
  89. // TaskList is an extension that allow you to use GFM task lists.
  90. var TaskList = &taskList{}
  91. func (e *taskList) Extend(m goldmark.Markdown) {
  92. m.Parser().AddOptions(parser.WithInlineParsers(
  93. util.Prioritized(NewTaskCheckBoxParser(), 0),
  94. ))
  95. m.Renderer().AddOptions(renderer.WithNodeRenderers(
  96. util.Prioritized(NewTaskCheckBoxHTMLRenderer(), 500),
  97. ))
  98. }