turndown-plugin-gfm.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. var turndownPluginGfm = (function (exports) {
  2. 'use strict';
  3. var highlightRegExp = /highlight-(?:text|source)-([a-z0-9]+)/;
  4. function highlightedCodeBlock (turndownService) {
  5. turndownService.addRule('highlightedCodeBlock', {
  6. filter: function (node) {
  7. var firstChild = node.firstChild;
  8. return (
  9. node.nodeName === 'DIV' &&
  10. highlightRegExp.test(node.className) &&
  11. firstChild &&
  12. firstChild.nodeName === 'PRE'
  13. )
  14. },
  15. replacement: function (content, node, options) {
  16. var className = node.className || '';
  17. var language = (className.match(highlightRegExp) || [null, ''])[1];
  18. return (
  19. '\n\n' + options.fence + language + '\n' +
  20. node.firstChild.textContent +
  21. '\n' + options.fence + '\n\n'
  22. )
  23. }
  24. });
  25. }
  26. function strikethrough (turndownService) {
  27. turndownService.addRule('strikethrough', {
  28. filter: ['del', 's', 'strike'],
  29. replacement: function (content) {
  30. return '~' + content + '~'
  31. }
  32. });
  33. }
  34. var indexOf = Array.prototype.indexOf;
  35. var every = Array.prototype.every;
  36. var rules = {};
  37. rules.tableCell = {
  38. filter: ['th', 'td'],
  39. replacement: function (content, node) {
  40. return cell(content, node)
  41. }
  42. };
  43. rules.tableRow = {
  44. filter: 'tr',
  45. replacement: function (content, node) {
  46. var borderCells = '';
  47. var alignMap = { left: ':--', right: '--:', center: ':-:' };
  48. if (isHeadingRow(node)) {
  49. for (var i = 0; i < node.childNodes.length; i++) {
  50. var border = '---';
  51. var align = (
  52. node.childNodes[i].getAttribute('align') || ''
  53. ).toLowerCase();
  54. if (align) border = alignMap[align] || border;
  55. borderCells += cell(border, node.childNodes[i]);
  56. }
  57. }
  58. return '\n' + content + (borderCells ? '\n' + borderCells : '')
  59. }
  60. };
  61. rules.table = {
  62. // Only convert tables with a heading row.
  63. // Tables with no heading row are kept using `keep` (see below).
  64. filter: function (node) {
  65. return node.nodeName === 'TABLE' && isHeadingRow(node.rows[0])
  66. },
  67. replacement: function (content) {
  68. // Ensure there are no blank lines
  69. content = content.replace('\n\n', '\n');
  70. return '\n\n' + content + '\n\n'
  71. }
  72. };
  73. rules.tableSection = {
  74. filter: ['thead', 'tbody', 'tfoot'],
  75. replacement: function (content) {
  76. return content
  77. }
  78. };
  79. // A tr is a heading row if:
  80. // - the parent is a THEAD
  81. // - or if its the first child of the TABLE or the first TBODY (possibly
  82. // following a blank THEAD)
  83. // - and every cell is a TH
  84. function isHeadingRow (tr) {
  85. var parentNode = tr.parentNode;
  86. return (
  87. parentNode.nodeName === 'THEAD' ||
  88. (
  89. parentNode.firstChild === tr &&
  90. (parentNode.nodeName === 'TABLE' || isFirstTbody(parentNode)) &&
  91. every.call(tr.childNodes, function (n) { return n.nodeName === 'TH' })
  92. )
  93. )
  94. }
  95. function isFirstTbody (element) {
  96. var previousSibling = element.previousSibling;
  97. return (
  98. element.nodeName === 'TBODY' && (
  99. !previousSibling ||
  100. (
  101. previousSibling.nodeName === 'THEAD' &&
  102. /^\s*$/i.test(previousSibling.textContent)
  103. )
  104. )
  105. )
  106. }
  107. //修复,当表格中有换行时,解析不正确,修复表格为空时,增加3个空格,确保能正确解析
  108. function cell(content, node) {
  109. var index = indexOf.call(node.parentNode.childNodes, node);
  110. var prefix = ' ';
  111. content = content.replace("\n", "<br/>")
  112. if (index === 0)
  113. prefix = '| ';
  114. let filteredContent = content.trim().replace(/\n\r/g, '<br/>').replace(/\n/g, "<br/>");
  115. filteredContent = filteredContent.replace(/\|+/g, '\\|');
  116. while (filteredContent.length < 3)
  117. filteredContent += ' ';
  118. return prefix + filteredContent + ' |'
  119. }
  120. function tables (turndownService) {
  121. turndownService.keep(function (node) {
  122. return node.nodeName === 'TABLE' && !isHeadingRow(node.rows[0])
  123. });
  124. for (var key in rules) turndownService.addRule(key, rules[key]);
  125. }
  126. function taskListItems (turndownService) {
  127. turndownService.addRule('taskListItems', {
  128. filter: function (node) {
  129. return node.type === 'checkbox' && node.parentNode.nodeName === 'LI'
  130. },
  131. replacement: function (content, node) {
  132. return (node.checked ? '[x]' : '[ ]') + ' '
  133. }
  134. });
  135. }
  136. function gfm (turndownService) {
  137. turndownService.use([
  138. highlightedCodeBlock,
  139. strikethrough,
  140. tables,
  141. taskListItems
  142. ]);
  143. }
  144. exports.gfm = gfm;
  145. exports.highlightedCodeBlock = highlightedCodeBlock;
  146. exports.strikethrough = strikethrough;
  147. exports.tables = tables;
  148. exports.taskListItems = taskListItems;
  149. return exports;
  150. }({}));