diff --git a/CHANGELOG b/CHANGELOG
index 118efc367..4fb48f183 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -17,6 +17,7 @@ Motions:
- Fixed empty motion comment field in motion update form [#3194].
- Removed server side image to base64 transformation and
added local transformation [#2705]
+- Added support for export motions in a zip archive [#3189].
Core:
- No reload on logoff. OpenSlides is now a full single page
diff --git a/bower.json b/bower.json
index 04bf56eb6..94b64c79a 100644
--- a/bower.json
+++ b/bower.json
@@ -28,6 +28,7 @@
"jquery.cookie": "~1.4.1",
"js-data": "~2.9.0",
"js-data-angular": "~3.2.1",
+ "jszip": "~3.1.3",
"lodash": "~4.16.0",
"ng-dialog": "~0.6.4",
"ng-file-upload": "~11.2.3",
diff --git a/openslides/core/static/js/core/pdf.js b/openslides/core/static/js/core/pdf.js
index e518607e4..45cd8bbda 100644
--- a/openslides/core/static/js/core/pdf.js
+++ b/openslides/core/static/js/core/pdf.js
@@ -859,10 +859,11 @@ angular.module('OpenSlidesApp.core.pdf', [])
.factory('PdfCreate', [
'$timeout',
+ '$q',
'gettextCatalog',
'FileSaver',
'Messaging',
- function ($timeout, gettextCatalog, FileSaver, Messaging) {
+ function ($timeout, $q, gettextCatalog, FileSaver, Messaging) {
var filenameMessageMap = {};
var b64toBlob = function(b64Data) {
var byteCharacters = atob(b64Data);
@@ -898,19 +899,28 @@ angular.module('OpenSlidesApp.core.pdf', [])
}, 1);
};
return {
+ getBase64FromDocument: function (pdfDocument) {
+ return $q(function (resolve, reject) {
+ var pdfWorker = new Worker('/static/js/workers/pdf-worker.js');
+ pdfWorker.addEventListener('message', function (event) {
+ resolve(event.data);
+ });
+ pdfWorker.addEventListener('error', function (event) {
+ reject(event);
+ });
+ pdfWorker.postMessage(JSON.stringify(pdfDocument));
+ });
+ },
download: function (pdfDocument, filename) {
stateChange('info', filename);
- var pdfWorker = new Worker('/static/js/workers/pdf-worker.js');
- pdfWorker.addEventListener('message', function (event) {
- var blob = b64toBlob(event.data);
+ this.getBase64FromDocument(pdfDocument).then(function (data) {
+ var blob = b64toBlob(data);
stateChange('success', filename);
FileSaver.saveAs(blob, filename);
+ }, function (error) {
+ stateChange('error', filename, error.message);
});
- pdfWorker.addEventListener('error', function (event) {
- stateChange('error', filename, event.message);
- });
- pdfWorker.postMessage(JSON.stringify(pdfDocument));
},
};
}
diff --git a/openslides/motions/static/js/motions/pdf.js b/openslides/motions/static/js/motions/pdf.js
index 144178b0e..e11b196c9 100644
--- a/openslides/motions/static/js/motions/pdf.js
+++ b/openslides/motions/static/js/motions/pdf.js
@@ -288,7 +288,6 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
for (var i = 0; i < fields.length; i++) {
if (motion.comments[i] && canSeeComment(i)) {
var title = gettextCatalog.getString('Comment') + ' ' + fields[i].name;
- console.log(fields[i]);
if (!fields[i].public) {
title += ' (' + gettextCatalog.getString('internal') + ')';
}
@@ -602,6 +601,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
.factory('MotionPdfExport', [
'$http',
+ '$q',
'Config',
'gettextCatalog',
'MotionChangeRecommendation',
@@ -614,15 +614,14 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
'PdfMakeBallotPaperProvider',
'PdfCreate',
'PDFLayout',
- '$q',
- function ($http, Config, gettextCatalog, MotionChangeRecommendation, HTMLValidizer, PdfMakeConverter,
+ 'Messaging',
+ 'FileSaver',
+ function ($http, $q, Config, gettextCatalog, MotionChangeRecommendation, HTMLValidizer, PdfMakeConverter,
MotionContentProvider, MotionCatalogContentProvider, PdfMakeDocumentProvider, PollContentProvider,
- PdfMakeBallotPaperProvider, PdfCreate, PDFLayout, $q) {
+ PdfMakeBallotPaperProvider, PdfCreate, PDFLayout, Messaging, FileSaver) {
return {
- export: function (motions, params, singleMotion) {
- if (!params) {
- params = {};
- }
+ getDocumentProvider: function (motions, params, singleMotion) {
+ params = _.clone(params || {}); // Clone this to avoid sideeffects.
_.defaults(params, {
filename: gettextCatalog.getString('motions') + '.pdf',
changeRecommendationMode: Config.get('motions_recommendation_text_mode').value,
@@ -643,11 +642,11 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
angular.forEach(motions, function (motion) {
if (singleMotion) {
motion.changeRecommendations = MotionChangeRecommendation.filter({
- 'where': {'motion_version_id': {'==': motion.active_version}}
+ 'where': {'motion_version_id': {'==': params.version}}
});
} else {
motion.changeRecommendations = MotionChangeRecommendation.filter({
- 'where': {'motion_version_id': {'==': params.version}}
+ 'where': {'motion_version_id': {'==': motion.active_version}}
});
}
var text = motion.getTextByMode(params.changeRecommendationMode, null);
@@ -659,43 +658,93 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
image_sources = image_sources.concat(tmp_image_sources);
});
- var image_map = {};
- _.forEach(image_sources, function (image_source) {
- image_map[image_source] = PDFLayout.imageURLtoBase64(image_source);
+ var imageMap = {};
+ var imagePromises = _.map(image_sources, function (image_source) {
+ return PDFLayout.imageURLtoBase64(image_source).then(function (base64Str) {
+ imageMap[image_source] = base64Str;
+ });
});
- var image_promises = Object.keys(image_map).map(function( key ) {
- return image_map[key];
+ return $q(function (resolve) {
+ //resolve promises to get base64
+ $q.all(imagePromises).then(function(base64Str) {
+ var converter = PdfMakeConverter.createInstance(imageMap);
+ var motionContentProviderArray = [];
+
+ //convert all motions to motionContentProviders
+ angular.forEach(motions, function (motion) {
+ var version = (singleMotion ? params.version : motion.active_version);
+ motionContentProviderArray.push(MotionContentProvider.createInstance(
+ converter, motion, version, params.changeRecommendationMode,
+ motion.changeRecommendations, params.lineNumberMode,
+ params.includeReason, params.includeComments
+ ));
+ });
+
+ var documentProvider;
+ if (singleMotion) {
+ documentProvider = PdfMakeDocumentProvider.createInstance(motionContentProviderArray[0]);
+ } else {
+ var motionCatalogContentProvider = MotionCatalogContentProvider.createInstance(motionContentProviderArray);
+ documentProvider = PdfMakeDocumentProvider.createInstance(motionCatalogContentProvider);
+ }
+
+ resolve(documentProvider);
+ });
});
-
- //resolv promises to get base64
- $q.all(image_promises).then(function(base64Str) {
- Object.keys(image_map).map(function(key, i) {
- image_map[key] = base64Str[i];
- });
-
- var converter = PdfMakeConverter.createInstance(image_map);
- var motionContentProviderArray = [];
-
- //convert all motions to motionContentProviders
- angular.forEach(motions, function (motion) {
- var version = (singleMotion ? params.version : motion.active_version);
- motionContentProviderArray.push(MotionContentProvider.createInstance(
- converter, motion, version, params.changeRecommendationMode,
- motion.changeRecommendations, params.lineNumberMode,
- params.includeReason, params.includeComments
- ));
- });
-
- var documentProvider;
- if (singleMotion) {
- documentProvider = PdfMakeDocumentProvider.createInstance(motionContentProviderArray[0]);
- } else {
- var motionCatalogContentProvider = MotionCatalogContentProvider.createInstance(motionContentProviderArray);
- documentProvider = PdfMakeDocumentProvider.createInstance(motionCatalogContentProvider);
+ },
+ export: function (motions, params, singleMotion) {
+ _.defaults(params, {
+ filename: gettextCatalog.getString('motions') + '.pdf',
+ });
+ this.getDocumentProvider(motions, params, singleMotion).then(
+ function (documentProvider) {
+ PdfCreate.download(documentProvider.getDocument(), params.filename);
}
+ );
+ },
+ exportZip: function (motions, params) {
+ var messageId = Messaging.addMessage('' +
+ gettextCatalog.getString('Generating PDFs and ZIP archive') + ' ...', 'info');
+ var zipFilename = params.filename || gettextCatalog.getString('motions') + '.zip';
+ params.filename = void 0; // clear this, so we do not override the default filenames for each pdf.
- PdfCreate.download(documentProvider.getDocument(), params.filename);
+ var self = this;
+ var pdfs = {};
+ var pdfPromises = _.map(motions, function (motion) {
+ var identifier = motion.identifier ? '-' + motion.identifier : '';
+ var filename = gettextCatalog.getString('Motion') + identifier + '.pdf';
+
+ return $q(function (resolve, reject) {
+ // get documentProvider for every motion.
+ self.getDocumentProvider(motion, params, true).then(function (documentProvider) {
+ var doc = documentProvider.getDocument();
+
+ PdfCreate.getBase64FromDocument(doc).then(function (data) {
+ pdfs[filename] = data;
+ resolve();
+ }, function (error) {
+ reject(error);
+ });
+ });
+ });
+ });
+
+ // Wait for all documents to be generated. Then put them into a zip and download it.
+ $q.all(pdfPromises).then(function () {
+ var zip = new JSZip();
+ _.forEach(pdfs, function (data, filename) {
+ zip.file(filename, data, {base64: true});
+ });
+ Messaging.createOrEditMessage(messageId, '' +
+ gettextCatalog.getString('ZIP successfully generated.'), 'success', {timeout: 3000});
+ zip.generateAsync({type: 'blob'}).then(function (content) {
+ FileSaver.saveAs(content, zipFilename);
+ });
+ }, function (error) {
+ Messaging.createOrEditMessage(messageId, '' + gettextCatalog.getString('Error while generating ZIP file') +
+ ': ' + error + '
', 'error');
});
},
createPollPdf: function (motion, version) {
diff --git a/openslides/motions/static/js/motions/site.js b/openslides/motions/static/js/motions/site.js
index a500bdc8c..d06d080f8 100644
--- a/openslides/motions/static/js/motions/site.js
+++ b/openslides/motions/static/js/motions/site.js
@@ -733,9 +733,23 @@ angular.module('OpenSlidesApp.motions.site', [
],
},
hideExpression: "model.format !== 'pdf'",
- });
+ });
}
}
+ if (!singleMotion) {
+ fields.push({
+ key: 'pdfFormat',
+ type: 'select-radio',
+ templateOptions: {
+ label: gettextCatalog.getString('PDF format'),
+ options: [
+ {name: gettextCatalog.getString('One PDF'), value: 'pdf'},
+ {name: gettextCatalog.getString('Multiple PDFs in a zip arcive'), value: 'zip'},
+ ],
+ },
+ hideExpression: "model.format !== 'pdf'",
+ });
+ }
return fields;
},
};
@@ -761,6 +775,7 @@ angular.module('OpenSlidesApp.motions.site', [
$scope.params = params || {};
_.defaults($scope.params, {
format: 'pdf',
+ pdfFormat: 'pdf',
changeRecommendationMode: Config.get('motions_recommendation_text_mode').value,
lineNumberMode: Config.get('motions_default_line_numbering').value,
includeReason: true,
@@ -772,7 +787,11 @@ angular.module('OpenSlidesApp.motions.site', [
$scope.export = function () {
switch ($scope.params.format) {
case 'pdf':
- MotionPdfExport.export(motions, $scope.params, singleMotion);
+ if ($scope.params.pdfFormat === 'pdf') {
+ MotionPdfExport.export(motions, $scope.params, singleMotion);
+ } else {
+ MotionPdfExport.exportZip(motions, $scope.params);
+ }
break;
case 'csv':
MotionCsvExport.export(motions, $scope.params);