summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client/components/sidebar/sidebar.jade6
-rw-r--r--client/components/sidebar/sidebar.js7
-rw-r--r--client/lib/exportHTML.js206
-rw-r--r--package-lock.json34
-rw-r--r--package.json1
5 files changed, 253 insertions, 1 deletions
diff --git a/client/components/sidebar/sidebar.jade b/client/components/sidebar/sidebar.jade
index 7d637142..04f2a8c2 100644
--- a/client/components/sidebar/sidebar.jade
+++ b/client/components/sidebar/sidebar.jade
@@ -363,7 +363,7 @@ template(name="boardMenuPopup")
template(name="exportBoard")
ul.pop-over-list
li
- a(href="{{exportUrl}}", download="{{exportJsonFilename}}")
+ a.download-json-link(href="{{exportUrl}}", download="{{exportJsonFilename}}")
i.fa.fa-share-alt
| {{_ 'export-board-json'}}
li
@@ -374,6 +374,10 @@ template(name="exportBoard")
a(href="{{exportTsvUrl}}", download="{{exportTsvFilename}}")
i.fa.fa-share-alt
| {{_ 'export-board-tsv'}}
+ li
+ a.html-export-board
+ i.fa.fa-archive
+ | {{_ 'export-board-html'}}
template(name="labelsWidget")
.board-widget.board-widget-labels
diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js
index 2c1cfd75..0e535041 100644
--- a/client/components/sidebar/sidebar.js
+++ b/client/components/sidebar/sidebar.js
@@ -463,6 +463,13 @@ BlazeComponent.extendComponent({
},
}).register('exportBoardPopup');
+Template.exportBoard.events({
+ 'click .html-export-board': async event => {
+ event.preventDefault();
+ await ExportHtml(Popup)();
+ }
+});
+
Template.labelsWidget.events({
'click .js-label': Popup.open('editLabel'),
'click .js-add-label': Popup.open('createLabel'),
diff --git a/client/lib/exportHTML.js b/client/lib/exportHTML.js
new file mode 100644
index 00000000..fe15b6aa
--- /dev/null
+++ b/client/lib/exportHTML.js
@@ -0,0 +1,206 @@
+const JSZip = require('jszip');
+
+window.ExportHtml = (Popup) => {
+ const saveAs = function(blob, filename) {
+ let dl = document.createElement('a');
+ dl.href = window.URL.createObjectURL(blob);
+ dl.onclick = event => document.body.removeChild(event.target);
+ dl.style.display = 'none';
+ dl.target = '_blank';
+ dl.download = filename;
+ document.body.appendChild(dl);
+ dl.click();
+ };
+
+ const asyncForEach = async function (array, callback) {
+ for (let index = 0; index < array.length; index++) {
+ await callback(array[index], index, array);
+ }
+ };
+
+ const getPageHtmlString = () => {
+ return `<!doctype html>${
+ window.document.querySelector('html').outerHTML
+ }`;
+ };
+
+ const removeAnchors = htmlString => {
+ const replaceOpenAnchor = htmlString.replace(new RegExp('<a ', 'gim'), '<span ');
+ return replaceOpenAnchor.replace(new RegExp('<\/a', 'gim'), '</span');
+ };
+
+ const ensureSidebarRemoved = () => {
+ document.querySelector('.board-sidebar.sidebar').remove();
+ };
+
+ const addJsonExportToZip = async (zip, boardSlug) => {
+ const downloadJSONLink = document.querySelector('.download-json-link');
+ const downloadJSONURL = downloadJSONLink.href;
+ const response = await fetch(downloadJSONURL);
+ const responseBody = await response.text();
+ zip.file(`data/${boardSlug}.json`, responseBody);
+ };
+
+ const closeSidebar = () => {
+ document.querySelector('.board-header-btn.js-toggle-sidebar').click();
+ };
+
+ const cleanBoardHtml = () => {
+ Array.from(document.querySelectorAll('script')).forEach(elem =>
+ elem.remove(),
+ );
+ Array.from(
+ document.querySelectorAll('link:not([rel="stylesheet"])'),
+ ).forEach(elem => elem.remove());
+ document.querySelector('#header-quick-access').remove();
+ Array.from(
+ document.querySelectorAll('#header-main-bar .board-header-btns'),
+ ).forEach(elem => elem.remove());
+ Array.from(document.querySelectorAll('.list-composer')).forEach(elem =>
+ elem.remove(),
+ );
+ Array.from(
+ document.querySelectorAll(
+ '.list-composer,.js-card-composer, .js-add-card',
+ ),
+ ).forEach(elem => elem.remove());
+ Array.from(
+ document.querySelectorAll('.js-perfect-scrollbar > div:nth-of-type(n+2)'),
+ ).forEach(elem => elem.remove());
+ Array.from(document.querySelectorAll('.js-perfect-scrollbar')).forEach(
+ elem => {
+ elem.style = 'overflow-y: auto !important;';
+ elem.classList.remove('js-perfect-scrollbar');
+ },
+ );
+ Array.from(document.querySelectorAll('[href]:not(link)')).forEach(elem =>
+ elem.attributes.removeNamedItem('href'),
+ );
+ Array.from(document.querySelectorAll('[href]')).forEach(elem => {
+ // eslint-disable-next-line no-self-assign
+ elem.href = elem.href;
+ // eslint-disable-next-line no-self-assign
+ elem.src = elem.src;
+ });
+ Array.from(document.querySelectorAll('.is-editable')).forEach(elem => {
+ elem.classList.remove('is-editable')
+ })
+
+ };
+
+ const getBoardSlug = () => {
+ return window.location.href.split('/').pop();
+ };
+
+ const getStylesheetList = () => {
+ return Array.from(
+ document.querySelectorAll('link[href][rel="stylesheet"]'),
+ );
+ };
+
+ const downloadStylesheets = async (stylesheets, zip) => {
+ await asyncForEach(stylesheets, async elem => {
+ const response = await fetch(elem.href);
+ const responseBody = await response.text();
+
+ const finalResponse = responseBody.replace(
+ new RegExp('packages\/[^\/]+\/upstream\/', 'gim'), '../'
+ );
+
+ const filename = elem.href
+ .split('/')
+ .pop()
+ .split('?')
+ .shift();
+ const fileFullPath = `style/${filename}`;
+ zip.file(fileFullPath, finalResponse);
+ elem.href = `../${fileFullPath}`;
+ });
+ };
+
+ const getSrcAttached = () => {
+ return Array.from(document.querySelectorAll('[src]'));
+ };
+
+ const downloadSrcAttached = async (elements, zip, boardSlug) => {
+ await asyncForEach(elements, async elem => {
+ const response = await fetch(elem.src);
+ const responseBody = await response.blob();
+ const filename = elem.src
+ .split('/')
+ .pop()
+ .split('?')
+ .shift();
+ const fileFullPath = `${boardSlug}/${elem.tagName.toLowerCase()}/${filename}`;
+ zip.file(fileFullPath, responseBody);
+ elem.src = `./${elem.tagName.toLowerCase()}/${filename}`;
+ });
+ };
+
+ const removeCssUrlSurround = url => {
+ const working = url || "";
+ return working
+ .split("url(")
+ .join("")
+ .split("\")")
+ .join("")
+ .split("\"")
+ .join("")
+ .split("')")
+ .join("")
+ .split("'")
+ .join("")
+ .split(")")
+ .join("");
+ };
+
+ const getCardCovers = () => {
+ return Array.from(document.querySelectorAll('.minicard-cover'))
+ .filter(elem => elem.style['background-image'])
+ }
+
+ const downloadCardCovers = async (elements, zip, boardSlug) => {
+ await asyncForEach(elements, async elem => {
+ const response = await fetch(removeCssUrlSurround(elem.style['background-image']));
+ const responseBody = await response.blob();
+ const filename = removeCssUrlSurround(elem.style['background-image'])
+ .split('/')
+ .pop()
+ .split('?')
+ .shift()
+ .split('#')
+ .shift();
+ const fileFullPath = `${boardSlug}/covers/${filename}`;
+ zip.file(fileFullPath, responseBody);
+ elem.style = "background-image: url('" + `covers/${filename}` + "')";
+ });
+ };
+
+ const addBoardHTMLToZip = (boardSlug, zip) => {
+ ensureSidebarRemoved();
+ const htmlOutputPath = `${boardSlug}/index.html`;
+ zip.file(htmlOutputPath, new Blob([
+ removeAnchors(getPageHtmlString())
+ ], { type: 'application/html' }));
+ };
+
+ return async () => {
+ const zip = new JSZip();
+ const boardSlug = getBoardSlug();
+
+ await addJsonExportToZip(zip, boardSlug);
+ Popup.close();
+ closeSidebar();
+ cleanBoardHtml();
+
+ await downloadStylesheets(getStylesheetList(), zip);
+ await downloadSrcAttached(getSrcAttached(), zip, boardSlug);
+ await downloadCardCovers(getCardCovers(), zip, boardSlug);
+
+ addBoardHTMLToZip(boardSlug, zip);
+
+ const content = await zip.generateAsync({ type: 'blob' });
+ saveAs(content, `${boardSlug}.zip`);
+ window.location.reload();
+ }
+};
diff --git a/package-lock.json b/package-lock.json
index 9d156628..1e69d824 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2039,6 +2039,11 @@
"minimatch": "^3.0.4"
}
},
+ "immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps="
+ },
"import-fresh": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
@@ -2460,6 +2465,17 @@
"minimist": "^1.2.5"
}
},
+ "jszip": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.4.0.tgz",
+ "integrity": "sha512-gZAOYuPl4EhPTXT0GjhI3o+ZAz3su6EhLrKUoAivcKqyqC7laS5JEv4XWZND9BgcDcF83vI85yGbDmDR6UhrIg==",
+ "requires": {
+ "lie": "~3.3.0",
+ "pako": "~1.0.2",
+ "readable-stream": "~2.3.6",
+ "set-immediate-shim": "~1.0.1"
+ }
+ },
"kind-of": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
@@ -2514,6 +2530,14 @@
"type-check": "~0.3.2"
}
},
+ "lie": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+ "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+ "requires": {
+ "immediate": "~3.0.5"
+ }
+ },
"lint-staged": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-7.3.0.tgz",
@@ -3869,6 +3893,11 @@
"path-to-regexp": "~1.2.1"
}
},
+ "pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
+ },
"papaparse": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.2.0.tgz",
@@ -4330,6 +4359,11 @@
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
+ "set-immediate-shim": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
+ "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E="
+ },
"set-value": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
diff --git a/package.json b/package.json
index 1b4d3e8a..b049a12d 100644
--- a/package.json
+++ b/package.json
@@ -66,6 +66,7 @@
"es6-promise": "^4.2.4",
"flatted": "^2.0.1",
"gridfs-stream": "^0.5.3",
+ "jszip": "^3.4.0",
"ldapjs": "^1.0.2",
"meteor-node-stubs": "^0.4.1",
"mongodb": "^3.5.7",