html2markdown.js 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. /**
  2. * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /**
  6. * @module markdown-gfm/html2markdown
  7. */
  8. import TurndownService from 'turndown';
  9. import { gfm } from 'turndown-plugin-gfm';
  10. // Override the original escape method by not escaping links.
  11. const originalEscape = TurndownService.prototype.escape;
  12. function escape( string ) {
  13. string = originalEscape( string );
  14. // Escape "<".
  15. string = string.replace( /</g, '\\<' );
  16. return string;
  17. }
  18. // eslint-disable-next-line max-len
  19. const regex = /\b(?:https?:\/\/|www\.)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()[\]{};:'".,<>?«»“”‘’])/g;
  20. TurndownService.prototype.escape = function( string ) {
  21. // Urls should not be escaped. Our strategy is using a regex to find them and escape everything
  22. // which is out of the matches parts.
  23. let escaped = '';
  24. let lastLinkEnd = 0;
  25. for ( const match of string.matchAll( regex ) ) {
  26. const index = match.index;
  27. // Append the substring between the last match and the current one (if anything).
  28. if ( index >= lastLinkEnd ) {
  29. escaped += escape( string.substring( lastLinkEnd, index ) );
  30. }
  31. const matchedURL = match[ 0 ];
  32. escaped += matchedURL;
  33. lastLinkEnd = index + matchedURL.length;
  34. }
  35. // Add text after the last link or at the string start if no matches.
  36. if ( lastLinkEnd < string.length ) {
  37. escaped += escape( string.substring( lastLinkEnd, string.length ) );
  38. }
  39. return escaped;
  40. };
  41. const turndownService = new TurndownService( {
  42. codeBlockStyle: 'fenced',
  43. hr: '---',
  44. headingStyle: 'atx'
  45. } );
  46. turndownService.use( [
  47. gfm,
  48. todoList
  49. ] );
  50. /**
  51. * Parses HTML to a markdown.
  52. *
  53. * @param {String} html
  54. * @returns {String}
  55. */
  56. export default function html2markdown( html ) {
  57. return turndownService.turndown( html );
  58. }
  59. export { turndownService };
  60. // This is a copy of the original taskListItems rule from turdown-plugin-gfm, with minor changes.
  61. function todoList( turndownService ) {
  62. turndownService.addRule( 'taskListItems', {
  63. filter( node ) {
  64. return node.type === 'checkbox' &&
  65. // Changes here as CKEditor outputs a deeper structure.
  66. ( node.parentNode.nodeName === 'LI' || node.parentNode.parentNode.nodeName === 'LI' );
  67. },
  68. replacement( content, node ) {
  69. return ( node.checked ? '[x]' : '[ ]' ) + ' ';
  70. }
  71. } );
  72. }