Merge pull request #3239 from FinnStutzenstein/personalNotePDF
Export personal note and comments in PDFs
This commit is contained in:
commit
dda9bb0434
@ -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,
|
||||||
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
@ -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);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user