diff --git a/openslides/core/config.py b/openslides/core/config.py index f3683ad83..fc0869e9d 100644 --- a/openslides/core/config.py +++ b/openslides/core/config.py @@ -15,7 +15,7 @@ INPUT_TYPE_MAPPING = { 'integer': int, 'boolean': bool, 'choice': str, - 'comments': list, + 'comments': dict, 'colorpicker': str, 'datetimepicker': int, 'majorityMethod': str, @@ -101,17 +101,29 @@ class ConfigHandler: raise ConfigError(e.messages[0]) if config_variable.input_type == 'comments': - if not isinstance(value, list): - raise ConfigError(_('motions_comments has to be a list.')) - for comment in value: - if not isinstance(comment, dict): - raise ConfigError(_('Each element in motions_comments has to be a dict.')) - if comment.get('name') is None or comment.get('public') is None: - raise ConfigError(_('A name and a public property have to be given.')) - if not isinstance(comment['name'], str): - raise ConfigError(_('name has to be string.')) - if not isinstance(comment['public'], bool): - raise ConfigError(_('public property has to be bool.')) + if not isinstance(value, dict): + raise ConfigError(_('motions_comments has to be a dict.')) + valuecopy = dict() + for id, commentsfield in value.items(): + try: + id = int(id) + except ValueError: + raise ConfigError(_('Each id has to be an int.')) + + if id < 1: + raise ConfigError(_('Each id has to be greater then 0.')) + # Deleted commentsfields are saved as None to block the used ids + if commentsfield is not None: + if not isinstance(commentsfield, dict): + raise ConfigError(_('Each commentsfield in motions_comments has to be a dict.')) + if commentsfield.get('name') is None or commentsfield.get('public') is None: + raise ConfigError(_('A name and a public property have to be given.')) + if not isinstance(commentsfield['name'], str): + raise ConfigError(_('name has to be string.')) + if not isinstance(commentsfield['public'], bool): + raise ConfigError(_('public property has to be bool.')) + valuecopy[id] = commentsfield + value = valuecopy if config_variable.input_type == 'logo': if not isinstance(value, dict): diff --git a/openslides/core/static/js/core/site.js b/openslides/core/static/js/core/site.js index e4cd2c079..1c053357d 100644 --- a/openslides/core/static/js/core/site.js +++ b/openslides/core/static/js/core/site.js @@ -743,15 +743,6 @@ angular.module('OpenSlidesApp.core.site', [ } ]) -.filter('excludeSpecialComments', function () { - return function (comments) { - return _.filter(comments, function (comment) { - var specialComment = comment.forState || comment.forRecommendation; - return !specialComment; - }); - }; -}) - // angular formly config options .run([ 'formlyConfig', @@ -1124,14 +1115,20 @@ angular.module('OpenSlidesApp.core.site', [ // For comments input $scope.addComment = function (configOption, parent) { - parent.value.push({ + var maxId = _.max(_.keys(parent.value)); + if (maxId === undefined) { + maxId = 1; + } else { + maxId = parseInt(maxId) + 1; + } + parent.value[maxId] = { name: gettextCatalog.getString('New'), public: false, - }); + }; $scope.save(configOption, parent.value); }; - $scope.removeComment = function (configOption, parent, index) { - parent.value.splice(index, 1); + $scope.removeComment = function (configOption, parent, id) { + parent.value[id] = null; $scope.save(configOption, parent.value); }; diff --git a/openslides/core/static/templates/config-form-field.html b/openslides/core/static/templates/config-form-field.html index d192c55fa..8ee60eb7d 100644 --- a/openslides/core/static/templates/config-form-field.html +++ b/openslides/core/static/templates/config-form-field.html @@ -20,8 +20,11 @@
') !== 0) { comment = '
' + comment + '
'; } @@ -174,7 +172,7 @@ angular.module('OpenSlidesApp.motions.docx', ['OpenSlidesApp.core.docx']) comment: comment, }); } - } + }); return comments; }; diff --git a/openslides/motions/static/js/motions/motion-services.js b/openslides/motions/static/js/motions/motion-services.js index 96acd82f4..173a188a6 100644 --- a/openslides/motions/static/js/motions/motion-services.js +++ b/openslides/motions/static/js/motions/motion-services.js @@ -134,14 +134,14 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions', editors: [] }; var options = Editor.getOptions('inline', 'YOffset'); - _.forEach($scope.commentsFieldsNoSpecialComments, function (field) { + _.forEach($scope.noSpecialCommentsFields, function (field, id) { var inlineEditing = MotionInlineEditing.createInstance($scope, motion, - 'view-original-comment-inline-editor-' + field.name, false, options, + 'view-original-comment-inline-editor-' + id, false, options, function (obj) { - return motion['comment ' + field.name]; + return motion['comment_' + id]; }, function (obj) { - motion['comment ' + field.name] = obj.editor.getData(); + motion['comment_' + id] = obj.editor.getData(); } ); commentsInlineEditing.editors.push(inlineEditing); diff --git a/openslides/motions/static/js/motions/pdf.js b/openslides/motions/static/js/motions/pdf.js index 16de80897..e72c67e8a 100644 --- a/openslides/motions/static/js/motions/pdf.js +++ b/openslides/motions/static/js/motions/pdf.js @@ -15,7 +15,9 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf']) 'Category', 'Config', 'Motion', - function($q, operator, gettextCatalog, PDFLayout, PdfMakeConverter, ImageConverter, HTMLValidizer, Category, Config, Motion) { + 'MotionComment', + function($q, operator, gettextCatalog, PDFLayout, PdfMakeConverter, ImageConverter, HTMLValidizer, + Category, Config, Motion, MotionComment) { /** * Provides the content as JS objects for Motions in pdfMake context * @constructor @@ -27,10 +29,19 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf']) var converter; // Query all image sources from motion text and reason - // TODO: Do we need images for comments here too?? var getImageSources = function () { var text = motion.getTextByMode(changeRecommendationMode, null); - var content = HTMLValidizer.validize(text) + HTMLValidizer.validize(motion.getReason()); + 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]); + } + }); + } + var content = HTMLValidizer.validize(text) + HTMLValidizer.validize(motion.getReason()) + comments; var map = Function.prototype.call.bind([].map); return map($(content).find('img'), function(element) { return element.getAttribute('src'); @@ -298,16 +309,12 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf']) // motion comments handling var motionComments = function () { if (includeComments) { - var fields = Config.get('motions_comments').value; - var canSeeComment = function (index) { - var specialComment = fields[index].forState || fields[index].forRecommendation; - return (fields[index].public || operator.hasPerms('motions.can_manage')) && !specialComment; - }; + var fields = MotionComment.getNoSpecialCommentsFields(); var comments = []; - 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) { + _.forEach(fields, function (field, id) { + if (motion.comments[id]) { + var title = gettextCatalog.getString('Comment') + ' ' + field.name; + if (!field.public) { title += ' (' + gettextCatalog.getString('internal') + ')'; } comments.push({ @@ -315,9 +322,9 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf']) style: 'heading3', marginTop: 25, }); - comments.push(converter.convertHTML(motion.comments[i])); + comments.push(converter.convertHTML(motion.comments[id])); } - } + }); return comments; } }; @@ -859,12 +866,13 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf']) 'PdfCreate', 'PDFLayout', 'PersonalNoteManager', + 'MotionComment', 'Messaging', 'FileSaver', function ($http, $q, operator, Config, gettextCatalog, MotionChangeRecommendation, HTMLValidizer, PdfMakeConverter, MotionContentProvider, MotionCatalogContentProvider, PdfMakeDocumentProvider, PollContentProvider, PdfMakeBallotPaperProvider, MotionPartialContentProvider, PdfCreate, - PDFLayout, PersonalNoteManager, Messaging, FileSaver) { + PDFLayout, PersonalNoteManager, MotionComment, Messaging, FileSaver) { return { getDocumentProvider: function (motions, params, singleMotion) { params = _.clone(params || {}); // Clone this to avoid sideeffects. @@ -1008,24 +1016,20 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf']) }); }, exportComments: function (motion, filename) { - var fields = Config.get('motions_comments').value; - var canSeeComment = function (index) { - var specialComment = fields[index].forState || fields[index].forRecommendation; - return (fields[index].public || operator.hasPerms('motions.can_manage')) && !specialComment; - }; + var fields = MotionComment.getNoSpecialCommentsFields(); 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) { + _.forEach(fields, function (field, id) { + if (motion.comments[id]) { + var title = gettextCatalog.getString('Comment') + ' ' + field.name; + if (!field.public) { title += ' (' + gettextCatalog.getString('internal') + ')'; } content.push({ heading: title, - text: motion.comments[i], + text: motion.comments[id], }); } - } + }); MotionPartialContentProvider.createInstance(motion, content).then(function (contentProvider) { PdfMakeDocumentProvider.createInstance(contentProvider).then(function (documentProvider) { PdfCreate.download(documentProvider.getDocument(), filename); diff --git a/openslides/motions/static/js/motions/site.js b/openslides/motions/static/js/motions/site.js index 3070f019a..a7babaad1 100644 --- a/openslides/motions/static/js/motions/site.js +++ b/openslides/motions/static/js/motions/site.js @@ -910,11 +910,10 @@ angular.module('OpenSlidesApp.motions.site', [ $scope.alert = {}; // Motion comments - $scope.commentsFields = Config.get('motions_comments').value; - $scope.commentsFieldsNoSpecialComments = _.filter($scope.commentsFields, function (field) { - var specialComment = field.forState || field.forRecommendation; - return !specialComment; - }); + $scope.noSpecialCommentsFields = MotionComment.getNoSpecialCommentsFields(); + $scope.showCommentsFilter = function () { + return _.keys($scope.noSpecialCommentsFields).length > 0; + }; // collect all states and all recommendations of all workflows $scope.states = []; @@ -1289,13 +1288,10 @@ angular.module('OpenSlidesApp.motions.site', [ }); } }; - $scope.commentsFields = Config.get('motions_comments').value; - $scope.commentsFieldsNoSpecialComments = _.filter($scope.commentsFields, function (field) { - var specialComment = field.forState || field.forRecommendation; - return !specialComment; - }); - $scope.commentFieldForState = MotionComment.getFieldNameForFlag('forState'); - $scope.commentFieldForRecommendation = MotionComment.getFieldNameForFlag('forRecommendation'); + $scope.commentsFields = MotionComment.getCommentsFields(); + $scope.noSpecialCommentsFields = MotionComment.getNoSpecialCommentsFields(); + $scope.commentFieldForStateId = MotionComment.getFieldIdForFlag('forState'); + $scope.commentFieldForRecommendationId = MotionComment.getFieldIdForFlag('forRecommendation'); $scope.version = motion.active_version; $scope.isCollapsed = true; $scope.lineNumberMode = Config.get('motions_default_line_numbering').value; @@ -1402,14 +1398,14 @@ angular.module('OpenSlidesApp.motions.site', [ // save additional state field $scope.saveAdditionalStateField = function (stateExtension) { if (stateExtension) { - motion["comment " + $scope.commentFieldForState] = stateExtension; + motion['comment_' + $scope.commentFieldForStateId] = stateExtension; $scope.save(motion); } }; // save additional recommendation field $scope.saveAdditionalRecommendationField = function (recommendationExtension) { if (recommendationExtension) { - motion["comment " + $scope.commentFieldForRecommendation] = recommendationExtension; + motion['comment_' + $scope.commentFieldForRecommendationId] = recommendationExtension; $scope.save(motion); } }; @@ -1471,18 +1467,9 @@ angular.module('OpenSlidesApp.motions.site', [ $scope.showVersion({id: motion.active_version}); }); }; - // check if user is allowed to see at least one comment field - $scope.isAllowedToSeeCommentField = function () { - var isAllowed = false; - if ($scope.commentsFields.length > 0) { - isAllowed = operator.hasPerms('motions.can_see_and_manage_comments') || _.find( - $scope.commentsFields, - function(field) { - return field.public && !field.forState && !field.forRecommendation; - } - ); - } - return Boolean(isAllowed); + // check if there is at least one comment field + $scope.commentFieldsAvailable = function () { + return _.keys($scope.noSpecialCommentsFields).length > 0; }; // personal note // For pinning the personal note container we need to adjust the width with JS. We diff --git a/openslides/motions/static/templates/motions/motion-detail.html b/openslides/motions/static/templates/motions/motion-detail.html index 4419aae11..ccb39a83f 100644 --- a/openslides/motions/static/templates/motions/motion-detail.html +++ b/openslides/motions/static/templates/motions/motion-detail.html @@ -161,10 +161,10 @@