summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md2
-rw-r--r--client/components/boards/boardColors.styl165
-rw-r--r--client/components/main/fonts.styl24
-rw-r--r--client/components/main/layouts.styl2
-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--models/boards.js1
-rw-r--r--package-lock.json34
-rw-r--r--package.json1
-rw-r--r--public/fonts/poppins-bold.woffbin0 -> 68016 bytes
-rw-r--r--public/fonts/poppins-medium.woffbin0 -> 68476 bytes
-rw-r--r--public/fonts/poppins-regular.woffbin0 -> 68608 bytes
-rw-r--r--server/migrations.js1
14 files changed, 446 insertions, 3 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cce2276e..de5cdafa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,8 @@ This release adds the following features:
- [Theme: Natural](https://github.com/wekan/wekan/pull/3098).
You can select it from Board Settings / Change color / natural.
Thanks to compumatter and helioguardabaxo,
+- [Export board to HTML static page .zip archive](https://github.com/wekan/wekan/pull/3043).
+ Thanks to Lewiscowles1986.
Thanks to above GitHub users for their contributions and translators for their translations.
diff --git a/client/components/boards/boardColors.styl b/client/components/boards/boardColors.styl
index 6b3994ff..0081143f 100644
--- a/client/components/boards/boardColors.styl
+++ b/client/components/boards/boardColors.styl
@@ -542,6 +542,169 @@ setBoardClear(color1,color2)
.list-header
background-color: #c9cfc3
border-bottom: 6px solid #c9cfc3
-
+
.swimlane .swimlane-header-wrap
background-color: #c2c0ab
+
+/*
+ Alternate "Modern" Styling
+*/
+.board-color-modern
+ setBoardColor(#2A80B8)
+
+ /* General */
+ body
+ background: #f5f5f5
+
+ &#header-quick-access
+ padding: 10px
+ font-size: 14px
+ background: #333 !important
+
+ &#header-quick-access ul
+ overflow: visible
+
+ &#header-quick-access ul li.current
+ border: 0 !important
+ font-weight: bold
+
+ &#header-quick-access ul li.separator
+ display: none
+
+ &#header-quick-access ul li:nth-child(3)
+ margin-right: 10px
+
+ &#header-quick-access ul li a
+ padding: 5px 10px
+ border-radius: 2px
+
+ &#header-quick-access ul li.current a
+ border-radius: 2px
+ background: rgba(255,255,255,.2)
+
+ &#header #header-main-bar h1
+ font-family: Poppins
+ font-weight: bold
+ &#header-quick-access #header-user-bar
+ position relative
+
+ &#header-quick-access #header-user-bar .header-user-bar-name
+ margin: 5px 3px 0 0;
+
+ section#notifications-drawer
+ top: 46px;
+ box-shadow: 0 4px 20px rgba(0,0,0,.1)
+ max-width: 100%
+
+ section#notifications-drawer .header
+ top: 46px;
+ border-radius: 0 3px
+ height: 21px
+ background: #f7f7f7
+
+ /* Swimlane */
+ .swimlane
+ background: #f5f5f5
+
+ .swimlane .swimlane-header-wrap .swimlane-header
+ font-family: Poppins
+
+ /* All board views */
+ .board-list .board-list-item
+ padding: 20px
+
+ .board-list-item-name
+ font-family: Poppins
+
+ /* Board */
+ .list
+ background: transparent
+ border-left: 0
+ margin: 10px 0
+ padding: 0px
+ border-radius: 5px
+ min-width: 300px
+
+ .list-body .open-minicard-composer:hover /*me*/
+ background: none
+ box-shadow: none
+
+ .list:first-child
+ margin-left: 5px
+
+ .list.list-composer.js-list-composer
+ transition: all .3s ease
+ min-width: 80px
+
+ .open-list-composer.js-open-inlined-form:hover
+ color: #222
+
+ .list-header
+ background: none
+ border-bottom-width: 0px
+
+ .list-header .list-header-name
+ font-family: Poppins
+ color: #000
+ font-weight: 500
+
+ /* Card changes */
+ .minicard
+ background: #FFF
+ padding: 15px 15px 10px
+ box-shadow: 0 3px 8px rgba(0,0,0,.05)
+
+ .minicard-plum:hover:not(.minicard-composer), .is-selected .minicard-plum, .draggable-hover-card .minicard-plum
+ background: none
+
+ .minicard-title
+ line-height: 1.5em
+
+ .minicard .minicard-cover
+ background-size: cover
+ margin: -15px -15px 10px
+ height: 100px
+
+ .card-label-orange
+ color: #fff
+
+ .card-date
+ font-size: 12px
+ padding: 3px 5px
+
+ /* Pop over */
+ .header-title
+ font-family: Poppins
+ font-size: 16px
+ color: #333
+
+ .pop-over
+ box-shadow: 0 4px 20px rgba(0,0,0,.1)
+ border: 0
+ border-radius: 5px
+
+ .pop-over .header
+ padding: 10px
+ border-bottom: 0
+ border-radius: 5px 5px 0 0
+
+ .pop-over .content-container .content
+ padding: 5px 20px 20px
+ width: 260px
+
+ .pop-over-list li > a
+ border-radius: 5px
+
+ .pop-over-list li > a > i
+ margin-right: 5px
+
+ .pop-over-list li>a .sub-name
+ margin-bottom: 8px
+
+ /* Sidebar */
+ .sidebar .sidebar-shadow
+ box-shadow: 0 0 60px rgba(0,0,0,.2)
+
+ .sidebar .sidebar-content
+ padding: 30px
+
diff --git a/client/components/main/fonts.styl b/client/components/main/fonts.styl
index fc8c8f00..5d6fb558 100644
--- a/client/components/main/fonts.styl
+++ b/client/components/main/fonts.styl
@@ -15,3 +15,27 @@
local('Roboto-Bold'),
url('/fonts/roboto-bold.woff2') format('woff2'),
url('/fonts/roboto-bold.woff') format('woff')
+
+@font-face
+ font-family: 'Poppins'
+ font-style: normal
+ font-weight: 400
+ src: local('Poppins'),
+ local('Poppins-Regular'),
+ url('/fonts/poppins-regular.woff') format('woff')
+
+@font-face
+ font-family: 'Poppins'
+ font-style: normal
+ font-weight: 500
+ src: local('Poppins Medium'),
+ local('Poppins-Medium'),
+ url('/fonts/poppins-medium.woff') format('woff')
+
+@font-face
+ font-family: 'Poppins'
+ font-style: normal
+ font-weight: 700
+ src: local('Poppins Bold'),
+ local('Poppins-Bold'),
+ url('/fonts/poppins-bold.woff') format('woff')
diff --git a/client/components/main/layouts.styl b/client/components/main/layouts.styl
index 01ce2f16..85a5f1b2 100644
--- a/client/components/main/layouts.styl
+++ b/client/components/main/layouts.styl
@@ -32,7 +32,7 @@ a:hover,a:focus
border-radius: unset
html, body, input, select, textarea, button
- font: 14px Roboto, "Helvetica Neue", Arial, Helvetica, sans-serif
+ font: 14px Roboto, Poppins, "Helvetica Neue", Arial, Helvetica, sans-serif
line-height: 18px
color: #4d4d4d
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/models/boards.js b/models/boards.js
index edfc7240..11d8fd89 100644
--- a/models/boards.js
+++ b/models/boards.js
@@ -257,6 +257,7 @@ Boards.attachSchema(
'corteza',
'clearblue',
'natural',
+ 'modern',
],
// eslint-disable-next-line consistent-return
autoValue() {
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",
diff --git a/public/fonts/poppins-bold.woff b/public/fonts/poppins-bold.woff
new file mode 100644
index 00000000..cc7e8447
--- /dev/null
+++ b/public/fonts/poppins-bold.woff
Binary files differ
diff --git a/public/fonts/poppins-medium.woff b/public/fonts/poppins-medium.woff
new file mode 100644
index 00000000..b8ccafeb
--- /dev/null
+++ b/public/fonts/poppins-medium.woff
Binary files differ
diff --git a/public/fonts/poppins-regular.woff b/public/fonts/poppins-regular.woff
new file mode 100644
index 00000000..1ff39605
--- /dev/null
+++ b/public/fonts/poppins-regular.woff
Binary files differ
diff --git a/server/migrations.js b/server/migrations.js
index ccf875df..5655bd1d 100644
--- a/server/migrations.js
+++ b/server/migrations.js
@@ -121,6 +121,7 @@ Migrations.add('use-css-class-for-boards-colors', () => {
'#568BA2': 'corteza',
'#499BEA': 'clearblue',
'#596557': 'natural',
+ '#2A80B8': 'modern',
};
Boards.find().forEach(board => {
const oldBoardColor = board.background.color;