Motion comments inline editing

This commit is contained in:
FinnStutzenstein 2016-12-20 12:17:37 +01:00
parent 4a935bb641
commit b44b58393d
6 changed files with 228 additions and 128 deletions

View File

@ -58,6 +58,7 @@ Motions:
- Number of ballots printed can now be set in config.
- Add new personal settings to remove all whitespaces from motion identifier.
- Add new personal settings to allow amendments of amendments.
- Added inline editing for comments.
Elections:
- Added options to calculate percentages on different bases.

View File

@ -59,125 +59,180 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
.factory('MotionInlineEditing', [
'Editor',
'Motion',
'Config',
'$timeout',
'gettextCatalog',
function (Editor, Motion, Config, $timeout, gettextCatalog) {
var obj = {
active: false,
changed: false,
trivialChange: false,
originalHtml: null
};
var $scope, motion;
obj.init = function (_scope, _motion) {
$scope = _scope;
motion = _motion;
obj.ckeditorOptions = Editor.getOptions();
function (Editor, Motion, $timeout, gettextCatalog) {
var createInstance = function ($scope, motion, selector, versioning, getOriginalData, saveData) {
var obj = {
active: false,
changed: false,
isEditable: false,
trivialChange: false,
originalHtml: null,
ckeditorOptions: Editor.getOptions(),
};
obj.ckeditorOptions.readOnly = true;
obj.isEditable = false;
obj.changed = false;
};
obj.setVersion = function (_motion, versionId) {
motion = _motion; // If this is not updated,
obj.originalHtml = motion.getTextWithLineBreaks(versionId);
obj.changed = false;
obj.editor.setReadOnly(true);
if (obj.editor) {
obj.editor.setData(obj.originalHtml);
}
};
obj.setVersion = function (_motion, versionId) {
motion = _motion; // If this is not updated,
obj.originalHtml = motion.getTextWithLineBreaks(versionId);
obj.changed = false;
obj.editor.setReadOnly(true);
if (obj.editor) {
obj.editor.setData(obj.originalHtml);
}
};
obj.enable = function () {
if (motion.isAllowed('update')) {
obj.active = true;
obj.isEditable = true;
obj.ckeditorOptions.language = gettextCatalog.getCurrentLanguage();
obj.editor = CKEDITOR.inline('view-original-inline-editor', obj.ckeditorOptions);
obj.editor.on('change', function () {
$timeout(function() {
if (obj.editor.getData() != obj.originalHtml) {
obj.changed = true;
} else {
obj.changed = false;
obj.enable = function () {
if (motion.isAllowed('update')) {
obj.active = true;
obj.isEditable = true;
obj.ckeditorOptions.language = gettextCatalog.getCurrentLanguage();
obj.editor = CKEDITOR.inline(selector, obj.ckeditorOptions);
obj.editor.on('change', function () {
$timeout(function() {
if (obj.editor.getData() != obj.originalHtml) {
obj.changed = true;
} else {
obj.changed = false;
}
});
});
obj.revert();
} else {
obj.disable();
}
};
obj.disable = function () {
if (obj.editor) {
obj.editor.setReadOnly(true);
obj.editor.setData(obj.originalHtml, {
callback: function() {
obj.editor.destroy();
}
});
});
obj.revert();
} else {
obj.disable();
}
};
obj.disable = function () {
if (obj.editor) {
obj.editor.setReadOnly(true);
obj.editor.setData(obj.originalHtml, {
callback: function() {
obj.editor.destroy();
}
});
}
$timeout(function() {
obj.active = false;
obj.changed = false;
obj.isEditable = false;
});
};
// sets editor content to the initial motion state
obj.revert = function() {
if (obj.editor) {
obj.originalHtml = motion.getTextWithLineBreaks($scope.version);
obj.editor.setData(
motion.getTextWithLineBreaks($scope.version), {
callback: function() {
obj.originalHtml = obj.editor.getData();
obj.editor.setReadOnly(false);
$timeout(function() {
obj.changed = false;
});
$timeout(function () {
obj.editor.focus();
}, 100);
}
});
}
};
obj.save = function () {
if (!motion.isAllowed('update')) {
throw 'No permission to update motion';
}
motion.setTextStrippingLineBreaks(obj.editor.getData());
motion.disable_versioning = (obj.trivialChange && Config.get('motions_allow_disable_versioning').value);
Motion.inject(motion);
// save change motion object on server
Motion.save(motion, {method: 'PATCH'}).then(
function (success) {
$scope.showVersion(motion.getVersion(-1));
obj.revert();
},
function (error) {
// save error: revert all changes by restore
// (refresh) original motion object from server
Motion.refresh(motion);
obj.revert();
var message = '';
for (var e in error.data) {
message += e + ': ' + error.data[e] + ' ';
}
$scope.alert = {type: 'danger', msg: message, show: true};
}
);
};
$timeout(function() {
obj.active = false;
obj.changed = false;
obj.isEditable = false;
});
};
return obj;
// sets editor content to the initial motion state
obj.revert = function(originalData) {
if (obj.editor) {
obj.originalHtml = getOriginalData(obj);
obj.editor.setData(
getOriginalData(obj), {
callback: function() {
obj.originalHtml = obj.editor.getData();
obj.editor.setReadOnly(false);
$timeout(function() {
obj.changed = false;
});
$timeout(function () {
obj.editor.focus();
}, 100);
}
});
}
};
obj.save = function () {
if (!motion.isAllowed('update')) {
throw 'No permission to update motion';
}
saveData(obj);
Motion.inject(motion);
// save change motion object on server
Motion.save(motion, {method: 'PATCH'}).then(
function (success) {
if (versioning) {
$scope.showVersion(motion.getVersion(-1));
}
obj.revert();
},
function (error) {
// save error: revert all changes by restore
// (refresh) original motion object from server
Motion.refresh(motion);
obj.revert();
var message = '';
for (var e in error.data) {
message += e + ': ' + error.data[e] + ' ';
}
$scope.alert = {type: 'danger', msg: message, show: true};
}
);
};
return obj;
};
return {
createInstance: createInstance
};
}
])
.factory('MotionCommentsInlineEditing', [
'MotionInlineEditing',
function (MotionInlineEditing) {
var createInstances = function ($scope, motion) {
var commentsInlineEditing = {
editors: []
};
_.forEach($scope.commentsFields, function (field) {
var inlineEditing = MotionInlineEditing.createInstance($scope, motion,
'view-original-comment-inline-editor-' + field.name, false,
function (obj) {
return motion['comment ' + field.name];
},
function (obj) {
motion['comment ' + field.name] = obj.editor.getData();
}
);
commentsInlineEditing.editors.push(inlineEditing);
});
commentsInlineEditing.saveToolbarVisible = function () {
return _.some(commentsInlineEditing.editors, function (instance) {
return instance.changed && instance.active;
});
};
commentsInlineEditing.active = function () {
return _.some(commentsInlineEditing.editors, function (instance) {
return instance.active;
});
};
commentsInlineEditing.save = function () {
_.forEach(commentsInlineEditing.editors, function (instance) {
instance.save();
});
};
commentsInlineEditing.revert = function () {
_.forEach(commentsInlineEditing.editors, function (instance) {
instance.revert();
});
};
commentsInlineEditing.enable = function () {
_.forEach(commentsInlineEditing.editors, function (instance) {
instance.enable();
});
};
commentsInlineEditing.disable = function () {
_.forEach(commentsInlineEditing.editors, function (instance) {
instance.disable();
});
};
return commentsInlineEditing;
};
return {
createInstances: createInstances,
};
}
])

View File

@ -1100,12 +1100,13 @@ angular.module('OpenSlidesApp.motions.site', [
'Config',
'motion',
'MotionInlineEditing',
'MotionCommentsInlineEditing',
'Projector',
'ProjectionDefault',
function($scope, $http, operator, ngDialog, MotionForm,
ChangeRecommmendationCreate, ChangeRecommmendationView, MotionChangeRecommendation, MotionPDFExport,
Motion, MotionComment, Category, Mediafile, Tag, User, Workflow, Config, motion, MotionInlineEditing,
Projector, ProjectionDefault) {
MotionCommentsInlineEditing, Projector, ProjectionDefault) {
Category.bindAll({}, $scope, 'categories');
Mediafile.bindAll({}, $scope, 'mediafiles');
Tag.bindAll({}, $scope, 'tags');
@ -1345,8 +1346,17 @@ angular.module('OpenSlidesApp.motions.site', [
};
// Inline editing functions
$scope.inlineEditing = MotionInlineEditing;
$scope.inlineEditing.init($scope, motion);
$scope.inlineEditing = MotionInlineEditing.createInstance($scope, motion,
'view-original-text-inline-editor', true,
function (obj) {
return motion.getTextWithLineBreaks($scope.version);
},
function (obj) {
motion.setTextStrippingLineBreaks(obj.editor.getData());
motion.disable_versioning = (obj.trivialChange && Config.get('motions_allow_disable_versioning').value);
}
);
$scope.commentsInlineEditing = MotionCommentsInlineEditing.createInstances($scope, motion);
// Change recommendation creation functions
$scope.createChangeRecommendation = ChangeRecommmendationCreate;

View File

@ -485,16 +485,5 @@
</div>
</div>
<div class="details" ng-if="isAllowedToSeeCommentField()">
<h3 translate>Comments</h3>
<div ng-repeat="field in commentsFields">
<div ng-if="(field.public && !field.forState && !field.forRecommendation) ||
(operator.hasPerms('motions.can_see_and_manage_comments') && !field.forState && !field.forRecommendation)">
<h4>
{{ field.name }}
<span ng-if="!field.public" class="label label-warning" translate>internal</span>
</h4>
<div ng-bind-html="motion.comments[$index]"></div>
</div>
</div>
</div>
<!-- Comments section -->
<ng-include src="'static/templates/motions/motion-detail/comments.html'"></ng-include>

View File

@ -0,0 +1,45 @@
<div class="details" ng-if="isAllowedToSeeCommentField()">
<div class="row">
<!-- inline editing toolbar -->
<div class="motion-toolbar">
<div class="pull-right inline-editing-activator" ng-if="motion.isAllowed('update')">
<button ng-if="!commentsInlineEditing.active()" ng-click="commentsInlineEditing.enable()"
class="btn btn-sm btn-default">
<i class="fa fa-pencil-square-o"></i>
<translate>Inline editing</translate>
</button>
<button ng-if="commentsInlineEditing.active()" ng-click="commentsInlineEditing.disable()"
class="btn btn-sm btn-default">
<i class="fa fa-times-circle"></i>
<translate>Inline editing</translate>
</button>
</div>
<h3 class="toolbar-left" translate>Comments</h3>
</div>
<!-- comment fields -->
<div class="col-sm-12">
<div ng-repeat="field in commentsFields">
<div ng-if="(field.public && !field.forState && !field.forRecommendation) ||
(operator.hasPerms('motions.can_see_and_manage_comments') && !field.forState && !field.forRecommendation)">
<h4>
{{ field.name }}
<span ng-if="!field.public" class="label label-warning" translate>internal</span>
</h4>
<div id="view-original-comment-inline-editor-{{ field.name }}" style="min-height: 14px;"
ng-bind-html="motion.comments[$index] | trusted" contenteditable="{{ commentsInlineEditing.editors[$index].isEditable }}"></div>
</div>
</div>
<!-- save toolbar -->
<div class="motion-save-toolbar" ng-class="{ 'visible': commentsInlineEditing.saveToolbarVisible() }">
<div class="changed-hint" ng-if="commentsFields.length > 1" translate>The comments have been changed.</div>
<div class="changed-hint" ng-if="commentsFields.length == 1" translate>The comment has been changed.</div>
<button type="button" ng-click="commentsInlineEditing.save()" class="btn btn-primary" translate>
Save
</button>
<button type="button" ng-click="commentsInlineEditing.revert()" class="btn btn-primary" translate>
Revert
</button>
</div>
</div>
</div>
</div>

View File

@ -1,8 +1,8 @@
<!-- Original view -->
<div ng-if="viewChangeRecommendations.mode == 'original' && version == motion.getVersion(-1).id">
<div id="view-original-inline-editor" ng-bind-html="motion.getTextWithLineBreaks(version, highlight) | trusted"
<div id="view-original-text-inline-editor" ng-bind-html="motion.getTextWithLineBreaks(version, highlight) | trusted"
class="motion-text motion-text-original line-numbers-{{ lineNumberMode }}"
contenteditable= "{{ inlineEditing.isEditable }}">
contenteditable="{{ inlineEditing.isEditable }}">
</div>
<div class="motion-save-toolbar" ng-class="{ 'visible': (inlineEditing.changed && inlineEditing.active) }">
<div class="changed-hint" translate>The text has been changed.</div>