721 lines
27 KiB
JavaScript
721 lines
27 KiB
JavaScript
(function () {
|
|
|
|
'use strict';
|
|
|
|
angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
|
|
|
|
.config([
|
|
'mainMenuProvider',
|
|
'gettext',
|
|
function (mainMenuProvider, gettext) {
|
|
mainMenuProvider.register({
|
|
'ui_sref': 'assignments.assignment.list',
|
|
'img_class': 'pie-chart',
|
|
'title': gettext('Elections'),
|
|
'weight': 400,
|
|
'perm': 'assignments.can_see'
|
|
});
|
|
}
|
|
])
|
|
|
|
.config([
|
|
'$stateProvider',
|
|
function($stateProvider) {
|
|
$stateProvider
|
|
.state('assignments', {
|
|
url: '/assignments',
|
|
abstract: true,
|
|
template: "<ui-view/>",
|
|
})
|
|
.state('assignments.assignment', {
|
|
abstract: true,
|
|
template: "<ui-view/>",
|
|
})
|
|
.state('assignments.assignment.list', {
|
|
resolve: {
|
|
assignments: function(Assignment) {
|
|
return Assignment.findAll();
|
|
},
|
|
items: function(Agenda) {
|
|
return Agenda.findAll().catch(
|
|
function () {
|
|
return null;
|
|
}
|
|
);
|
|
},
|
|
phases: function(Assignment) {
|
|
return Assignment.getPhases();
|
|
}
|
|
}
|
|
})
|
|
.state('assignments.assignment.detail', {
|
|
controller: 'AssignmentDetailCtrl',
|
|
resolve: {
|
|
assignment: function(Assignment, $stateParams) {
|
|
return Assignment.find($stateParams.id).then(function(assignment) {
|
|
return Assignment.loadRelations(assignment, 'agenda_item');
|
|
});
|
|
},
|
|
users: function(User) {
|
|
return User.findAll();
|
|
},
|
|
phases: function(Assignment) {
|
|
return Assignment.getPhases();
|
|
}
|
|
}
|
|
})
|
|
// redirects to assignment detail and opens assignment edit form dialog, uses edit url,
|
|
// used by ui-sref links from agenda only
|
|
// (from assignment controller use AssignmentForm factory instead to open dialog in front
|
|
// of current view without redirect)
|
|
.state('assignments.assignment.detail.update', {
|
|
onEnter: ['$stateParams', '$state', 'ngDialog', 'Assignment',
|
|
function($stateParams, $state, ngDialog, Assignment) {
|
|
ngDialog.open({
|
|
template: 'static/templates/assignments/assignment-form.html',
|
|
controller: 'AssignmentUpdateCtrl',
|
|
className: 'ngdialog-theme-default wide-form',
|
|
closeByEscape: false,
|
|
closeByDocument: false,
|
|
resolve: {
|
|
assignment: function() {
|
|
return Assignment.find($stateParams.id).then(function(assignment) {
|
|
return Assignment.loadRelations(assignment, 'agenda_item');
|
|
});
|
|
},
|
|
},
|
|
preCloseCallback: function() {
|
|
$state.go('assignments.assignment.detail', {assignment: $stateParams.id});
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
]
|
|
});
|
|
}
|
|
])
|
|
|
|
// Service for generic assignment form (create and update)
|
|
.factory('AssignmentForm', [
|
|
'gettextCatalog',
|
|
'operator',
|
|
function (gettextCatalog, operator) {
|
|
return {
|
|
// ngDialog for assignment form
|
|
getDialog: function (assignment) {
|
|
var resolve;
|
|
if (assignment) {
|
|
resolve = {
|
|
assignment: function() {
|
|
return assignment;
|
|
},
|
|
agenda_item: function(Assignment) {
|
|
return Assignment.loadRelations(assignment, 'agenda_item');
|
|
}
|
|
};
|
|
}
|
|
return {
|
|
template: 'static/templates/assignments/assignment-form.html',
|
|
controller: (assignment) ? 'AssignmentUpdateCtrl' : 'AssignmentCreateCtrl',
|
|
className: 'ngdialog-theme-default wide-form',
|
|
closeByEscape: false,
|
|
closeByDocument: false,
|
|
resolve: (resolve) ? resolve : null
|
|
};
|
|
},
|
|
// angular-formly fields for assignment form
|
|
getFormFields: function () {
|
|
return [
|
|
{
|
|
key: 'title',
|
|
type: 'input',
|
|
templateOptions: {
|
|
label: gettextCatalog.getString('Title'),
|
|
required: true
|
|
}
|
|
},
|
|
{
|
|
key: 'description',
|
|
type: 'textarea',
|
|
templateOptions: {
|
|
label: gettextCatalog.getString('Description')
|
|
}
|
|
},
|
|
{
|
|
key: 'open_posts',
|
|
type: 'input',
|
|
templateOptions: {
|
|
label: gettextCatalog.getString('Number of posts to be elected'),
|
|
type: 'number',
|
|
required: true
|
|
}
|
|
},
|
|
{
|
|
key: 'poll_description_default',
|
|
type: 'input',
|
|
templateOptions: {
|
|
label: gettextCatalog.getString('Default comment on the ballot paper')
|
|
}
|
|
},
|
|
{
|
|
key: 'showAsAgendaItem',
|
|
type: 'checkbox',
|
|
templateOptions: {
|
|
label: gettextCatalog.getString('Show as agenda item'),
|
|
description: gettextCatalog.getString('If deactivated the election appears as internal item on agenda.')
|
|
},
|
|
hide: !operator.hasPerms('assignments.can_manage')
|
|
}];
|
|
}
|
|
};
|
|
}
|
|
])
|
|
|
|
.controller('AssignmentListCtrl', [
|
|
'$scope',
|
|
'ngDialog',
|
|
'AssignmentForm',
|
|
'Assignment',
|
|
'phases',
|
|
function($scope, ngDialog, AssignmentForm, Assignment, phases) {
|
|
Assignment.bindAll({}, $scope, 'assignments');
|
|
$scope.phases = phases;
|
|
$scope.alert = {};
|
|
|
|
// setup table sorting
|
|
$scope.sortColumn = 'title';
|
|
$scope.filterPresent = '';
|
|
$scope.reverse = false;
|
|
// function to sort by clicked column
|
|
$scope.toggleSort = function (column) {
|
|
if ( $scope.sortColumn === column ) {
|
|
$scope.reverse = !$scope.reverse;
|
|
}
|
|
$scope.sortColumn = column;
|
|
};
|
|
// define custom search filter string
|
|
$scope.getFilterString = function (assignment) {
|
|
return [
|
|
assignment.title,
|
|
assignment.description,
|
|
$scope.phases[assignment.phase].display_name,
|
|
_.map(assignment.assignment_related_users,
|
|
function (candidate) {return candidate.user.get_short_name();}).join(" "),
|
|
].join(" ");
|
|
};
|
|
|
|
// open new/edit dialog
|
|
$scope.openDialog = function (assignment) {
|
|
ngDialog.open(AssignmentForm.getDialog(assignment));
|
|
};
|
|
// cancel QuickEdit mode
|
|
$scope.cancelQuickEdit = function (assignment) {
|
|
// revert all changes by restore (refresh) original assignment object from server
|
|
Assignment.refresh(assignment);
|
|
assignment.quickEdit = false;
|
|
};
|
|
// save changed assignment
|
|
$scope.save = function (assignment) {
|
|
Assignment.save(assignment).then(
|
|
function(success) {
|
|
assignment.quickEdit = false;
|
|
$scope.alert.show = false;
|
|
},
|
|
function(error){
|
|
var message = '';
|
|
for (var e in error.data) {
|
|
message += e + ': ' + error.data[e] + ' ';
|
|
}
|
|
$scope.alert = { type: 'danger', msg: message, show: true };
|
|
});
|
|
};
|
|
// *** delete mode functions ***
|
|
$scope.isDeleteMode = false;
|
|
// check all checkboxes
|
|
$scope.checkAll = function () {
|
|
angular.forEach($scope.assignments, function (assignment) {
|
|
assignment.selected = $scope.selectedAll;
|
|
});
|
|
};
|
|
// uncheck all checkboxes if isDeleteMode is closed
|
|
$scope.uncheckAll = function () {
|
|
if (!$scope.isDeleteMode) {
|
|
$scope.selectedAll = false;
|
|
angular.forEach($scope.assignments, function (assignment) {
|
|
assignment.selected = false;
|
|
});
|
|
}
|
|
};
|
|
// delete all selected assignments
|
|
$scope.deleteMultiple = function () {
|
|
angular.forEach($scope.assignments, function (assignment) {
|
|
if (assignment.selected)
|
|
Assignment.destroy(assignment.id);
|
|
});
|
|
$scope.isDeleteMode = false;
|
|
$scope.uncheckAll();
|
|
};
|
|
// delete single assignment
|
|
$scope.delete = function (assignment) {
|
|
Assignment.destroy(assignment.id);
|
|
};
|
|
}
|
|
])
|
|
|
|
.controller('AssignmentDetailCtrl', [
|
|
'$scope',
|
|
'$http',
|
|
'filterFilter',
|
|
'gettext',
|
|
'ngDialog',
|
|
'AssignmentForm',
|
|
'operator',
|
|
'Assignment',
|
|
'User',
|
|
'assignment',
|
|
'phases',
|
|
function($scope, $http, filterFilter, gettext, ngDialog, AssignmentForm, operator, Assignment, User, assignment, phases) {
|
|
User.bindAll({}, $scope, 'users');
|
|
Assignment.bindOne(assignment.id, $scope, 'assignment');
|
|
Assignment.loadRelations(assignment, 'agenda_item');
|
|
$scope.candidateSelectBox = {};
|
|
$scope.phases = phases;
|
|
$scope.alert = {};
|
|
|
|
// open edit dialog
|
|
$scope.openDialog = function (assignment) {
|
|
ngDialog.open(AssignmentForm.getDialog(assignment));
|
|
};
|
|
// add (nominate) candidate
|
|
$scope.addCandidate = function (userId) {
|
|
$http.post('/rest/assignments/assignment/' + assignment.id + '/candidature_other/', {'user': userId})
|
|
.success(function(data){
|
|
$scope.alert.show = false;
|
|
$scope.candidateSelectBox = {};
|
|
})
|
|
.error(function(data){
|
|
$scope.alert = { type: 'danger', msg: data.detail, show: true };
|
|
$scope.candidateSelectBox = {};
|
|
});
|
|
};
|
|
// remove candidate
|
|
$scope.removeCandidate = function (userId) {
|
|
$http.delete('/rest/assignments/assignment/' + assignment.id + '/candidature_other/',
|
|
{headers: {'Content-Type': 'application/json'},
|
|
data: JSON.stringify({user: userId})})
|
|
.error(function(data){
|
|
$scope.alert = { type: 'danger', msg: data.detail, show: true };
|
|
});
|
|
};
|
|
// add me (nominate self as candidate)
|
|
$scope.addMe = function () {
|
|
$http.post('/rest/assignments/assignment/' + assignment.id + '/candidature_self/', {})
|
|
.success(function(data){
|
|
$scope.alert.show = false;
|
|
})
|
|
.error(function(data){
|
|
$scope.alert = { type: 'danger', msg: data.detail, show: true };
|
|
});
|
|
};
|
|
// remove me (withdraw own candidature)
|
|
$scope.removeMe = function () {
|
|
$http.delete('/rest/assignments/assignment/' + assignment.id + '/candidature_self/')
|
|
.success(function(data){
|
|
$scope.alert.show = false;
|
|
})
|
|
.error(function(data){
|
|
$scope.alert = { type: 'danger', msg: data.detail, show: true };
|
|
});
|
|
};
|
|
// check if current user is already a candidate (elected==false)
|
|
$scope.isCandidate = function () {
|
|
var check = assignment.assignment_related_users.map( function(candidate) {
|
|
if ( !candidate.elected ) {
|
|
return candidate.user_id;
|
|
}
|
|
}).indexOf(operator.user.id);
|
|
if (check > -1)
|
|
return true;
|
|
else
|
|
return false;
|
|
};
|
|
// update phase
|
|
$scope.updatePhase = function (phase_id) {
|
|
assignment.phase = phase_id;
|
|
Assignment.save(assignment);
|
|
};
|
|
// create new ballot
|
|
$scope.createBallot = function () {
|
|
$http.post('/rest/assignments/assignment/' + assignment.id + '/create_poll/')
|
|
.success(function(data){
|
|
$scope.alert.show = false;
|
|
if (assignment.phase === 0) {
|
|
$scope.updatePhase(1);
|
|
}
|
|
})
|
|
.error(function(data){
|
|
$scope.alert = { type: 'danger', msg: data.detail, show: true };
|
|
});
|
|
};
|
|
// delete ballot
|
|
$scope.deleteBallot = function (poll) {
|
|
poll.DSDestroy();
|
|
};
|
|
// edit poll dialog
|
|
$scope.editPollDialog = function (poll, ballot) {
|
|
ngDialog.open({
|
|
template: 'static/templates/assignments/assignmentpoll-form.html',
|
|
controller: 'AssignmentPollUpdateCtrl',
|
|
className: 'ngdialog-theme-default',
|
|
closeByEscape: false,
|
|
closeByDocument: false,
|
|
resolve: {
|
|
assignmentpoll: function (AssignmentPoll) {
|
|
return AssignmentPoll.find(poll.id);
|
|
},
|
|
ballot: function () {
|
|
return ballot;
|
|
}
|
|
}
|
|
});
|
|
};
|
|
// publish ballot
|
|
$scope.publishBallot = function (poll, isPublished) {
|
|
poll.DSUpdate({
|
|
assignment_id: assignment.id,
|
|
published: isPublished,
|
|
})
|
|
.then(function(success) {
|
|
$scope.alert.show = false;
|
|
})
|
|
.catch(function(error) {
|
|
var message = '';
|
|
for (var e in error.data) {
|
|
message += e + ': ' + error.data[e] + ' ';
|
|
}
|
|
$scope.alert = { type: 'danger', msg: message, show: true };
|
|
});
|
|
};
|
|
// mark candidate as (not) elected
|
|
$scope.markElected = function (user, reverse) {
|
|
if (reverse) {
|
|
$http.delete(
|
|
'/rest/assignments/assignment/' + assignment.id + '/mark_elected/',
|
|
{
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
data: JSON.stringify({user: user})
|
|
}
|
|
);
|
|
} else {
|
|
$http.post('/rest/assignments/assignment/' + assignment.id + '/mark_elected/', {'user': user})
|
|
.then(function(success) {
|
|
var elected = filterFilter(assignment.assignment_related_users,{elected: true});
|
|
// Set phase to 'finished' if there are enough (= number of open posts) elected users.
|
|
// Note: The array 'elected' does NOT contains the candidate who is just marked as elected.
|
|
// So add 1 to length to get the real number of all elected users.
|
|
if (elected.length + 1 == assignment.open_posts ) {
|
|
$scope.updatePhase(2);
|
|
}
|
|
});
|
|
}
|
|
|
|
};
|
|
|
|
// Just mark some vote value strings for translation.
|
|
gettext('Yes');
|
|
gettext('No');
|
|
gettext('Abstain');
|
|
}
|
|
])
|
|
|
|
.controller('AssignmentCreateCtrl', [
|
|
'$scope',
|
|
'Assignment',
|
|
'AssignmentForm',
|
|
'Agenda',
|
|
function($scope, Assignment, AssignmentForm, Agenda) {
|
|
$scope.model = {};
|
|
// set default value for open posts form field
|
|
$scope.model.open_posts = 1;
|
|
// get all form fields
|
|
$scope.formFields = AssignmentForm.getFormFields();
|
|
|
|
// save assignment
|
|
$scope.save = function(assignment) {
|
|
Assignment.create(assignment).then(
|
|
function(success) {
|
|
// find related agenda item
|
|
Agenda.find(success.agenda_item_id).then(function(item) {
|
|
// check form element and set item type (AGENDA_ITEM = 1, HIDDEN_ITEM = 2)
|
|
var type = assignment.showAsAgendaItem ? 1 : 2;
|
|
// save only if agenda item type is modified
|
|
if (item.type != type) {
|
|
item.type = type;
|
|
Agenda.save(item);
|
|
}
|
|
});
|
|
$scope.closeThisDialog();
|
|
}
|
|
);
|
|
};
|
|
}
|
|
])
|
|
|
|
.controller('AssignmentUpdateCtrl', [
|
|
'$scope',
|
|
'Assignment',
|
|
'AssignmentForm',
|
|
'Agenda',
|
|
'assignment',
|
|
function($scope, Assignment, AssignmentForm, Agenda, assignment) {
|
|
$scope.alert = {};
|
|
// set initial values for form model by create deep copy of assignment object
|
|
// so list/detail view is not updated while editing
|
|
$scope.model = angular.copy(assignment);
|
|
// get all form fields
|
|
$scope.formFields = AssignmentForm.getFormFields();
|
|
for (var i = 0; i < $scope.formFields.length; i++) {
|
|
if ($scope.formFields[i].key == "showAsAgendaItem") {
|
|
// get state from agenda item (hidden/internal or agenda item)
|
|
$scope.formFields[i].defaultValue = !assignment.agenda_item.is_hidden;
|
|
}
|
|
}
|
|
|
|
// save assignment
|
|
$scope.save = function (assignment) {
|
|
// inject the changed assignment (copy) object back into DS store
|
|
Assignment.inject(assignment);
|
|
// save change assignment object on server
|
|
Assignment.save(assignment).then(
|
|
function(success) {
|
|
// check form element and set item type (AGENDA_ITEM = 1, HIDDEN_ITEM = 2)
|
|
var type = assignment.showAsAgendaItem ? 1 : 2;
|
|
// save only if agenda item type is modified
|
|
if (assignment.agenda_item.type != type) {
|
|
assignment.agenda_item.type = type;
|
|
Agenda.save(assignment.agenda_item);
|
|
}
|
|
$scope.closeThisDialog();
|
|
},
|
|
function (error) {
|
|
// save error: revert all changes by restore
|
|
// (refresh) original assignment object from server
|
|
Assignment.refresh(assignment);
|
|
var message = '';
|
|
for (var e in error.data) {
|
|
message += e + ': ' + error.data[e] + ' ';
|
|
}
|
|
$scope.alert = {type: 'danger', msg: message, show: true};
|
|
}
|
|
);
|
|
};
|
|
}
|
|
])
|
|
|
|
.controller('AssignmentPollUpdateCtrl', [
|
|
'$scope',
|
|
'gettextCatalog',
|
|
'AssignmentPoll',
|
|
'assignmentpoll',
|
|
'ballot',
|
|
function($scope, gettextCatalog, AssignmentPoll, assignmentpoll, ballot) {
|
|
// set initial values for form model by create deep copy of assignmentpoll object
|
|
// so detail view is not updated while editing poll
|
|
$scope.model = angular.copy(assignmentpoll);
|
|
$scope.ballot = ballot;
|
|
$scope.formFields = [];
|
|
$scope.alert = {};
|
|
|
|
// add dynamic form fields
|
|
assignmentpoll.options.forEach(function(option) {
|
|
var defaultValue;
|
|
if (assignmentpoll.yesnoabstain || assignmentpoll.yesno) {
|
|
if (assignmentpoll.yesnoabstain) {
|
|
defaultValue = {
|
|
'yes': '',
|
|
'no': '',
|
|
'abstain': ''
|
|
};
|
|
}
|
|
else {
|
|
defaultValue = {
|
|
'yes': '',
|
|
'no': ''
|
|
};
|
|
}
|
|
|
|
if (option.votes.length) {
|
|
defaultValue.yes = option.votes[0].weight;
|
|
defaultValue.no = option.votes[1].weight;
|
|
if (assignmentpoll.yesnoabstain){
|
|
defaultValue.abstain = option.votes[2].weight;
|
|
}
|
|
}
|
|
$scope.formFields.push(
|
|
{
|
|
noFormControl: true,
|
|
template: '<strong>' + option.candidate.get_full_name() + '</strong>'
|
|
},
|
|
{
|
|
key: 'yes_' + option.candidate_id,
|
|
type: 'input',
|
|
templateOptions: {
|
|
label: gettextCatalog.getString('Yes'),
|
|
type: 'number',
|
|
required: true
|
|
},
|
|
defaultValue: defaultValue.yes
|
|
},
|
|
{
|
|
key: 'no_' + option.candidate_id,
|
|
type: 'input',
|
|
templateOptions: {
|
|
label: gettextCatalog.getString('No'),
|
|
type: 'number',
|
|
required: true
|
|
},
|
|
defaultValue: defaultValue.no
|
|
});
|
|
if (assignmentpoll.yesnoabstain){
|
|
$scope.formFields.push(
|
|
{
|
|
key:'abstain_' + option.candidate_id,
|
|
type: 'input',
|
|
templateOptions: {
|
|
label: gettextCatalog.getString('Abstain'),
|
|
type: 'number',
|
|
required: true
|
|
},
|
|
defaultValue: defaultValue.abstain
|
|
});
|
|
}
|
|
} else {
|
|
if (option.votes.length) {
|
|
defaultValue = option.votes[0].weight;
|
|
}
|
|
$scope.formFields.push(
|
|
{
|
|
key: 'vote_' + option.candidate_id,
|
|
type: 'input',
|
|
templateOptions: {
|
|
label: option.candidate.get_full_name(),
|
|
type: 'number',
|
|
required: true
|
|
},
|
|
defaultValue: defaultValue
|
|
});
|
|
}
|
|
});
|
|
// add general form fields
|
|
$scope.formFields.push(
|
|
{
|
|
key: 'votesvalid',
|
|
type: 'input',
|
|
templateOptions: {
|
|
label: gettextCatalog.getString('Votes valid'),
|
|
type: 'number'
|
|
}
|
|
},
|
|
{
|
|
key: 'votesinvalid',
|
|
type: 'input',
|
|
templateOptions: {
|
|
label: gettextCatalog.getString('Votes invalid'),
|
|
type: 'number'
|
|
}
|
|
},
|
|
{
|
|
key: 'votescast',
|
|
type: 'input',
|
|
templateOptions: {
|
|
label: gettextCatalog.getString('Votes cast'),
|
|
type: 'number'
|
|
}
|
|
},
|
|
// TODO: update description in separat request
|
|
// (without vote result values)
|
|
{
|
|
key: 'description',
|
|
type: 'input',
|
|
templateOptions: {
|
|
label: gettextCatalog.getString('Comment on the ballot paper')
|
|
}
|
|
}
|
|
);
|
|
|
|
// save assignmentpoll
|
|
$scope.save = function (poll) {
|
|
var votes = [];
|
|
if (assignmentpoll.yesnoabstain) {
|
|
assignmentpoll.options.forEach(function(option) {
|
|
votes.push({
|
|
"Yes": poll['yes_' + option.candidate_id],
|
|
"No": poll['no_' + option.candidate_id],
|
|
"Abstain": poll['abstain_' + option.candidate_id]
|
|
});
|
|
});
|
|
} else if (assignmentpoll.yesno) {
|
|
assignmentpoll.options.forEach(function(option) {
|
|
votes.push({
|
|
"Yes": poll['yes_' + option.candidate_id],
|
|
"No": poll['no_' + option.candidate_id]
|
|
});
|
|
});
|
|
} else {
|
|
assignmentpoll.options.forEach(function(option) {
|
|
votes.push({
|
|
"Votes": poll['vote_' + option.candidate_id],
|
|
});
|
|
});
|
|
}
|
|
// save change poll object on server
|
|
poll.DSUpdate({
|
|
assignment_id: poll.assignment_id,
|
|
votes: votes,
|
|
votesvalid: poll.votesvalid,
|
|
votesinvalid: poll.votesinvalid,
|
|
votescast: poll.votescast
|
|
})
|
|
.then(function(success) {
|
|
$scope.alert.show = false;
|
|
$scope.closeThisDialog();
|
|
})
|
|
.catch(function(error) {
|
|
var message = '';
|
|
for (var e in error.data) {
|
|
message += e + ': ' + error.data[e] + ' ';
|
|
}
|
|
$scope.alert = { type: 'danger', msg: message, show: true };
|
|
});
|
|
};
|
|
}
|
|
])
|
|
|
|
//mark all assignment config strings for translation with Javascript
|
|
.config([
|
|
'gettext',
|
|
function (gettext) {
|
|
gettext('Election method');
|
|
gettext('Automatic assign of method');
|
|
gettext('Always one option per candidate');
|
|
gettext('Always Yes-No-Abstain per candidate');
|
|
gettext('Always Yes/No per candidate');
|
|
gettext('Elections');
|
|
gettext('Ballot and ballot papers');
|
|
gettext('The 100 % base of an election result consists of');
|
|
gettext('Number of ballot papers (selection)');
|
|
gettext('Number of all delegates');
|
|
gettext('Number of all participants');
|
|
gettext('Use the following custom number');
|
|
gettext('Custom number of ballot papers');
|
|
gettext('Publish election result for elected candidates only (' +
|
|
'projector view)');
|
|
gettext('Title for PDF document (all elections)');
|
|
gettext('Preamble text for PDF document (all elections)');
|
|
}
|
|
]);
|
|
|
|
}());
|