summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md14
-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--i18n/pl.i18n.json48
-rw-r--r--package-lock.json34
-rw-r--r--package.json1
7 files changed, 291 insertions, 25 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ffcb2428..de5cdafa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,17 @@
+# Upcoming Wekan release
+
+This release adds the following features:
+
+- [Found Time Tracking GPLv3 software "Titra" with integration to Wekan](https://github.com/wekan/wekan/wiki/Time-Tracking).
+ Thanks to willhseitz.
+- [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.
+
# v4.03 2020-05-16 Wekan release
This release adds the following features:
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/i18n/pl.i18n.json b/i18n/pl.i18n.json
index 8c6380ad..e76a2d83 100644
--- a/i18n/pl.i18n.json
+++ b/i18n/pl.i18n.json
@@ -164,15 +164,15 @@
"cardStartVotingPopup-title": "Zacznij głosowanie",
"positiveVoteMembersPopup-title": "Zwolennicy",
"negativeVoteMembersPopup-title": "Przeciwnicy",
- "card-edit-voting": "Edit voting",
- "editVoteEndDatePopup-title": "Change vote end date",
- "allowNonBoardMembers": "Allow all logged in users",
+ "card-edit-voting": "Edytuj głosowanie",
+ "editVoteEndDatePopup-title": "Zmień głos oraz datę",
+ "allowNonBoardMembers": "Pozwól wszystkim zalogowanym użytkownikom",
"vote-question": "Pytanie do głosowania",
"vote-public": "Pokaż, kto głosował na wybrane opcje",
"vote-for-it": "za",
"vote-against": "przeciwko",
- "deleteVotePopup-title": "Delete vote?",
- "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.",
+ "deleteVotePopup-title": "Usunąć głos?",
+ "vote-delete-pop": "Usuwanie jest nieodwracalne. Stracisz wszystkie czynności związane z tym głosem.",
"cardDeletePopup-title": "Usunąć kartę?",
"cardDetailsActionsPopup-title": "Czynności kart",
"cardLabelsPopup-title": "Etykiety",
@@ -309,7 +309,7 @@
"error-board-notAMember": "Musisz być członkiem tej tablicy, żeby wykonać tę czynność",
"error-json-malformed": "Twoja fraza nie jest w formacie JSON",
"error-json-schema": "Twoje dane JSON nie zawierają prawidłowych informacji w poprawnym formacie",
- "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format",
+ "error-csv-schema": "Twój plik CSV(plik oddzielony przecinkami)/TSV (plik oddzielony tabulatorami) nie zawiera danych w poprawnym formacie",
"error-list-doesNotExist": "Ta lista nie isnieje",
"error-user-doesNotExist": "Ten użytkownik nie istnieje",
"error-user-notAllowSelf": "Nie możesz zaprosić samego siebie",
@@ -317,10 +317,10 @@
"error-username-taken": "Ta nazwa jest już zajęta",
"error-email-taken": "Adres email jest już zarezerwowany",
"export-board": "Eksportuj tablicę",
- "export-board-json": "Export board to JSON",
- "export-board-csv": "Export board to CSV",
- "export-board-tsv": "Export board to TSV",
- "export-board-html": "Export board to HTML",
+ "export-board-json": "Eksportuj tablicę do JSON",
+ "export-board-csv": "Eksportuj tablicę do CSV",
+ "export-board-tsv": "Eksportuj tablicę do TSV",
+ "export-board-html": "Eksportuj tablicę do HTML",
"exportBoardPopup-title": "Eksportuj tablicę",
"sort": "Sortuj",
"sort-desc": "Kliknij by sortować listę",
@@ -357,16 +357,16 @@
"import-board-c": "Import tablicy",
"import-board-title-trello": "Importuj tablicę z Trello",
"import-board-title-wekan": "Importuj tablicę z poprzedniego eksportu",
- "import-board-title-csv": "Import board from CSV/TSV",
+ "import-board-title-csv": "Importuj tablicę z CSV/TSV",
"from-trello": "Z Trello",
"from-wekan": "Z poprzedniego eksportu",
- "from-csv": "From CSV/TSV",
+ "from-csv": "Z CSV/TSV",
"import-board-instruction-trello": "W twojej tablicy na Trello przejdź do 'Menu', następnie 'Więcej', 'Drukuj i eksportuj', 'Eksportuj jako JSON' i skopiuj wynik",
- "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .",
+ "import-board-instruction-csv": "Wklej swoje dane w postaci wartości oddzielanych przecinkiem (CSV) lub tabulatorem (TSV)",
"import-board-instruction-wekan": "Na Twojej tablicy przejdź do 'Menu', a następnie wybierz 'Eksportuj tablicę' i skopiuj tekst w pobranym pliku.",
"import-board-instruction-about-errors": "W przypadku, gdy otrzymujesz błędy importowania tablicy, czasami importowanie pomimo wszystko działa poprawnie i tablica znajduje się w oknie Wszystkie tablice.",
"import-json-placeholder": "Wklej Twoje dane JSON tutaj",
- "import-csv-placeholder": "Paste your valid CSV/TSV data here",
+ "import-csv-placeholder": "Wklej swoje dane z pliku CSV/TSV tutaj",
"import-map-members": "Przypisz członków",
"import-members-map": "Twoje zaimportowane tablice mają kilku członków. Proszę wybierz członków których chcesz zaimportować dla Twoich użytkowników",
"import-show-user-mapping": "Przejrzyj wybranych członków",
@@ -399,7 +399,7 @@
"swimlaneActionPopup-title": "Opcje diagramu czynności",
"swimlaneAddPopup-title": "Dodaj diagram czynności poniżej",
"listImportCardPopup-title": "Zaimportuj kartę z Trello",
- "listImportCardsTsvPopup-title": "Import Excel CSV/TSV",
+ "listImportCardsTsvPopup-title": "Importuj CSV/TSV z Excela",
"listMorePopup-title": "Więcej",
"link-list": "Podepnij do tej listy",
"list-delete-pop": "Wszystkie czynności zostaną usunięte z Aktywności i nie będziesz w stanie przywrócić listy. Nie ma możliwości cofnięcia tej operacji.",
@@ -639,7 +639,7 @@
"r-when-a-card": "Gdy karta",
"r-is": "jest",
"r-is-moved": "jest przenoszona",
- "r-added-to": "Added to",
+ "r-added-to": "Dodany do",
"r-removed-from": "usunął z",
"r-the-board": "tablicy",
"r-list": "lista",
@@ -799,12 +799,12 @@
"saturday": "Sobota",
"sunday": "Niedziela",
"status": "Status",
- "swimlane": "Swimlane",
- "owner": "Owner",
- "last-modified-at": "Last modified at",
- "last-activity": "Last activity",
- "voting": "Voting",
- "archived": "Archived",
- "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has",
- "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list"
+ "swimlane": "Diagram czynności",
+ "owner": "Właściciel",
+ "last-modified-at": "Ostatnio zmodyfikowano",
+ "last-activity": "Ostatnia aktywność",
+ "voting": "Głosowanie",
+ "archived": "Zarchiwizowany",
+ "delete-linked-card-before-this-card": "Nie możesz usunąć tej karty, dopóki nie usuniesz podpiętej karty, w której są",
+ "delete-linked-cards-before-this-list": "Nie możesz usunąć tej karty, dopóki nie usuniesz podpiętych kart, które wskazują na karty w tej liście"
}
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",