Merge pull request #2813 from FinnStutzenstein/MotionCommentInline
Motion comments inline editing
This commit is contained in:
commit
fb24ca0aba
@ -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.
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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>
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user