exportHTML.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. const JSZip = require('jszip');
  2. window.ExportHtml = Popup => {
  3. const saveAs = function(blob, filename) {
  4. const dl = document.createElement('a');
  5. dl.href = window.URL.createObjectURL(blob);
  6. dl.onclick = event => document.body.removeChild(event.target);
  7. dl.style.display = 'none';
  8. dl.target = '_blank';
  9. dl.download = filename;
  10. document.body.appendChild(dl);
  11. dl.click();
  12. };
  13. const asyncForEach = async function(array, callback) {
  14. for (let index = 0; index < array.length; index++) {
  15. await callback(array[index], index, array);
  16. }
  17. };
  18. const getPageHtmlString = () => {
  19. return `<!doctype html>${window.document.querySelector('html').outerHTML}`;
  20. };
  21. const removeAnchors = htmlString => {
  22. const replaceOpenAnchor = htmlString.replace(
  23. new RegExp('<a ', 'gim'),
  24. '<span ',
  25. );
  26. return replaceOpenAnchor.replace(new RegExp('</a', 'gim'), '</span');
  27. };
  28. const ensureSidebarRemoved = () => {
  29. document.querySelector('.board-sidebar.sidebar').remove();
  30. };
  31. const addJsonExportToZip = async (zip, boardSlug) => {
  32. const downloadJSONLink = document.querySelector('.download-json-link');
  33. const downloadJSONURL = downloadJSONLink.href;
  34. const response = await fetch(downloadJSONURL);
  35. const responseBody = await response.text();
  36. zip.file(`data/${boardSlug}.json`, responseBody);
  37. };
  38. const closeSidebar = () => {
  39. document.querySelector('.board-header-btn.js-toggle-sidebar').click();
  40. };
  41. const cleanBoardHtml = () => {
  42. Array.from(document.querySelectorAll('script')).forEach(elem =>
  43. elem.remove(),
  44. );
  45. Array.from(
  46. document.querySelectorAll('link:not([rel="stylesheet"])'),
  47. ).forEach(elem => elem.remove());
  48. document.querySelector('#header-quick-access').remove();
  49. Array.from(
  50. document.querySelectorAll('#header-main-bar .board-header-btns'),
  51. ).forEach(elem => elem.remove());
  52. Array.from(document.querySelectorAll('.list-composer')).forEach(elem =>
  53. elem.remove(),
  54. );
  55. Array.from(
  56. document.querySelectorAll(
  57. '.list-composer,.js-card-composer, .js-add-card',
  58. ),
  59. ).forEach(elem => elem.remove());
  60. Array.from(document.querySelectorAll('[href]:not(link)')).forEach(elem =>
  61. elem.attributes.removeNamedItem('href'),
  62. );
  63. Array.from(document.querySelectorAll('[href]')).forEach(elem => {
  64. // eslint-disable-next-line no-self-assign
  65. elem.href = elem.href;
  66. // eslint-disable-next-line no-self-assign
  67. elem.src = elem.src;
  68. });
  69. Array.from(document.querySelectorAll('.is-editable')).forEach(elem => {
  70. elem.classList.remove('is-editable');
  71. });
  72. };
  73. const getBoardSlug = () => {
  74. return window.location.href.split('/').pop();
  75. };
  76. const getStylesheetList = () => {
  77. return Array.from(
  78. document.querySelectorAll('link[href][rel="stylesheet"]'),
  79. );
  80. };
  81. const downloadStylesheets = async (stylesheets, zip) => {
  82. await asyncForEach(stylesheets, async elem => {
  83. const response = await fetch(elem.href);
  84. const responseBody = await response.text();
  85. const finalResponse = responseBody.replace(
  86. new RegExp('packages/[^/]+/upstream/', 'gim'),
  87. '../',
  88. );
  89. const filename = elem.href
  90. .split('/')
  91. .pop()
  92. .split('?')
  93. .shift();
  94. const fileFullPath = `style/${filename}`;
  95. zip.file(fileFullPath, finalResponse);
  96. elem.href = `../${fileFullPath}`;
  97. });
  98. };
  99. const getSrcAttached = () => {
  100. return Array.from(document.querySelectorAll('[src]'));
  101. };
  102. const downloadSrcAttached = async (elements, zip, boardSlug) => {
  103. await asyncForEach(elements, async elem => {
  104. const response = await fetch(elem.src);
  105. const responseBody = await response.blob();
  106. const filename = elem.src
  107. .split('/')
  108. .pop()
  109. .split('?')
  110. .shift();
  111. const fileFullPath = `${boardSlug}/${elem.tagName.toLowerCase()}/${filename}`;
  112. zip.file(fileFullPath, responseBody);
  113. elem.src = `./${elem.tagName.toLowerCase()}/${filename}`;
  114. });
  115. };
  116. const removeCssUrlSurround = url => {
  117. const working = url || '';
  118. return working
  119. .split('url(')
  120. .join('')
  121. .split('")')
  122. .join('')
  123. .split('"')
  124. .join('')
  125. .split("')")
  126. .join('')
  127. .split("'")
  128. .join('')
  129. .split(')')
  130. .join('');
  131. };
  132. const getCardCovers = () => {
  133. return Array.from(document.querySelectorAll('.minicard-cover')).filter(
  134. elem => elem.style['background-image'],
  135. );
  136. };
  137. const downloadCardCovers = async (elements, zip, boardSlug) => {
  138. await asyncForEach(elements, async elem => {
  139. const response = await fetch(
  140. removeCssUrlSurround(elem.style['background-image']),
  141. );
  142. const responseBody = await response.blob();
  143. const filename = removeCssUrlSurround(elem.style['background-image'])
  144. .split('/')
  145. .pop()
  146. .split('?')
  147. .shift()
  148. .split('#')
  149. .shift();
  150. const fileFullPath = `${boardSlug}/covers/${filename}`;
  151. zip.file(fileFullPath, responseBody);
  152. elem.style = "background-image: url('" + `covers/${filename}` + "')";
  153. });
  154. };
  155. const addBoardHTMLToZip = (boardSlug, zip) => {
  156. ensureSidebarRemoved();
  157. const htmlOutputPath = `${boardSlug}/index.html`;
  158. zip.file(
  159. htmlOutputPath,
  160. new Blob([removeAnchors(getPageHtmlString())], {
  161. type: 'application/html',
  162. }),
  163. );
  164. };
  165. return async () => {
  166. const zip = new JSZip();
  167. const boardSlug = getBoardSlug();
  168. await addJsonExportToZip(zip, boardSlug);
  169. Popup.back();
  170. closeSidebar();
  171. cleanBoardHtml();
  172. await downloadStylesheets(getStylesheetList(), zip);
  173. await downloadSrcAttached(getSrcAttached(), zip, boardSlug);
  174. await downloadCardCovers(getCardCovers(), zip, boardSlug);
  175. addBoardHTMLToZip(boardSlug, zip);
  176. const content = await zip.generateAsync({ type: 'blob' });
  177. saveAs(content, `${boardSlug}.zip`);
  178. window.location.reload();
  179. };
  180. };