Motion comments inline editing
This commit is contained in:
parent
4a935bb641
commit
b44b58393d
@ -58,6 +58,7 @@ Motions:
|
|||||||
- Number of ballots printed can now be set in config.
|
- 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 remove all whitespaces from motion identifier.
|
||||||
- Add new personal settings to allow amendments of amendments.
|
- Add new personal settings to allow amendments of amendments.
|
||||||
|
- Added inline editing for comments.
|
||||||
|
|
||||||
Elections:
|
Elections:
|
||||||
- Added options to calculate percentages on different bases.
|
- Added options to calculate percentages on different bases.
|
||||||
|
@ -59,125 +59,180 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
|
|||||||
.factory('MotionInlineEditing', [
|
.factory('MotionInlineEditing', [
|
||||||
'Editor',
|
'Editor',
|
||||||
'Motion',
|
'Motion',
|
||||||
'Config',
|
|
||||||
'$timeout',
|
'$timeout',
|
||||||
'gettextCatalog',
|
'gettextCatalog',
|
||||||
function (Editor, Motion, Config, $timeout, gettextCatalog) {
|
function (Editor, Motion, $timeout, gettextCatalog) {
|
||||||
var obj = {
|
var createInstance = function ($scope, motion, selector, versioning, getOriginalData, saveData) {
|
||||||
active: false,
|
var obj = {
|
||||||
changed: false,
|
active: false,
|
||||||
trivialChange: false,
|
changed: false,
|
||||||
originalHtml: null
|
isEditable: false,
|
||||||
};
|
trivialChange: false,
|
||||||
|
originalHtml: null,
|
||||||
var $scope, motion;
|
ckeditorOptions: Editor.getOptions(),
|
||||||
|
};
|
||||||
obj.init = function (_scope, _motion) {
|
|
||||||
$scope = _scope;
|
|
||||||
motion = _motion;
|
|
||||||
obj.ckeditorOptions = Editor.getOptions();
|
|
||||||
obj.ckeditorOptions.readOnly = true;
|
obj.ckeditorOptions.readOnly = true;
|
||||||
obj.isEditable = false;
|
|
||||||
obj.changed = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
obj.setVersion = function (_motion, versionId) {
|
obj.setVersion = function (_motion, versionId) {
|
||||||
motion = _motion; // If this is not updated,
|
motion = _motion; // If this is not updated,
|
||||||
obj.originalHtml = motion.getTextWithLineBreaks(versionId);
|
obj.originalHtml = motion.getTextWithLineBreaks(versionId);
|
||||||
obj.changed = false;
|
obj.changed = false;
|
||||||
obj.editor.setReadOnly(true);
|
obj.editor.setReadOnly(true);
|
||||||
if (obj.editor) {
|
if (obj.editor) {
|
||||||
obj.editor.setData(obj.originalHtml);
|
obj.editor.setData(obj.originalHtml);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
obj.enable = function () {
|
obj.enable = function () {
|
||||||
if (motion.isAllowed('update')) {
|
if (motion.isAllowed('update')) {
|
||||||
obj.active = true;
|
obj.active = true;
|
||||||
obj.isEditable = true;
|
obj.isEditable = true;
|
||||||
obj.ckeditorOptions.language = gettextCatalog.getCurrentLanguage();
|
obj.ckeditorOptions.language = gettextCatalog.getCurrentLanguage();
|
||||||
obj.editor = CKEDITOR.inline('view-original-inline-editor', obj.ckeditorOptions);
|
obj.editor = CKEDITOR.inline(selector, obj.ckeditorOptions);
|
||||||
obj.editor.on('change', function () {
|
obj.editor.on('change', function () {
|
||||||
$timeout(function() {
|
$timeout(function() {
|
||||||
if (obj.editor.getData() != obj.originalHtml) {
|
if (obj.editor.getData() != obj.originalHtml) {
|
||||||
obj.changed = true;
|
obj.changed = true;
|
||||||
} else {
|
} else {
|
||||||
obj.changed = false;
|
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',
|
'Config',
|
||||||
'motion',
|
'motion',
|
||||||
'MotionInlineEditing',
|
'MotionInlineEditing',
|
||||||
|
'MotionCommentsInlineEditing',
|
||||||
'Projector',
|
'Projector',
|
||||||
'ProjectionDefault',
|
'ProjectionDefault',
|
||||||
function($scope, $http, operator, ngDialog, MotionForm,
|
function($scope, $http, operator, ngDialog, MotionForm,
|
||||||
ChangeRecommmendationCreate, ChangeRecommmendationView, MotionChangeRecommendation, MotionPDFExport,
|
ChangeRecommmendationCreate, ChangeRecommmendationView, MotionChangeRecommendation, MotionPDFExport,
|
||||||
Motion, MotionComment, Category, Mediafile, Tag, User, Workflow, Config, motion, MotionInlineEditing,
|
Motion, MotionComment, Category, Mediafile, Tag, User, Workflow, Config, motion, MotionInlineEditing,
|
||||||
Projector, ProjectionDefault) {
|
MotionCommentsInlineEditing, Projector, ProjectionDefault) {
|
||||||
Category.bindAll({}, $scope, 'categories');
|
Category.bindAll({}, $scope, 'categories');
|
||||||
Mediafile.bindAll({}, $scope, 'mediafiles');
|
Mediafile.bindAll({}, $scope, 'mediafiles');
|
||||||
Tag.bindAll({}, $scope, 'tags');
|
Tag.bindAll({}, $scope, 'tags');
|
||||||
@ -1345,8 +1346,17 @@ angular.module('OpenSlidesApp.motions.site', [
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Inline editing functions
|
// Inline editing functions
|
||||||
$scope.inlineEditing = MotionInlineEditing;
|
$scope.inlineEditing = MotionInlineEditing.createInstance($scope, motion,
|
||||||
$scope.inlineEditing.init($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
|
// Change recommendation creation functions
|
||||||
$scope.createChangeRecommendation = ChangeRecommmendationCreate;
|
$scope.createChangeRecommendation = ChangeRecommmendationCreate;
|
||||||
|
@ -485,16 +485,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="details" ng-if="isAllowedToSeeCommentField()">
|
<!-- Comments section -->
|
||||||
<h3 translate>Comments</h3>
|
<ng-include src="'static/templates/motions/motion-detail/comments.html'"></ng-include>
|
||||||
<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>
|
|
||||||
|
@ -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 -->
|
<!-- Original view -->
|
||||||
<div ng-if="viewChangeRecommendations.mode == 'original' && version == motion.getVersion(-1).id">
|
<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 }}"
|
class="motion-text motion-text-original line-numbers-{{ lineNumberMode }}"
|
||||||
contenteditable= "{{ inlineEditing.isEditable }}">
|
contenteditable="{{ inlineEditing.isEditable }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="motion-save-toolbar" ng-class="{ 'visible': (inlineEditing.changed && inlineEditing.active) }">
|
<div class="motion-save-toolbar" ng-class="{ 'visible': (inlineEditing.changed && inlineEditing.active) }">
|
||||||
<div class="changed-hint" translate>The text has been changed.</div>
|
<div class="changed-hint" translate>The text has been changed.</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user