Extended motion export dialog (fixes #3493)

- Cleanup formfield types
- Remove complete columns in the csv
This commit is contained in:
FinnStutzenstein 2017-11-17 11:25:42 +01:00 committed by Emanuel Schütze
parent 4f9b1e178f
commit 79e6e31229
9 changed files with 448 additions and 193 deletions

View File

@ -788,8 +788,9 @@ angular.module('OpenSlidesApp.core.site', [
overwriteOk: true, overwriteOk: true,
}); });
formlyConfig.setType({ formlyConfig.setType({
name: 'select-radio', name: 'checkbox-buttons',
templateUrl: 'static/templates/core/select-radio.html', templateUrl: 'static/templates/core/checkbox-buttons.html',
overwriteOk: true,
}); });
formlyConfig.setType({ formlyConfig.setType({
name: 'select-single', name: 'select-single',

View File

@ -0,0 +1,14 @@
<div class="form-group">
<div>
<label class="control-label" ng-if="to.label">
{{ to.label }}
</label>
</div>
<div class="btn-group">
<label ng-repeat="option in to.options" class="btn btn-default btn-sm" uib-btn-checkbox
ng-model="model[options.key][option.id]"
ng-disabled="option.disabled">
{{ option.name | translate }}
</label>
</div>
</div>

View File

@ -1,6 +1,17 @@
<div class="btn-group" data-toggle="buttons"> <div class="form-group">
<label ng-repeat="(key, option) in to.options" ng-click="model[options.key] = option.value;" <div ng-if="to.label">
class="btn btn-default btn-sm" ng-class="{active: (model[options.key] == option.value)}"> <label class="control-label">
<input type="radio" tabindex="0" ng-value="option.value" ng-model="model[options.key]">{{option.name}} {{ to.label | translate }}
</label> </label>
</div> </div>
<div class="btn-group">
<label ng-repeat="option in to.options" class="btn btn-default btn-sm"
ng-class="{active: (model[options.key] === option.value)}"
ng-disabled="option.disabled">
<input type="radio" ng-value="option.value"
ng-model="model[options.key]" ng-checked="model[options.key] === option.value"
ng-disabled="option.disabled" ng-change="to.change(option.value)">
{{ option.name | translate }}
</label>
</div>
</div>

View File

@ -1,17 +0,0 @@
<div class="form-group">
<div>
<label class="control-label" ng-if="to.label">
{{ to.label }}
</label>
</div>
<div class="btn-group">
<label ng-repeat="option in to.options" class="btn btn-default btn-sm"
ng-class="{active: (model[options.key] === option.value)}"
ng-disabled="option.disabled">
<input type="radio" name="select-radio-{{ options.key }}" ng-value="option.value"
ng-model="model[options.key]" ng-checked="model[options.key] === option.value"
ng-disabled="option.disabled" ng-change="to.change(option.value)">
{{ option.name | translate }}
</label>
</div>
</div>

View File

@ -10,8 +10,30 @@ angular.module('OpenSlidesApp.motions.csv', [])
'CsvDownload', 'CsvDownload',
'lineNumberingService', 'lineNumberingService',
function (gettextCatalog, Config, CsvDownload, lineNumberingService) { function (gettextCatalog, Config, CsvDownload, lineNumberingService) {
var makeHeaderline = function () { var makeHeaderline = function (params) {
var headerline = ['Identifier', 'Title', 'Text', 'Reason', 'Submitter', 'Category', 'Origin']; var headerline = ['Identifier', 'Title'];
if (params.include.text) {
headerline.push('Text');
}
if (params.include.reason) {
headerline.push('Reason');
}
if (params.include.submitters) {
headerline.push('Submitter');
}
headerline.push('Category');
if (params.include.origin) {
headerline.push('Origin');
}
if (params.include.motionBlock) {
headerline.push('Motion block');
}
if (params.include.state) {
headerline.push('State');
}
if (params.include.recommendation) {
headerline.push('Recommendation');
}
return _.map(headerline, function (entry) { return _.map(headerline, function (entry) {
return gettextCatalog.getString(entry); return gettextCatalog.getString(entry);
}); });
@ -24,41 +46,89 @@ angular.module('OpenSlidesApp.motions.csv', [])
_.defaults(params, { _.defaults(params, {
filename: 'motions-export.csv', filename: 'motions-export.csv',
changeRecommendationMode: Config.get('motions_recommendation_text_mode').value, changeRecommendationMode: Config.get('motions_recommendation_text_mode').value,
includeReason: true, include: {
text: true,
reason: true,
submitters: true,
origin: true,
motionBlock: true,
state: true,
recommendation: true,
},
}); });
if (!_.includes(['original', 'changed', 'agreed'], params.changeRecommendationMode)) { if (!_.includes(['original', 'changed', 'agreed'], params.changeRecommendationMode)) {
params.changeRecommendationMode = 'original'; params.changeRecommendationMode = 'original';
} }
var csvRows = [ var csvRows = [
makeHeaderline() makeHeaderline(params)
]; ];
_.forEach(motions, function (motion) { _.forEach(motions, function (motion) {
var text = motion.getTextByMode(params.changeRecommendationMode, null, null, false); var text = motion.getTextByMode(params.changeRecommendationMode, null, null, false);
var row = []; var row = [];
// Identifier and title
row.push('"' + motion.identifier !== null ? motion.identifier : '' + '"'); row.push('"' + motion.identifier !== null ? motion.identifier : '' + '"');
row.push('"' + motion.getTitle() + '"'); row.push('"' + motion.getTitle() + '"');
// Text
if (params.include.text) {
row.push('"' + text + '"'); row.push('"' + text + '"');
if (params.includeReason) {
row.push('"' + motion.getReason() + '"');
} else {
row.push('""');
} }
var submitter = motion.submitters[0] ? motion.submitters[0].get_full_name() : '';
row.push('"' + submitter + '"'); // Reason
if (params.include.reason) {
row.push('"' + motion.getReason() + '"');
}
// Submitters
if (params.include.submitters) {
var submitters = motion.submitters[0] ? motion.submitters[0].get_full_name() : '';
row.push('"' + submitters + '"');
}
// Category
var category = motion.category ? motion.category.name : ''; var category = motion.category ? motion.category.name : '';
row.push('"' + category + '"'); row.push('"' + category + '"');
// Origin
if (params.include.origin) {
row.push('"' + motion.origin + '"'); row.push('"' + motion.origin + '"');
}
// Motion block
if (params.include.motionBlock) {
var blockTitle = motion.motionBlock ? motion.motionBlock.title : '';
row.push('"' + blockTitle + '"');
}
// State
if (params.include.state) {
row.push('"' + motion.getStateName() + '"');
}
// Recommendation
if (params.include.recommendation) {
row.push('"' + motion.getRecommendationName() + '"');
}
csvRows.push(row); csvRows.push(row);
}); });
CsvDownload(csvRows, 'motions-export.csv'); CsvDownload(csvRows, 'motions-export.csv');
}, },
downloadExample: function () { downloadExample: function () {
var csvRows = [makeHeaderline(), var csvRows = [makeHeaderline({ include: {
text: true,
reason: true,
submitters: true,
origin: true,
motionBlock: true,
state: true,
recommendation: true,
}}),
// example entries // example entries
['A1', 'Title 1', 'Text 1', 'Reason 1', 'Submitter A', 'Category A', 'Last Year Conference A'], ['A1', 'Title 1', 'Text 1', 'Reason 1', 'Submitter A', 'Category A', 'Last Year Conference A', 'Block A', 'permitted', ''],
['B1', 'Title 2', 'Text 2', 'Reason 2', 'Submitter B', 'Category B', ''], ['B1', 'Title 2', 'Text 2', 'Reason 2', 'Submitter B', 'Category B', '', 'Block A', 'accepted', 'Acceptance'],
['' , 'Title 3', 'Text 3', '', '', '', ''], ['' , 'Title 3', 'Text 3', '', '', '', '', '', '', ''],
]; ];
CsvDownload(csvRows, 'motions-example.csv'); CsvDownload(csvRows, 'motions-example.csv');
}, },

View File

@ -97,9 +97,9 @@ angular.module('OpenSlidesApp.motions.docx', ['OpenSlidesApp.core.docx'])
var sequential_enabled = Config.get('motions_export_sequential_number').value; var sequential_enabled = Config.get('motions_export_sequential_number').value;
// promises for create the actual motion data // promises for create the actual motion data
var promises = _.map(motions, function (motion) { var promises = _.map(motions, function (motion) {
var text = motion.getTextByMode(params.changeRecommendationMode, null, null, false); var text = params.include.text ? motion.getTextByMode(params.changeRecommendationMode, null, null, false) : '';
var reason = params.includeReason ? motion.getReason() : ''; var reason = params.include.reason ? motion.getReason() : '';
var comments = params.includeComments ? getMotionComments(motion) : []; var comments = getMotionComments(motion, params.includeComments);
// Data for one motions. Must include translations, ... // Data for one motions. Must include translations, ...
var motionData = { var motionData = {
@ -115,9 +115,9 @@ angular.module('OpenSlidesApp.motions.docx', ['OpenSlidesApp.core.docx'])
id: motion.id, id: motion.id,
identifier: motion.identifier, identifier: motion.identifier,
title: motion.getTitle(), title: motion.getTitle(),
submitters: _.map(motion.submitters, function (submitter) { submitters: params.include.submitters ? _.map(motion.submitters, function (submitter) {
return submitter.get_full_name(); return submitter.get_full_name();
}).join(', '), }).join(', ') : '',
status: motion.getStateName(), status: motion.getStateName(),
// Miscellaneous stuff // Miscellaneous stuff
preamble: gettextCatalog.getString(Config.get('motions_preamble').value), preamble: gettextCatalog.getString(Config.get('motions_preamble').value),
@ -154,13 +154,13 @@ angular.module('OpenSlidesApp.motions.docx', ['OpenSlidesApp.core.docx'])
}); });
}; };
var getMotionComments = function (motion) { var getMotionComments = function (motion, fieldsIncluded) {
var fields = MotionComment.getNoSpecialCommentsFields(); var fields = MotionComment.getNoSpecialCommentsFields();
var comments = []; var comments = [];
_.forEach(fields, function (field, id) { _.forEach(fieldsIncluded, function (ok, id) {
if (motion.comments[id]) { if (ok && motion.comments[id]) {
var title = gettextCatalog.getString('Comment') + ' ' + field.name; var title = fields[id].name;
if (!field.public) { if (!fields[id].public) {
title += ' (' + gettextCatalog.getString('internal') + ')'; title += ' (' + gettextCatalog.getString('internal') + ')';
} }
var comment = motion.comments[id]; var comment = motion.comments[id];
@ -179,14 +179,16 @@ angular.module('OpenSlidesApp.motions.docx', ['OpenSlidesApp.core.docx'])
return { return {
export: function (motions, params) { export: function (motions, params) {
converter = Html2DocxConverter.createInstance(); converter = Html2DocxConverter.createInstance();
if (!params) { params = _.clone(params || {}); // Clone this to avoid sideeffects.
params = {};
}
_.defaults(params, { _.defaults(params, {
filename: 'motions-export.docx', filename: 'motions-export.docx',
changeRecommendationMode: Config.get('motions_recommendation_text_mode').value, changeRecommendationMode: Config.get('motions_recommendation_text_mode').value,
includeReason: true, include: {
includeComments: false, text: true,
reason: true,
submitters: true,
},
includeComments: {},
}); });
if (!_.includes(['original', 'changed', 'agreed'], params.changeRecommendationMode)) { if (!_.includes(['original', 'changed', 'agreed'], params.changeRecommendationMode)) {
params.changeRecommendationMode = 'original'; params.changeRecommendationMode = 'original';

View File

@ -23,24 +23,36 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
* @constructor * @constructor
*/ */
var createInstance = function(motion, motionVersion, changeRecommendationMode, var createInstance = function(motion, motionVersion, params) {
changeRecommendations, lineNumberMode, includeReason, includeComments) { params = _.clone(params || {}); // Clone this to avoid sideeffects.
_.defaults(params, {
changeRecommendationMode: Config.get('motions_recommendation_text_mode').value,
lineNumberMode: Config.get('motions_default_line_numbering').value,
include: {
text: true,
reason: true,
state: true,
submitters: true,
votingresult: true,
motionBlock: true,
origin: true,
recommendation: true,
},
includeComments: {},
});
var converter; var converter;
// Query all image sources from motion text and reason // Query all image sources from motion text and reason
var getImageSources = function () { var getImageSources = function () {
var text = motion.getTextByMode(changeRecommendationMode, null); var text = motion.getTextByMode(params.changeRecommendationMode, null);
var reason = motion.getReason(); var reason = motion.getReason();
var comments = ''; var comments = '';
if (includeComments) { _.forEach(params.includeComments, function (ok, id) {
var fields = MotionComment.getNoSpecialCommentsFields(); if (ok && motion.comments[id]) {
_.forEach(fields, function (field, id) {
if (motion.comments[id]) {
comments += HTMLValidizer.validize(motion.comments[id]); comments += HTMLValidizer.validize(motion.comments[id]);
} }
}); });
}
var content = HTMLValidizer.validize(text) + HTMLValidizer.validize(motion.getReason()) + comments; var content = HTMLValidizer.validize(text) + HTMLValidizer.validize(motion.getReason()) + comments;
var map = Function.prototype.call.bind([].map); var map = Function.prototype.call.bind([].map);
return map($(content).find('img'), function(element) { return map($(content).find('img'), function(element) {
@ -77,6 +89,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
var submitters = _.map(motion.submitters, function (submitter) { var submitters = _.map(motion.submitters, function (submitter) {
return submitter.get_full_name(); return submitter.get_full_name();
}).join(', '); }).join(', ');
if (params.include.submitters) {
metaTableBody.push([ metaTableBody.push([
{ {
text: gettextCatalog.getString('Submitters') + ':', text: gettextCatalog.getString('Submitters') + ':',
@ -87,8 +100,10 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
style: 'grey' style: 'grey'
} }
]); ]);
}
// state // state
if (params.include.state) {
metaTableBody.push([ metaTableBody.push([
{ {
text: gettextCatalog.getString('State') + ':', text: gettextCatalog.getString('State') + ':',
@ -99,9 +114,10 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
style: 'grey' style: 'grey'
} }
]); ]);
}
// recommendation // recommendation
if (motion.getRecommendationName()) { if (params.include.recommendation && motion.getRecommendationName()) {
metaTableBody.push([ metaTableBody.push([
{ {
text: Config.get('motions_recommendations_by').value + ':', text: Config.get('motions_recommendations_by').value + ':',
@ -128,7 +144,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
} }
// motion block // motion block
if (motion.motionBlock) { if (params.include.motionBlock && motion.motionBlock) {
metaTableBody.push([ metaTableBody.push([
{ {
text: gettextCatalog.getString('Motion block') + ':', text: gettextCatalog.getString('Motion block') + ':',
@ -139,8 +155,22 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
} }
]); ]);
} }
// origin
if (params.include.origin && motion.origin) {
metaTableBody.push([
{
text: gettextCatalog.getString('Origin') + ':',
style: ['bold', 'grey'] },
{
text: motion.origin,
style: 'grey'
}
]);
}
// voting result // voting result
if (motion.polls.length > 0 && motion.polls[0].has_votes) { if (params.include.votingresult && motion.polls.length > 0 && motion.polls[0].has_votes) {
var column1 = []; var column1 = [];
var column2 = []; var column2 = [];
var column3 = []; var column3 = [];
@ -219,10 +249,10 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
} }
// summary of change recommendations (for motion diff version only) // summary of change recommendations (for motion diff version only)
if (changeRecommendationMode == "diff" && changeRecommendations.length) { if (params.changeRecommendationMode == "diff" && motion.changeRecommendations.length) {
var columnLineNumbers = []; var columnLineNumbers = [];
var columnChangeType = []; var columnChangeType = [];
angular.forEach(_.orderBy(changeRecommendations, ['line_from']), function(change) { angular.forEach(_.orderBy(motion.changeRecommendations, ['line_from']), function(change) {
// line numbers column // line numbers column
var line; var line;
if (change.line_from >= change.line_to - 1) { if (change.line_from >= change.line_to - 1) {
@ -264,12 +294,13 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
]); ]);
} }
if (metaTableBody.length) {
// build table // build table
// Used placeholder for 'layout' functions whiche are // Used placeholder for 'layout' functions whiche are
// replaced by lineWitdh/lineColor function in pfd-worker.js. // replaced by lineWitdh/lineColor function in pfd-worker.js.
// TODO: Remove placeholder and us static values for LineWidth and LineColor // TODO: Remove placeholder and us static values for LineWidth and LineColor
// if pdfmake has fixed this. // if pdfmake has fixed this.
var metaTableJsonString = { var metaTable = {
table: { table: {
widths: ['35%','65%'], widths: ['35%','65%'],
body: metaTableBody, body: metaTableBody,
@ -277,34 +308,43 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
margin: [0, 0, 0, 20], margin: [0, 0, 0, 20],
layout: '{{motion-placeholder-to-insert-functions-here}}' layout: '{{motion-placeholder-to-insert-functions-here}}'
}; };
return metaTableJsonString; return metaTable;
} else {
return {};
}
}; };
// motion title // motion title
var motionTitle = function() { var motionTitle = function() {
if (params.include.text) {
return [{ return [{
text: motion.getTitle(motionVersion), text: motion.getTitle(motionVersion),
style: 'heading3' style: 'heading3'
}]; }];
}
}; };
// motion preamble // motion preamble
var motionPreamble = function () { var motionPreamble = function () {
if (params.include.text) {
return { return {
text: Config.translate(Config.get('motions_preamble').value), text: Config.translate(Config.get('motions_preamble').value),
margin: [0, 10, 0, 0] margin: [0, 10, 0, 0]
}; };
}
}; };
// motion text (with line-numbers) // motion text (with line-numbers)
var motionText = function() { var motionText = function() {
var motionTextContent = motion.getTextByMode(changeRecommendationMode, motionVersion); if (params.include.text) {
return converter.convertHTML(motionTextContent, lineNumberMode); var motionTextContent = motion.getTextByMode(params.changeRecommendationMode, motionVersion);
return converter.convertHTML(motionTextContent, params.lineNumberMode);
}
}; };
// motion reason heading // motion reason heading
var motionReason = function() { var motionReason = function() {
if (includeReason) { if (params.include.reason) {
var reason = []; var reason = [];
if (motion.getReason(motionVersion)) { if (motion.getReason(motionVersion)) {
reason.push({ reason.push({
@ -327,13 +367,13 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
// motion comments handling // motion comments handling
var motionComments = function () { var motionComments = function () {
if (includeComments) { if (_.keys(params.includeComments).length !== 0) {
var fields = MotionComment.getNoSpecialCommentsFields(); var fields = MotionComment.getNoSpecialCommentsFields();
var comments = []; var comments = [];
_.forEach(fields, function (field, id) { _.forEach(params.includeComments, function (ok, id) {
if (motion.comments[id]) { if (ok && motion.comments[id]) {
var title = field.name; var title = fields[id].name;
if (!field.public) { if (!fields[id].public) {
title += ' (' + gettextCatalog.getString('internal') + ')'; title += ' (' + gettextCatalog.getString('internal') + ')';
} }
comments.push({ comments.push({
@ -895,13 +935,6 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
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.
_.defaults(params, {
filename: gettextCatalog.getString('motions') + '.pdf',
changeRecommendationMode: Config.get('motions_recommendation_text_mode').value,
lineNumberMode: Config.get('motions_default_line_numbering').value,
includeReason: true,
includeComments: false,
});
if (singleMotion) { if (singleMotion) {
_.defaults(params, { _.defaults(params, {
@ -927,9 +960,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
var motionContentProviderPromises = _.map(motions, function (motion) { var motionContentProviderPromises = _.map(motions, function (motion) {
var version = (singleMotion ? params.version : motion.active_version); var version = (singleMotion ? params.version : motion.active_version);
return MotionContentProvider.createInstance( return MotionContentProvider.createInstance(
motion, version, params.changeRecommendationMode, motion, version, params
motion.changeRecommendations, params.lineNumberMode,
params.includeReason, params.includeComments
).then(function (contentProvider) { ).then(function (contentProvider) {
motionContentProviderArray.push(contentProvider); motionContentProviderArray.push(contentProvider);
}); });

View File

@ -242,7 +242,7 @@ angular.module('OpenSlidesApp.motions.site', [
key: 'type', key: 'type',
type: 'radio-buttons', type: 'radio-buttons',
templateOptions: { templateOptions: {
name: 'type', label: 'Type',
options: [ options: [
{name: gettextCatalog.getString('Replacement'), value: 0}, {name: gettextCatalog.getString('Replacement'), value: 0},
{name: gettextCatalog.getString('Insertion'), value: 1}, {name: gettextCatalog.getString('Insertion'), value: 1},
@ -618,7 +618,9 @@ angular.module('OpenSlidesApp.motions.site', [
'operator', 'operator',
'gettextCatalog', 'gettextCatalog',
'Config', 'Config',
function (operator, gettextCatalog, Config) { 'MotionComment',
function (operator, gettextCatalog, Config, MotionComment) {
var noSpecialCommentsFields = MotionComment.getNoSpecialCommentsFields();
return { return {
getDialog: function (motions, params, singleMotion) { getDialog: function (motions, params, singleMotion) {
return { return {
@ -634,14 +636,46 @@ angular.module('OpenSlidesApp.motions.site', [
}, },
}; };
}, },
getFormFields: function (singleMotion, formatChangeCallback) { getFormFields: function (singleMotion, motions, formatChangeCallback) {
var fields = []; var fields = [];
var commentsAvailable = Config.get('motions_comments').value.length !== 0; var commentsAvailable = _.keys(noSpecialCommentsFields).length !== 0;
var getMetaInformationOptions = function (disabled) {
if (!disabled) {
disabled = {};
}
var options = [
{name: gettextCatalog.getString('State'), id: 'state', disabled: disabled.state},
{name: gettextCatalog.getString('Submitters'), id: 'submitters', disabled: disabled.submitters},
{name: gettextCatalog.getString('Voting result'), id: 'votingresult', disabled: disabled.votingResult}
];
if (_.some(motions, function (motion) { return motion.motionBlock; })) {
options.push({
name: gettextCatalog.getString('Motion block'),
id: 'motionBlock',
disabled: disabled.motionBlock,
});
}
if (_.some(motions, function (motion) { return motion.origin; })) {
options.push({
name: gettextCatalog.getString('Origin'),
id: 'origin',
disabled: disabled.origin,
});
}
if (Config.get('motions_recommendations_by').value) {
options.push({
name: gettextCatalog.getString('Recommendation'),
id: 'recommendation',
disabled: disabled.recommendation
});
}
return options;
};
if (!singleMotion) { if (!singleMotion) {
fields = [ fields = [
{ {
key: 'format', key: 'format',
type: 'select-radio', type: 'radio-buttons',
templateOptions: { templateOptions: {
label: gettextCatalog.getString('Format'), label: gettextCatalog.getString('Format'),
options: [ options: [
@ -658,7 +692,7 @@ angular.module('OpenSlidesApp.motions.site', [
fields.push.apply(fields, [ fields.push.apply(fields, [
{ {
key: 'lineNumberMode', key: 'lineNumberMode',
type: 'select-radio', type: 'radio-buttons',
templateOptions: { templateOptions: {
label: gettextCatalog.getString('Line numbering'), label: gettextCatalog.getString('Line numbering'),
options: [ options: [
@ -671,7 +705,7 @@ angular.module('OpenSlidesApp.motions.site', [
}, },
{ {
key: 'lineNumberMode', key: 'lineNumberMode',
type: 'select-radio', type: 'radio-buttons',
templateOptions: { templateOptions: {
label: gettextCatalog.getString('Line numbering'), label: gettextCatalog.getString('Line numbering'),
options: [ options: [
@ -684,7 +718,7 @@ angular.module('OpenSlidesApp.motions.site', [
}, },
{ {
key: 'changeRecommendationMode', key: 'changeRecommendationMode',
type: 'select-radio', type: 'radio-buttons',
templateOptions: { templateOptions: {
label: gettextCatalog.getString('Change recommendations'), label: gettextCatalog.getString('Change recommendations'),
options: [ options: [
@ -698,7 +732,7 @@ angular.module('OpenSlidesApp.motions.site', [
}, },
{ {
key: 'changeRecommendationMode', key: 'changeRecommendationMode',
type: 'select-radio', type: 'radio-buttons',
templateOptions: { templateOptions: {
label: gettextCatalog.getString('Change recommendations'), label: gettextCatalog.getString('Change recommendations'),
options: [ options: [
@ -711,27 +745,62 @@ angular.module('OpenSlidesApp.motions.site', [
hideExpression: "model.format === 'pdf'", hideExpression: "model.format === 'pdf'",
}, },
{ {
key: 'includeReason', key: 'include',
type: 'select-radio', type: 'checkbox-buttons',
templateOptions: { templateOptions: {
label: gettextCatalog.getString('Reason'), label: gettextCatalog.getString('Content'),
options: [ options: [
{name: gettextCatalog.getString('Yes'), value: true}, {name: gettextCatalog.getString('Text'), id: 'text'},
{name: gettextCatalog.getString('No'), value: false}, {name: gettextCatalog.getString('Reason'), id: 'reason'},
], ],
}, },
}, },
{
key: 'include',
type: 'checkbox-buttons',
templateOptions: {
label: gettextCatalog.getString('Metainformation'),
options: getMetaInformationOptions(),
},
hideExpression: "model.format !== 'pdf'",
},
{
key: 'include',
type: 'checkbox-buttons',
templateOptions: {
label: gettextCatalog.getString('Metainformation'),
options: getMetaInformationOptions({votingResult: true}),
},
hideExpression: "model.format !== 'csv'",
},
{
key: 'include',
type: 'checkbox-buttons',
templateOptions: {
label: gettextCatalog.getString('Metainformation'),
options: getMetaInformationOptions({
state: true,
votingResult: true,
motionBlock: true,
origin: true,
recommendation: true,
}),
},
hideExpression: "model.format !== 'docx'",
},
]); ]);
if (commentsAvailable) { if (commentsAvailable) {
fields.push({ fields.push({
key: 'includeComments', key: 'includeComments',
type: 'select-radio', type: 'checkbox-buttons',
templateOptions: { templateOptions: {
label: gettextCatalog.getString('Comments'), label: gettextCatalog.getString('Comments'),
options: [ options: _.map(noSpecialCommentsFields, function (field, id) {
{name: gettextCatalog.getString('Yes'), value: true}, return {
{name: gettextCatalog.getString('No'), value: false}, name: gettextCatalog.getString(field.name),
], id: id,
};
}),
}, },
hideExpression: "model.format === 'csv'", hideExpression: "model.format === 'csv'",
}); });
@ -740,7 +809,7 @@ angular.module('OpenSlidesApp.motions.site', [
if (!singleMotion) { if (!singleMotion) {
fields.push({ fields.push({
key: 'pdfFormat', key: 'pdfFormat',
type: 'select-radio', type: 'radio-buttons',
templateOptions: { templateOptions: {
label: gettextCatalog.getString('PDF format'), label: gettextCatalog.getString('PDF format'),
options: [ options: [
@ -769,9 +838,27 @@ angular.module('OpenSlidesApp.motions.site', [
'singleMotion', 'singleMotion',
function ($scope, Config, MotionExportForm, MotionPdfExport, MotionCsvExport, function ($scope, Config, MotionExportForm, MotionPdfExport, MotionCsvExport,
MotionDocxExport, motions, params, singleMotion) { MotionDocxExport, motions, params, singleMotion) {
$scope.formFields = MotionExportForm.getFormFields(singleMotion, function () { $scope.formFields = MotionExportForm.getFormFields(singleMotion, motions, function () {
if ($scope.params.format !== 'pdf') {
$scope.params.changeRecommendationMode = 'original'; $scope.params.changeRecommendationMode = 'original';
$scope.params.lineNumberMode = 'none'; $scope.params.lineNumberMode = 'none';
$scope.params.include.votingresult = false;
}
if ($scope.params.format === 'docx') {
$scope.params.include.state = false;
$scope.params.include.motionBlock = false;
$scope.params.include.origin = false;
$scope.params.include.recommendation = false;
} else {
$scope.params.include.state = true;
$scope.params.include.motionBlock = true;
$scope.params.include.origin = true;
$scope.params.include.recommendation = true;
}
if ($scope.params.format === 'pdf') {
$scope.params.include.state = true;
$scope.params.include.votingresult = true;
}
}); });
$scope.params = params || {}; $scope.params = params || {};
_.defaults($scope.params, { _.defaults($scope.params, {
@ -779,8 +866,17 @@ angular.module('OpenSlidesApp.motions.site', [
pdfFormat: 'pdf', pdfFormat: 'pdf',
changeRecommendationMode: Config.get('motions_recommendation_text_mode').value, changeRecommendationMode: Config.get('motions_recommendation_text_mode').value,
lineNumberMode: Config.get('motions_default_line_numbering').value, lineNumberMode: Config.get('motions_default_line_numbering').value,
includeReason: true, include: {
includeComments: false, text: true,
reason: true,
state: true,
submitters: true,
votingresult: true,
motionBlock: true,
origin: true,
recommendation: true,
},
includeComments: {},
}); });
$scope.motions = motions; $scope.motions = motions;
$scope.singleMotion = singleMotion; $scope.singleMotion = singleMotion;
@ -1973,9 +2069,10 @@ angular.module('OpenSlidesApp.motions.site', [
'gettext', 'gettext',
'Category', 'Category',
'Motion', 'Motion',
'MotionBlock',
'User', 'User',
'MotionCsvExport', 'MotionCsvExport',
function($scope, $q, gettext, Category, Motion, User, MotionCsvExport) { function($scope, $q, gettext, Category, Motion, MotionBlock, User, MotionCsvExport) {
// set initial data for csv import // set initial data for csv import
$scope.motions = []; $scope.motions = [];
@ -1988,7 +2085,7 @@ angular.module('OpenSlidesApp.motions.site', [
}, },
}; };
var FIELDS = ['identifier', 'title', 'text', 'reason', 'submitter', 'category', 'origin']; var FIELDS = ['identifier', 'title', 'text', 'reason', 'submitter', 'category', 'origin', 'motionBlock'];
$scope.motions = []; $scope.motions = [];
$scope.onCsvChange = function (csv) { $scope.onCsvChange = function (csv) {
$scope.motions = []; $scope.motions = [];
@ -2030,38 +2127,43 @@ angular.module('OpenSlidesApp.motions.site', [
motion.reason = '<p>' + motion.reason + '</p>'; motion.reason = '<p>' + motion.reason + '</p>';
} }
// submitter // submitter
if (motion.submitter) { if (motion.submitter && motion.submitter !== '') {
if (motion.submitter !== '') { angular.forEach(User.getAll(), function (user) {
// All user objects are already loaded via the resolve statement from ui-router.
var users = User.getAll();
angular.forEach(users, function (user) {
if (user.short_name == motion.submitter.trim()) { if (user.short_name == motion.submitter.trim()) {
motion.submitters_id = [user.id]; motion.submitters_id = [user.id];
motion.submitter = User.get(user.id).full_name; motion.submitter = user.full_name;
} }
}); });
} if (!motion.submitters_id) {
}
if (motion.submitter && motion.submitter !== '' && !motion.submitters_id) {
motion.submitter_create = gettext('New participant will be created.'); motion.submitter_create = gettext('New participant will be created.');
} }
}
// category // category
if (motion.category) { if (motion.category && motion.category !== '') {
if (motion.category !== '') { angular.forEach(Category.getAll(), function (category) {
// All categore objects are already loaded via the resolve statement from ui-router.
var categories = Category.getAll();
angular.forEach(categories, function (category) {
// search for existing category // search for existing category
if (category.name == motion.category) { if (category.name == motion.category.trim()) {
motion.category_id = category.id; motion.category_id = category.id;
motion.category = Category.get(category.id).name; motion.category = category.name;
} }
}); });
} if (!motion.category_id) {
}
if (motion.category && motion.category !== '' && !motion.category_id) {
motion.category_create = gettext('New category will be created.'); motion.category_create = gettext('New category will be created.');
} }
}
// Motion block
if (motion.motionBlock && motion.motionBlock !== '') {
angular.forEach(MotionBlock.getAll(), function (block) {
// search for existing block
if (block.title == motion.motionBlock.trim()) {
motion.motion_block_id = block.id;
motion.motionBlock = block.title;
}
});
if (!motion.motion_block_id) {
motion.motionBlock_create = gettext('New motion block will be created.');
}
}
$scope.motions.push(motion); $scope.motions.push(motion);
}); });
@ -2092,10 +2194,12 @@ angular.module('OpenSlidesApp.motions.site', [
// Reset counters // Reset counters
$scope.usersCreated = 0; $scope.usersCreated = 0;
$scope.categoriesCreated = 0; $scope.categoriesCreated = 0;
$scope.motionBlocksCreated = 0;
var importedUsers = []; var importedUsers = [];
var importedCategories = []; var importedCategories = [];
// collect users and categories var importedMotionBlocks = [];
// collect users, categories and motion blocks
angular.forEach($scope.motions, function (motion) { angular.forEach($scope.motions, function (motion) {
if (motion.selected && !motion.importerror) { if (motion.selected && !motion.importerror) {
// collect user if not exists // collect user if not exists
@ -2116,10 +2220,17 @@ angular.module('OpenSlidesApp.motions.site', [
}; };
importedCategories.push(category); importedCategories.push(category);
} }
// collect motion block if not exists
if (!motion.motion_block_id && motion.motionBlock) {
var motionBlock = {
title: motion.motionBlock,
};
importedMotionBlocks.push(motionBlock);
}
} }
}); });
// unique users and categories // unique users, categories and motion blocks
var importedUsersUnique = _.uniqWith(importedUsers, function (u1, u2) { var importedUsersUnique = _.uniqWith(importedUsers, function (u1, u2) {
return u1.first_name == u2.first_name && return u1.first_name == u2.first_name &&
u1.last_name == u2.last_name; u1.last_name == u2.last_name;
@ -2127,12 +2238,15 @@ angular.module('OpenSlidesApp.motions.site', [
var importedCategoriesUnique = _.uniqWith(importedCategories, function (c1, c2) { var importedCategoriesUnique = _.uniqWith(importedCategories, function (c1, c2) {
return c1.name == c2.name; return c1.name == c2.name;
}); });
var importedMotionBlocksUnique = _.uniqWith(importedMotionBlocks, function (c1, c2) {
return c1.title == c2.title;
});
// Promises for users and categories // Promises for users and categories
var createPromises = []; var createPromises = [];
// create users and categories // create users and categories
importedUsersUnique.forEach(function (user) { _.forEach(importedUsersUnique, function (user) {
createPromises.push(User.create(user).then( createPromises.push(User.create(user).then(
function (success) { function (success) {
user.id = success.id; user.id = success.id;
@ -2140,7 +2254,7 @@ angular.module('OpenSlidesApp.motions.site', [
} }
)); ));
}); });
importedCategoriesUnique.forEach(function (category) { _.forEach(importedCategoriesUnique, function (category) {
createPromises.push(Category.create(category).then( createPromises.push(Category.create(category).then(
function (success) { function (success) {
category.id = success.id; category.id = success.id;
@ -2148,6 +2262,14 @@ angular.module('OpenSlidesApp.motions.site', [
} }
)); ));
}); });
_.forEach(importedMotionBlocksUnique, function (motionBlock) {
createPromises.push(MotionBlock.create(motionBlock).then(
function (success) {
motionBlock.id = success.id;
$scope.motionBlocksCreated++;
}
));
});
// wait for users and categories to create // wait for users and categories to create
$q.all(createPromises).then( function() { $q.all(createPromises).then( function() {
@ -2160,7 +2282,7 @@ angular.module('OpenSlidesApp.motions.site', [
var last_name = motion.submitter.substr(index+1); var last_name = motion.submitter.substr(index+1);
// search for user, set id. // search for user, set id.
importedUsersUnique.forEach(function (user) { _.forEach(importedUsersUnique, function (user) {
if (user.first_name == first_name && if (user.first_name == first_name &&
user.last_name == last_name) { user.last_name == last_name) {
motion.submitters_id = [user.id]; motion.submitters_id = [user.id];
@ -2172,12 +2294,24 @@ angular.module('OpenSlidesApp.motions.site', [
var name = motion.category; var name = motion.category;
// search for category, set id. // search for category, set id.
importedCategoriesUnique.forEach(function (category) { _.forEach(importedCategoriesUnique, function (category) {
if (category.name == name) { if (category.name == name) {
motion.category_id = category.id; motion.category_id = category.id;
} }
}); });
} }
// add motion block
if (!motion.motion_block_id && motion.motionBlock) {
var title = motion.motionBlock;
// search for motion block
_.forEach(importedMotionBlocksUnique, function (motionBlock) {
if (motionBlock.title == title) {
motion.motion_block_id = motionBlock.id;
}
});
}
// finally create motion // finally create motion
Motion.create(motion).then( Motion.create(motion).then(

View File

@ -25,9 +25,11 @@
<translate>Reason</translate>, <translate>Reason</translate>,
<translate>Submitter</translate>, <translate>Submitter</translate>,
<translate>Category</translate>, <translate>Category</translate>,
<translate>Origin</translate> <translate>Origin</translate>,
<translate>Motion block</translate>
</code> </code>
<li translate>Identifier, reason, submitter, category and origin are optional and may be empty. <li translate>Identifier, reason, submitter, category, origin and motion block are optional and may be empty.
<li translate>Additional columns after the required ones may be present and won't affect the import.
<li translate>Only double quotes are accepted as text delimiter (no single quotes). <li translate>Only double quotes are accepted as text delimiter (no single quotes).
<li><a id="downloadLink" href="" ng-click="downloadCSVExample()" translate>Download CSV example file</a> <li><a id="downloadLink" href="" ng-click="downloadCSVExample()" translate>Download CSV example file</a>
</ul> </ul>
@ -46,7 +48,8 @@
<th translate>Reason <th translate>Reason
<th translate>Submitter <th translate>Submitter
<th translate>Category <th translate>Category
<th translate>Origin</th> <th translate>Origin
<th translate>Motion block
<tbody ng-repeat="motion in motions"> <tbody ng-repeat="motion in motions">
<tr> <tr>
<td class="minimum" <td class="minimum"
@ -93,6 +96,11 @@
</span> </span>
{{ motion.category }} {{ motion.category }}
<td>{{ motion.origin | limitTo:30 }}{{ motion.origin.length > 30 ? '...' : '' }} <td>{{ motion.origin | limitTo:30 }}{{ motion.origin.length > 30 ? '...' : '' }}
<td ng-class="{ 'text-warning': motion.motionBlock_create }">
<span ng-if="motion.motionBlock_create" title="{{ motion.motionBlock_create | translate }}">
<i class="fa fa-plus-circle"></i>
</span>
{{ motion.motionBlock }}
</table> </table>
</div> </div>
@ -113,7 +121,8 @@
{{ motionsImported.length }} {{ motionsImported.length }}
<translate>motions were successfully imported.</translate> <translate>motions were successfully imported.</translate>
(<translate>Users created</translate>: {{ usersCreated }}, (<translate>Users created</translate>: {{ usersCreated }},
<translate>Categories created</translate>: {{ categoriesCreated }}) <translate>Categories created</translate>: {{ categoriesCreated }},
<translate>Motion blocks created</translate>: {{ motionBlocksCreated }})
</div> </div>
<div class="spacer"> <div class="spacer">