list-item.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. /**
  2. * @import {ElementContent, Element, Properties} from 'hast'
  3. * @import {ListItem, Parents} from 'mdast'
  4. * @import {State} from '../state.js'
  5. */
  6. /**
  7. * Turn an mdast `listItem` node into hast.
  8. *
  9. * @param {State} state
  10. * Info passed around.
  11. * @param {ListItem} node
  12. * mdast node.
  13. * @param {Parents | undefined} parent
  14. * Parent of `node`.
  15. * @returns {Element}
  16. * hast node.
  17. */
  18. export function listItem(state, node, parent) {
  19. const results = state.all(node)
  20. const loose = parent ? listLoose(parent) : listItemLoose(node)
  21. /** @type {Properties} */
  22. const properties = {}
  23. /** @type {Array<ElementContent>} */
  24. const children = []
  25. if (typeof node.checked === 'boolean') {
  26. const head = results[0]
  27. /** @type {Element} */
  28. let paragraph
  29. if (head && head.type === 'element' && head.tagName === 'p') {
  30. paragraph = head
  31. } else {
  32. paragraph = {type: 'element', tagName: 'p', properties: {}, children: []}
  33. results.unshift(paragraph)
  34. }
  35. if (paragraph.children.length > 0) {
  36. paragraph.children.unshift({type: 'text', value: ' '})
  37. }
  38. paragraph.children.unshift({
  39. type: 'element',
  40. tagName: 'input',
  41. properties: {type: 'checkbox', checked: node.checked, disabled: true},
  42. children: []
  43. })
  44. // According to github-markdown-css, this class hides bullet.
  45. // See: <https://github.com/sindresorhus/github-markdown-css>.
  46. properties.className = ['task-list-item']
  47. }
  48. let index = -1
  49. while (++index < results.length) {
  50. const child = results[index]
  51. // Add eols before nodes, except if this is a loose, first paragraph.
  52. if (
  53. loose ||
  54. index !== 0 ||
  55. child.type !== 'element' ||
  56. child.tagName !== 'p'
  57. ) {
  58. children.push({type: 'text', value: '\n'})
  59. }
  60. if (child.type === 'element' && child.tagName === 'p' && !loose) {
  61. children.push(...child.children)
  62. } else {
  63. children.push(child)
  64. }
  65. }
  66. const tail = results[results.length - 1]
  67. // Add a final eol.
  68. if (tail && (loose || tail.type !== 'element' || tail.tagName !== 'p')) {
  69. children.push({type: 'text', value: '\n'})
  70. }
  71. /** @type {Element} */
  72. const result = {type: 'element', tagName: 'li', properties, children}
  73. state.patch(node, result)
  74. return state.applyData(node, result)
  75. }
  76. /**
  77. * @param {Parents} node
  78. * @return {Boolean}
  79. */
  80. function listLoose(node) {
  81. let loose = false
  82. if (node.type === 'list') {
  83. loose = node.spread || false
  84. const children = node.children
  85. let index = -1
  86. while (!loose && ++index < children.length) {
  87. loose = listItemLoose(children[index])
  88. }
  89. }
  90. return loose
  91. }
  92. /**
  93. * @param {ListItem} node
  94. * @return {Boolean}
  95. */
  96. function listItemLoose(node) {
  97. const spread = node.spread
  98. return spread === null || spread === undefined
  99. ? node.children.length > 1
  100. : spread
  101. }