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
|
||||
client [#3212].
|
||||
- Reworked DOCX export parser and added comments to DOCX [#3258].
|
||||
- New pdf export for personal note and comments [#3239].
|
||||
|
||||
Users:
|
||||
- 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', [
|
||||
'PDFLayout',
|
||||
'gettextCatalog',
|
||||
@ -630,6 +791,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
|
||||
.factory('MotionPdfExport', [
|
||||
'$http',
|
||||
'$q',
|
||||
'operator',
|
||||
'Config',
|
||||
'gettextCatalog',
|
||||
'MotionChangeRecommendation',
|
||||
@ -640,13 +802,15 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
|
||||
'PdfMakeDocumentProvider',
|
||||
'PollContentProvider',
|
||||
'PdfMakeBallotPaperProvider',
|
||||
'MotionPartialContentProvider',
|
||||
'PdfCreate',
|
||||
'PDFLayout',
|
||||
'Messaging',
|
||||
'FileSaver',
|
||||
function ($http, $q, Config, gettextCatalog, MotionChangeRecommendation, HTMLValidizer, PdfMakeConverter,
|
||||
MotionContentProvider, MotionCatalogContentProvider, PdfMakeDocumentProvider, PollContentProvider,
|
||||
PdfMakeBallotPaperProvider, PdfCreate, PDFLayout, Messaging, FileSaver) {
|
||||
function ($http, $q, operator, Config, gettextCatalog, MotionChangeRecommendation, HTMLValidizer,
|
||||
PdfMakeConverter, MotionContentProvider, MotionCatalogContentProvider, PdfMakeDocumentProvider,
|
||||
PollContentProvider, PdfMakeBallotPaperProvider, MotionPartialContentProvider, PdfCreate,
|
||||
PDFLayout, Messaging, FileSaver) {
|
||||
return {
|
||||
getDocumentProvider: function (motions, params, singleMotion) {
|
||||
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);
|
||||
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 () {
|
||||
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,17 +2,24 @@
|
||||
<div class="row">
|
||||
<!-- inline editing toolbar -->
|
||||
<div class="motion-toolbar">
|
||||
<div class="pull-right inline-editing-activator" ng-if="motion.isAllowed('change_comments')">
|
||||
<button ng-if="!commentsInlineEditing.active()" ng-click="commentsInlineEditing.enable()"
|
||||
class="btn btn-sm btn-default">
|
||||
<i class="fa fa-pencil-square-o"></i>
|
||||
<translate>Inline editing</translate>
|
||||
</button>
|
||||
<button ng-if="commentsInlineEditing.active()" ng-click="commentsInlineEditing.disable()"
|
||||
class="btn btn-sm btn-default">
|
||||
<i class="fa fa-times-circle"></i>
|
||||
<translate>Inline editing</translate>
|
||||
<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()"
|
||||
class="btn btn-sm btn-default">
|
||||
<i class="fa fa-pencil-square-o"></i>
|
||||
<translate>Inline editing</translate>
|
||||
</button>
|
||||
<button ng-if="commentsInlineEditing.active()" ng-click="commentsInlineEditing.disable()"
|
||||
class="btn btn-sm btn-default">
|
||||
<i class="fa fa-times-circle"></i>
|
||||
<translate>Inline editing</translate>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<h3 class="toolbar-left" translate>Comments</h3>
|
||||
</div>
|
||||
|
@ -3,6 +3,11 @@
|
||||
<!-- inline editing toolbar -->
|
||||
<div class="motion-toolbar">
|
||||
<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()"
|
||||
class="btn btn-sm btn-default">
|
||||
<i class="fa fa-pencil-square-o"></i>
|
||||
|
Loading…
Reference in New Issue
Block a user