exportHTML.js 6.2 KB

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