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,
});
formlyConfig.setType({
name: 'select-radio',
templateUrl: 'static/templates/core/select-radio.html',
name: 'checkbox-buttons',
templateUrl: 'static/templates/core/checkbox-buttons.html',
overwriteOk: true,
});
formlyConfig.setType({
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">
<label ng-repeat="(key, option) in to.options" ng-click="model[options.key] = option.value;"
class="btn btn-default btn-sm" ng-class="{active: (model[options.key] == option.value)}">
<input type="radio" tabindex="0" ng-value="option.value" ng-model="model[options.key]">{{option.name}}
<div class="form-group">
<div ng-if="to.label">
<label class="control-label">
{{ to.label | translate }}
</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" 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',
'lineNumberingService',
function (gettextCatalog, Config, CsvDownload, lineNumberingService) {
var makeHeaderline = function () {
var headerline = ['Identifier', 'Title', 'Text', 'Reason', 'Submitter', 'Category', 'Origin'];
var makeHeaderline = function (params) {
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 gettextCatalog.getString(entry);
});
@ -24,41 +46,89 @@ angular.module('OpenSlidesApp.motions.csv', [])
_.defaults(params, {
filename: 'motions-export.csv',
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)) {
params.changeRecommendationMode = 'original';
}
var csvRows = [
makeHeaderline()
makeHeaderline(params)
];
_.forEach(motions, function (motion) {
var text = motion.getTextByMode(params.changeRecommendationMode, null, null, false);
var row = [];
// Identifier and title
row.push('"' + motion.identifier !== null ? motion.identifier : '' + '"');
row.push('"' + motion.getTitle() + '"');
row.push('"' + text + '"');
if (params.includeReason) {
row.push('"' + motion.getReason() + '"');
} else {
row.push('""');
// Text
if (params.include.text) {
row.push('"' + text + '"');
}
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 : '';
row.push('"' + category + '"');
row.push('"' + motion.origin + '"');
// Origin
if (params.include.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);
});
CsvDownload(csvRows, 'motions-export.csv');
},
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
['A1', 'Title 1', 'Text 1', 'Reason 1', 'Submitter A', 'Category A', 'Last Year Conference A'],
['B1', 'Title 2', 'Text 2', 'Reason 2', 'Submitter B', 'Category B', ''],
['' , 'Title 3', 'Text 3', '', '', '', ''],
['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', '', 'Block A', 'accepted', 'Acceptance'],
['' , 'Title 3', 'Text 3', '', '', '', '', '', '', ''],
];
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;
// promises for create the actual motion data
var promises = _.map(motions, function (motion) {
var text = motion.getTextByMode(params.changeRecommendationMode, null, null, false);
var reason = params.includeReason ? motion.getReason() : '';
var comments = params.includeComments ? getMotionComments(motion) : [];
var text = params.include.text ? motion.getTextByMode(params.changeRecommendationMode, null, null, false) : '';
var reason = params.include.reason ? motion.getReason() : '';
var comments = getMotionComments(motion, params.includeComments);
// Data for one motions. Must include translations, ...
var motionData = {
@ -115,9 +115,9 @@ angular.module('OpenSlidesApp.motions.docx', ['OpenSlidesApp.core.docx'])
id: motion.id,
identifier: motion.identifier,
title: motion.getTitle(),
submitters: _.map(motion.submitters, function (submitter) {
submitters: params.include.submitters ? _.map(motion.submitters, function (submitter) {
return submitter.get_full_name();
}).join(', '),
}).join(', ') : '',
status: motion.getStateName(),
// Miscellaneous stuff
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 comments = [];
_.forEach(fields, function (field, id) {
if (motion.comments[id]) {
var title = gettextCatalog.getString('Comment') + ' ' + field.name;
if (!field.public) {
_.forEach(fieldsIncluded, function (ok, id) {
if (ok && motion.comments[id]) {
var title = fields[id].name;
if (!fields[id].public) {
title += ' (' + gettextCatalog.getString('internal') + ')';
}
var comment = motion.comments[id];
@ -179,14 +179,16 @@ angular.module('OpenSlidesApp.motions.docx', ['OpenSlidesApp.core.docx'])
return {
export: function (motions, params) {
converter = Html2DocxConverter.createInstance();
if (!params) {
params = {};
}
params = _.clone(params || {}); // Clone this to avoid sideeffects.
_.defaults(params, {
filename: 'motions-export.docx',
changeRecommendationMode: Config.get('motions_recommendation_text_mode').value,
includeReason: true,
includeComments: false,
include: {
text: true,
reason: true,
submitters: true,
},
includeComments: {},
});
if (!_.includes(['original', 'changed', 'agreed'], params.changeRecommendationMode)) {
params.changeRecommendationMode = 'original';

View File

@ -23,24 +23,36 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
* @constructor
*/
var createInstance = function(motion, motionVersion, changeRecommendationMode,
changeRecommendations, lineNumberMode, includeReason, includeComments) {
var createInstance = function(motion, motionVersion, params) {
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;
// Query all image sources from motion text and reason
var getImageSources = function () {
var text = motion.getTextByMode(changeRecommendationMode, null);
var text = motion.getTextByMode(params.changeRecommendationMode, null);
var reason = motion.getReason();
var comments = '';
if (includeComments) {
var fields = MotionComment.getNoSpecialCommentsFields();
_.forEach(fields, function (field, id) {
if (motion.comments[id]) {
comments += HTMLValidizer.validize(motion.comments[id]);
}
});
}
_.forEach(params.includeComments, function (ok, id) {
if (ok && motion.comments[id]) {
comments += HTMLValidizer.validize(motion.comments[id]);
}
});
var content = HTMLValidizer.validize(text) + HTMLValidizer.validize(motion.getReason()) + comments;
var map = Function.prototype.call.bind([].map);
return map($(content).find('img'), function(element) {
@ -77,31 +89,35 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
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'
}
]);
if (params.include.submitters) {
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'
}
]);
if (params.include.state) {
metaTableBody.push([
{
text: gettextCatalog.getString('State') + ':',
style: ['bold', 'grey']
},
{
text: motion.getStateName(),
style: 'grey'
}
]);
}
// recommendation
if (motion.getRecommendationName()) {
if (params.include.recommendation && motion.getRecommendationName()) {
metaTableBody.push([
{
text: Config.get('motions_recommendations_by').value + ':',
@ -128,7 +144,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
}
// motion block
if (motion.motionBlock) {
if (params.include.motionBlock && motion.motionBlock) {
metaTableBody.push([
{
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
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 column2 = [];
var column3 = [];
@ -219,10 +249,10 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
}
// summary of change recommendations (for motion diff version only)
if (changeRecommendationMode == "diff" && changeRecommendations.length) {
if (params.changeRecommendationMode == "diff" && motion.changeRecommendations.length) {
var columnLineNumbers = [];
var columnChangeType = [];
angular.forEach(_.orderBy(changeRecommendations, ['line_from']), function(change) {
angular.forEach(_.orderBy(motion.changeRecommendations, ['line_from']), function(change) {
// line numbers column
var line;
if (change.line_from >= change.line_to - 1) {
@ -264,47 +294,57 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
]);
}
// 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: ['35%','65%'],
body: metaTableBody,
},
margin: [0, 0, 0, 20],
layout: '{{motion-placeholder-to-insert-functions-here}}'
};
return metaTableJsonString;
if (metaTableBody.length) {
// 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 metaTable = {
table: {
widths: ['35%','65%'],
body: metaTableBody,
},
margin: [0, 0, 0, 20],
layout: '{{motion-placeholder-to-insert-functions-here}}'
};
return metaTable;
} else {
return {};
}
};
// motion title
var motionTitle = function() {
return [{
text: motion.getTitle(motionVersion),
style: 'heading3'
}];
if (params.include.text) {
return [{
text: motion.getTitle(motionVersion),
style: 'heading3'
}];
}
};
// motion preamble
var motionPreamble = function () {
return {
text: Config.translate(Config.get('motions_preamble').value),
margin: [0, 10, 0, 0]
};
if (params.include.text) {
return {
text: Config.translate(Config.get('motions_preamble').value),
margin: [0, 10, 0, 0]
};
}
};
// motion text (with line-numbers)
var motionText = function() {
var motionTextContent = motion.getTextByMode(changeRecommendationMode, motionVersion);
return converter.convertHTML(motionTextContent, lineNumberMode);
if (params.include.text) {
var motionTextContent = motion.getTextByMode(params.changeRecommendationMode, motionVersion);
return converter.convertHTML(motionTextContent, params.lineNumberMode);
}
};
// motion reason heading
var motionReason = function() {
if (includeReason) {
if (params.include.reason) {
var reason = [];
if (motion.getReason(motionVersion)) {
reason.push({
@ -327,13 +367,13 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
// motion comments handling
var motionComments = function () {
if (includeComments) {
if (_.keys(params.includeComments).length !== 0) {
var fields = MotionComment.getNoSpecialCommentsFields();
var comments = [];
_.forEach(fields, function (field, id) {
if (motion.comments[id]) {
var title = field.name;
if (!field.public) {
_.forEach(params.includeComments, function (ok, id) {
if (ok && motion.comments[id]) {
var title = fields[id].name;
if (!fields[id].public) {
title += ' (' + gettextCatalog.getString('internal') + ')';
}
comments.push({
@ -895,13 +935,6 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
return {
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,
lineNumberMode: Config.get('motions_default_line_numbering').value,
includeReason: true,
includeComments: false,
});
if (singleMotion) {
_.defaults(params, {
@ -927,9 +960,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
var motionContentProviderPromises = _.map(motions, function (motion) {
var version = (singleMotion ? params.version : motion.active_version);
return MotionContentProvider.createInstance(
motion, version, params.changeRecommendationMode,
motion.changeRecommendations, params.lineNumberMode,
params.includeReason, params.includeComments
motion, version, params
).then(function (contentProvider) {
motionContentProviderArray.push(contentProvider);
});

View File

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

View File

@ -25,9 +25,11 @@
<translate>Reason</translate>,
<translate>Submitter</translate>,
<translate>Category</translate>,
<translate>Origin</translate>
<translate>Origin</translate>,
<translate>Motion block</translate>
</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><a id="downloadLink" href="" ng-click="downloadCSVExample()" translate>Download CSV example file</a>
</ul>
@ -46,7 +48,8 @@
<th translate>Reason
<th translate>Submitter
<th translate>Category
<th translate>Origin</th>
<th translate>Origin
<th translate>Motion block
<tbody ng-repeat="motion in motions">
<tr>
<td class="minimum"
@ -93,6 +96,11 @@
</span>
{{ motion.category }}
<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>
</div>
@ -113,7 +121,8 @@
{{ motionsImported.length }}
<translate>motions were successfully imported.</translate>
(<translate>Users created</translate>: {{ usersCreated }},
<translate>Categories created</translate>: {{ categoriesCreated }})
<translate>Categories created</translate>: {{ categoriesCreated }},
<translate>Motion blocks created</translate>: {{ motionBlocksCreated }})
</div>
<div class="spacer">