html.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /**
  2. * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension
  3. * @typedef {import('./syntax.js').Align} Align
  4. */
  5. const alignment = {
  6. none: '',
  7. left: ' align="left"',
  8. right: ' align="right"',
  9. center: ' align="center"'
  10. };
  11. /**
  12. * HTML extension for micromark (passed in `htmlExtensions`).
  13. *
  14. * @type {HtmlExtension}
  15. */
  16. export const gfmTableHtml = {
  17. enter: {
  18. table(token) {
  19. /** @type {Array<Align>} */
  20. // @ts-expect-error Custom.
  21. const tableAlign = token._align;
  22. this.lineEndingIfNeeded();
  23. this.tag('<table>');
  24. this.setData('tableAlign', tableAlign);
  25. },
  26. tableBody() {
  27. // Clear slurping line ending from the delimiter row.
  28. this.setData('slurpOneLineEnding');
  29. this.tag('<tbody>');
  30. },
  31. tableData() {
  32. const tableAlign = /** @type {Array<Align>} */
  33. this.getData('tableAlign');
  34. const tableColumn = /** @type {number} */this.getData('tableColumn');
  35. const align = alignment[tableAlign[tableColumn]];
  36. if (align === undefined) {
  37. // Capture results to ignore them.
  38. this.buffer();
  39. } else {
  40. this.lineEndingIfNeeded();
  41. this.tag('<td' + align + '>');
  42. }
  43. },
  44. tableHead() {
  45. this.lineEndingIfNeeded();
  46. this.tag('<thead>');
  47. },
  48. tableHeader() {
  49. const tableAlign = /** @type {Array<Align>} */
  50. this.getData('tableAlign');
  51. const tableColumn = /** @type {number} */this.getData('tableColumn');
  52. const align = alignment[tableAlign[tableColumn]];
  53. this.lineEndingIfNeeded();
  54. this.tag('<th' + align + '>');
  55. },
  56. tableRow() {
  57. this.setData('tableColumn', 0);
  58. this.lineEndingIfNeeded();
  59. this.tag('<tr>');
  60. }
  61. },
  62. exit: {
  63. // Overwrite the default code text data handler to unescape escaped pipes when
  64. // they are in tables.
  65. codeTextData(token) {
  66. let value = this.sliceSerialize(token);
  67. if (this.getData('tableAlign')) {
  68. value = value.replace(/\\([\\|])/g, replace);
  69. }
  70. this.raw(this.encode(value));
  71. },
  72. table() {
  73. this.setData('tableAlign');
  74. // If there was no table body, make sure the slurping from the delimiter row
  75. // is cleared.
  76. this.setData('slurpAllLineEndings');
  77. this.lineEndingIfNeeded();
  78. this.tag('</table>');
  79. },
  80. tableBody() {
  81. this.lineEndingIfNeeded();
  82. this.tag('</tbody>');
  83. },
  84. tableData() {
  85. const tableAlign = /** @type {Array<Align>} */
  86. this.getData('tableAlign');
  87. const tableColumn = /** @type {number} */this.getData('tableColumn');
  88. if (tableColumn in tableAlign) {
  89. this.tag('</td>');
  90. this.setData('tableColumn', tableColumn + 1);
  91. } else {
  92. // Stop capturing.
  93. this.resume();
  94. }
  95. },
  96. tableHead() {
  97. this.lineEndingIfNeeded();
  98. this.tag('</thead>');
  99. this.setData('slurpOneLineEnding', true);
  100. // Slurp the line ending from the delimiter row.
  101. },
  102. tableHeader() {
  103. const tableColumn = /** @type {number} */this.getData('tableColumn');
  104. this.tag('</th>');
  105. this.setData('tableColumn', tableColumn + 1);
  106. },
  107. tableRow() {
  108. const tableAlign = /** @type {Array<Align>} */
  109. this.getData('tableAlign');
  110. let tableColumn = /** @type {number} */this.getData('tableColumn');
  111. while (tableColumn < tableAlign.length) {
  112. this.lineEndingIfNeeded();
  113. this.tag('<td' + alignment[tableAlign[tableColumn]] + '></td>');
  114. tableColumn++;
  115. }
  116. this.setData('tableColumn', tableColumn);
  117. this.lineEndingIfNeeded();
  118. this.tag('</tr>');
  119. }
  120. }
  121. };
  122. /**
  123. * @param {string} $0
  124. * @param {string} $1
  125. * @returns {string}
  126. */
  127. function replace($0, $1) {
  128. // Pipes work, backslashes don’t (but can’t escape pipes).
  129. return $1 === '|' ? $1 : $0;
  130. }