Merge pull request #3239 from FinnStutzenstein/personalNotePDF

Export personal note and comments in PDFs
This commit is contained in:
Emanuel Schütze 2017-05-19 14:52:31 +02:00 committed by GitHub
commit dda9bb0434
5 changed files with 240 additions and 13 deletions

View File

@ -27,6 +27,7 @@ Motions:
- Added warning message if an edit dialog was already opened by another - Added warning message if an edit dialog was already opened by another
client [#3212]. client [#3212].
- Reworked DOCX export parser and added comments to DOCX [#3258]. - Reworked DOCX export parser and added comments to DOCX [#3258].
- New pdf export for personal note and comments [#3239].
Users: Users:
- User without permission to see users can now see agenda item speakers, - User without permission to see users can now see agenda item speakers,

View File

@ -372,6 +372,167 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
} }
]) ])
.factory('MotionPartialContentProvider', [
'$q',
'gettextCatalog',
'PDFLayout',
'PdfMakeConverter',
'PdfImageConverter',
'HTMLValidizer',
function ($q, gettextCatalog, PDFLayout, PdfMakeConverter, PdfImageConverter, HTMLValidizer) {
/*
* content should be an array of content blocks. Each content is an object providing a
* heading and a text. E.g.
* [{heading: 'comment1', text: '<html in here>'}, {heading: ...}, ...]
* */
var createInstance = function (motion, content) {
var converter;
// Query all image sources from the content
var getImageSources = function () {
var imageSources = [];
_.forEach(content, function (contentBlock) {
var html = HTMLValidizer.validize(contentBlock.text);
imageSources = imageSources.concat(_.map($(html).find('img'), function(element) {
return element.getAttribute('src');
}));
});
return imageSources;
};
// title
var identifier = motion.identifier ? ' ' + motion.identifier : '';
var title = PDFLayout.createTitle(
gettextCatalog.getString('Motion') + identifier + ': ' + motion.getTitle()
);
// subtitle
var subtitleLines = [];
if (motion.parent_id) {
var parentMotion = Motion.get(motion.parent_id);
subtitleLines.push(
gettextCatalog.getString('Amendment of motion') + ': ' +
(parentMotion.identifier ? parentMotion.identifier : parentMotion.getTitle())
);
}
subtitleLines.push(gettextCatalog.getString('Sequential number') + ': ' + motion.id);
var subtitle = PDFLayout.createSubtitle(subtitleLines);
// meta data table
var metaTable = function() {
var metaTableBody = [];
// submitters
var submitters = _.map(motion.submitters, function (submitter) {
return submitter.get_full_name();
}).join(', ');
metaTableBody.push([
{
text: gettextCatalog.getString('Submitters') + ':',
style: ['bold', 'grey'],
},
{
text: submitters,
style: 'grey'
}
]);
// state
metaTableBody.push([
{
text: gettextCatalog.getString('State') + ':',
style: ['bold', 'grey']
},
{
text: motion.getStateName(),
style: 'grey'
}
]);
// recommendation
if (motion.getRecommendationName()) {
metaTableBody.push([
{
text: Config.get('motions_recommendations_by').value + ':',
style: ['bold', 'grey']
},
{
text: motion.getRecommendationName(),
style: 'grey'
}
]);
}
// category
if (motion.category) {
metaTableBody.push([
{
text: gettextCatalog.getString('Category') + ':',
style: ['bold', 'grey'] },
{
text: motion.category.name,
style: 'grey'
}
]);
}
// build table
// Used placeholder for 'layout' functions whiche are
// replaced by lineWitdh/lineColor function in pfd-worker.js.
// TODO: Remove placeholder and us static values for LineWidth and LineColor
// if pdfmake has fixed this.
var metaTableJsonString = {
table: {
widths: ['30%','70%'],
body: metaTableBody,
},
margin: [0, 0, 0, 20],
layout: '{{motion-placeholder-to-insert-functions-here}}'
};
return metaTableJsonString;
};
var getContentBlockData = function (block) {
var data = [];
data.push({
text: block.heading,
style: 'heading3',
marginTop: 25,
});
data.push(converter.convertHTML(block.text));
return data;
};
// Generates content as a pdfmake consumable
var getContent = function() {
var pdfContent = [
title,
subtitle,
metaTable(),
];
_.forEach(content, function (contentBlock) {
pdfContent.push(getContentBlockData(contentBlock));
});
return pdfContent;
};
return $q(function (resolve) {
PdfImageConverter.toBase64(getImageSources()).then(function (imageMap) {
converter = PdfMakeConverter.createInstance(imageMap);
resolve({
getContent: getContent,
});
});
});
};
return {
createInstance: createInstance
};
}
])
.factory('PollContentProvider', [ .factory('PollContentProvider', [
'PDFLayout', 'PDFLayout',
'gettextCatalog', 'gettextCatalog',
@ -630,6 +791,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
.factory('MotionPdfExport', [ .factory('MotionPdfExport', [
'$http', '$http',
'$q', '$q',
'operator',
'Config', 'Config',
'gettextCatalog', 'gettextCatalog',
'MotionChangeRecommendation', 'MotionChangeRecommendation',
@ -640,13 +802,15 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
'PdfMakeDocumentProvider', 'PdfMakeDocumentProvider',
'PollContentProvider', 'PollContentProvider',
'PdfMakeBallotPaperProvider', 'PdfMakeBallotPaperProvider',
'MotionPartialContentProvider',
'PdfCreate', 'PdfCreate',
'PDFLayout', 'PDFLayout',
'Messaging', 'Messaging',
'FileSaver', 'FileSaver',
function ($http, $q, Config, gettextCatalog, MotionChangeRecommendation, HTMLValidizer, PdfMakeConverter, function ($http, $q, operator, Config, gettextCatalog, MotionChangeRecommendation, HTMLValidizer,
MotionContentProvider, MotionCatalogContentProvider, PdfMakeDocumentProvider, PollContentProvider, PdfMakeConverter, MotionContentProvider, MotionCatalogContentProvider, PdfMakeDocumentProvider,
PdfMakeBallotPaperProvider, PdfCreate, PDFLayout, Messaging, FileSaver) { PollContentProvider, PdfMakeBallotPaperProvider, MotionPartialContentProvider, PdfCreate,
PDFLayout, Messaging, FileSaver) {
return { return {
getDocumentProvider: function (motions, params, singleMotion) { getDocumentProvider: function (motions, params, singleMotion) {
params = _.clone(params || {}); // Clone this to avoid sideeffects. params = _.clone(params || {}); // Clone this to avoid sideeffects.
@ -776,6 +940,44 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
var documentProvider = PdfMakeBallotPaperProvider.createInstance(pollContentProvider); var documentProvider = PdfMakeBallotPaperProvider.createInstance(pollContentProvider);
PdfCreate.download(documentProvider.getDocument(), filename); PdfCreate.download(documentProvider.getDocument(), filename);
}, },
exportPersonalNote: function (motion, filename) {
var personalNote = _.find(motion.personal_notes, function (note) {
return note.user_id === operator.user.id;
});
var content = [{
heading: gettextCatalog.getString('Personal note'),
text: personalNote ? personalNote.note : '',
}];
MotionPartialContentProvider.createInstance(motion, content).then(function (contentProvider) {
PdfMakeDocumentProvider.createInstance(contentProvider).then(function (documentProvider) {
PdfCreate.download(documentProvider.getDocument(), filename);
});
});
},
exportComments: function (motion, filename) {
var fields = Config.get('motions_comments').value;
var canSeeComment = function (index) {
return fields[index].public || operator.hasPerms('motions.can_manage');
};
var content = [];
for (var i = 0; i < fields.length; i++) {
if (motion.comments[i] && canSeeComment(i)) {
var title = gettextCatalog.getString('Comment') + ' ' + fields[i].name;
if (!fields[i].public) {
title += ' (' + gettextCatalog.getString('internal') + ')';
}
content.push({
heading: title,
text: motion.comments[i],
});
}
}
MotionPartialContentProvider.createInstance(motion, content).then(function (contentProvider) {
PdfMakeDocumentProvider.createInstance(contentProvider).then(function (documentProvider) {
PdfCreate.download(documentProvider.getDocument(), filename);
});
});
},
}; };
} }
]); ]);

View File

@ -1547,6 +1547,18 @@ angular.module('OpenSlidesApp.motions.site', [
$scope.createPollPdf = function () { $scope.createPollPdf = function () {
MotionPdfExport.createPollPdf($scope.motion, $scope.version); MotionPdfExport.createPollPdf($scope.motion, $scope.version);
}; };
$scope.exportComments = function () {
var identifier = $scope.motion.identifier ? '-' + $scope.motion.identifier : '';
var commentsString = ' - ' + gettextCatalog.getString('Comments');
var filename = gettextCatalog.getString('Motion') + identifier + commentsString + '.pdf';
MotionPdfExport.exportComments($scope.motion, filename);
};
$scope.exportPersonalNote = function () {
var identifier = $scope.motion.identifier ? '-' + $scope.motion.identifier : '';
var personalNoteString = ' - ' + gettextCatalog.getString('personal note');
var filename = gettextCatalog.getString('Motion') + identifier + personalNoteString + '.pdf';
MotionPdfExport.exportPersonalNote($scope.motion, filename);
};
} }
]) ])

View File

@ -2,7 +2,13 @@
<div class="row"> <div class="row">
<!-- inline editing toolbar --> <!-- inline editing toolbar -->
<div class="motion-toolbar"> <div class="motion-toolbar">
<div class="pull-right inline-editing-activator" ng-if="motion.isAllowed('change_comments')"> <div class="pull-right inline-editing-activator">
<button ng-click="exportComments()" class="btn btn-default btn-sm"
uib-tooltip="{{ 'Export comments only' | translate }}">
<i class="fa fa-file-pdf-o"></i>
<translate>PDF</translate>
</button>
<span ng-if="motion.isAllowed('change_comments')">
<button ng-if="!commentsInlineEditing.active()" ng-click="commentsInlineEditing.enable()" <button ng-if="!commentsInlineEditing.active()" ng-click="commentsInlineEditing.enable()"
class="btn btn-sm btn-default"> class="btn btn-sm btn-default">
<i class="fa fa-pencil-square-o"></i> <i class="fa fa-pencil-square-o"></i>
@ -13,6 +19,7 @@
<i class="fa fa-times-circle"></i> <i class="fa fa-times-circle"></i>
<translate>Inline editing</translate> <translate>Inline editing</translate>
</button> </button>
</span>
</div> </div>
<h3 class="toolbar-left" translate>Comments</h3> <h3 class="toolbar-left" translate>Comments</h3>
</div> </div>

View File

@ -3,6 +3,11 @@
<!-- inline editing toolbar --> <!-- inline editing toolbar -->
<div class="motion-toolbar"> <div class="motion-toolbar">
<div class="pull-right inline-editing-activator"> <div class="pull-right inline-editing-activator">
<button ng-click="exportPersonalNote()" class="btn btn-default btn-sm"
uib-tooltip="{{ 'Export personal note only' | translate }}">
<i class="fa fa-file-pdf-o"></i>
<translate>PDF</translate>
</button>
<button ng-if="!personalNoteInlineEditing.active" ng-click="personalNoteInlineEditing.enable()" <button ng-if="!personalNoteInlineEditing.active" ng-click="personalNoteInlineEditing.enable()"
class="btn btn-sm btn-default"> class="btn btn-sm btn-default">
<i class="fa fa-pencil-square-o"></i> <i class="fa fa-pencil-square-o"></i>