exportHTML.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  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(
  61. document.querySelectorAll('.js-perfect-scrollbar > div:nth-of-type(n+2)'),
  62. ).forEach(elem => elem.remove());
  63. Array.from(document.querySelectorAll('.js-perfect-scrollbar')).forEach(
  64. elem => {
  65. elem.style = 'overflow-y: auto !important;';
  66. elem.classList.remove('js-perfect-scrollbar');
  67. },
  68. );
  69. Array.from(document.querySelectorAll('[href]:not(link)')).forEach(elem =>
  70. elem.attributes.removeNamedItem('href'),
  71. );
  72. Array.from(document.querySelectorAll('[href]')).forEach(elem => {
  73. // eslint-disable-next-line no-self-assign
  74. elem.href = elem.href;
  75. // eslint-disable-next-line no-self-assign
  76. elem.src = elem.src;
  77. });
  78. Array.from(document.querySelectorAll('.is-editable')).forEach(elem => {
  79. elem.classList.remove('is-editable');
  80. });
  81. };
  82. const getBoardSlug = () => {
  83. return window.location.href.split('/').pop();
  84. };
  85. const getStylesheetList = () => {
  86. return Array.from(
  87. document.querySelectorAll('link[href][rel="stylesheet"]'),
  88. );
  89. };
  90. const downloadStylesheets = async (stylesheets, zip) => {
  91. await asyncForEach(stylesheets, async elem => {
  92. const response = await fetch(elem.href);
  93. const responseBody = await response.text();
  94. const finalResponse = responseBody.replace(
  95. new RegExp('packages/[^/]+/upstream/', 'gim'),
  96. '../',
  97. );
  98. const filename = elem.href
  99. .split('/')
  100. .pop()
  101. .split('?')
  102. .shift();
  103. const fileFullPath = `style/${filename}`;
  104. zip.file(fileFullPath, finalResponse);
  105. elem.href = `../${fileFullPath}`;
  106. });
  107. };
  108. const getSrcAttached = () => {
  109. return Array.from(document.querySelectorAll('[src]'));
  110. };
  111. const downloadSrcAttached = async (elements, zip, boardSlug) => {
  112. await asyncForEach(elements, async elem => {
  113. const response = await fetch(elem.src);
  114. const responseBody = await response.blob();
  115. const filename = elem.src
  116. .split('/')
  117. .pop()
  118. .split('?')
  119. .shift();
  120. const fileFullPath = `${boardSlug}/${elem.tagName.toLowerCase()}/${filename}`;
  121. zip.file(fileFullPath, responseBody);
  122. elem.src = `./${elem.tagName.toLowerCase()}/${filename}`;
  123. });
  124. };
  125. const removeCssUrlSurround = url => {
  126. const working = url || '';
  127. return working
  128. .split('url(')
  129. .join('')
  130. .split('")')
  131. .join('')
  132. .split('"')
  133. .join('')
  134. .split("')")
  135. .join('')
  136. .split("'")
  137. .join('')
  138. .split(')')
  139. .join('');
  140. };
  141. const getCardCovers = () => {
  142. return Array.from(document.querySelectorAll('.minicard-cover')).filter(
  143. elem => elem.style['background-image'],
  144. );
  145. };
  146. const downloadCardCovers = async (elements, zip, boardSlug) => {
  147. await asyncForEach(elements, async elem => {
  148. const response = await fetch(
  149. removeCssUrlSurround(elem.style['background-image']),
  150. );
  151. const responseBody = await response.blob();
  152. const filename = removeCssUrlSurround(elem.style['background-image'])
  153. .split('/')
  154. .pop()
  155. .split('?')
  156. .shift()
  157. .split('#')
  158. .shift();
  159. const fileFullPath = `${boardSlug}/covers/${filename}`;
  160. zip.file(fileFullPath, responseBody);
  161. elem.style = "background-image: url('" + `covers/${filename}` + "')";
  162. });
  163. };
  164. const addBoardHTMLToZip = (boardSlug, zip) => {
  165. ensureSidebarRemoved();
  166. const htmlOutputPath = `${boardSlug}/index.html`;
  167. zip.file(
  168. htmlOutputPath,
  169. new Blob([removeAnchors(getPageHtmlString())], {
  170. type: 'application/html',
  171. }),
  172. );
  173. };
  174. return async () => {
  175. const zip = new JSZip();
  176. const boardSlug = getBoardSlug();
  177. await addJsonExportToZip(zip, boardSlug);
  178. Popup.close();
  179. closeSidebar();
  180. cleanBoardHtml();
  181. await downloadStylesheets(getStylesheetList(), zip);
  182. await downloadSrcAttached(getSrcAttached(), zip, boardSlug);
  183. await downloadCardCovers(getCardCovers(), zip, boardSlug);
  184. addBoardHTMLToZip(boardSlug, zip);
  185. const content = await zip.generateAsync({ type: 'blob' });
  186. saveAs(content, `${boardSlug}.zip`);
  187. window.location.reload();
  188. };
  189. };