Change recommendations for titles
This commit is contained in:
parent
d073cbbf6f
commit
9f8dce6e34
@ -21,6 +21,7 @@ Agenda:
|
||||
Motions:
|
||||
- New export dialog [#3185].
|
||||
- New feature: Personal notes for motions [#3190, #3267, #3404].
|
||||
- New feature: Change recommendations for the title of a motion [#3626].
|
||||
- Fixed issue when creating/deleting motion comment fields in the
|
||||
settings [#3187].
|
||||
- Fixed empty motion comment field in motion update form [#3194].
|
||||
|
@ -280,8 +280,12 @@ class MotionChangeRecommendationSerializer(ModelSerializer):
|
||||
'text',
|
||||
'creation_time',)
|
||||
|
||||
def is_title_cr(self, data):
|
||||
return int(data['line_from']) == 0 and int(data['line_to']) == 0
|
||||
|
||||
def validate(self, data):
|
||||
if 'text' in data:
|
||||
# Change recommendations for titles are stored as plain-text, thus they don't need to be html-escaped
|
||||
if 'text' in data and not self.is_title_cr(data):
|
||||
data['text'] = validate_html(data['text'])
|
||||
return data
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
border-radius: 3px;
|
||||
margin-bottom: 5px;
|
||||
margin-top: -15px;
|
||||
padding-top: 5px;
|
||||
padding: 5px 5px 0 5px;
|
||||
|
||||
h3 {
|
||||
margin-top: 10px;
|
||||
|
@ -106,16 +106,12 @@
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
.line-numbers-outside .os-line-number.selectable:hover:before, .line-numbers-outside .os-line-number.selected:before {
|
||||
@mixin addChangeRecommendationBtn {
|
||||
cursor: pointer;
|
||||
content: "\f067";
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 0.25em;
|
||||
top: 4px;
|
||||
left: 43px;
|
||||
font-family: FontAwesome;
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
@ -124,6 +120,55 @@
|
||||
background-color: #337ab7;
|
||||
}
|
||||
|
||||
.line-numbers-outside .os-line-number.selectable:hover:before, .line-numbers-outside .os-line-number.selected:before {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 43px;
|
||||
display: inline-block;
|
||||
@include addChangeRecommendationBtn();
|
||||
}
|
||||
|
||||
.motion-header {
|
||||
.submenu {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
.motion-title {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
// Grab the left padding of the parent element to catch hover-events for the :before element
|
||||
margin-left: -20px;
|
||||
padding-left: 20px;
|
||||
|
||||
.change-title {
|
||||
position: relative;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
.change-title:before {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
left: -17px;
|
||||
@include addChangeRecommendationBtn();
|
||||
display: none;
|
||||
}
|
||||
&:hover .change-title.selectable:before {
|
||||
display: block;
|
||||
}
|
||||
.title-change-indicator {
|
||||
background-color: #0333ff;
|
||||
position: absolute;
|
||||
width: 4px;
|
||||
height: 32px;
|
||||
left: 10px;
|
||||
top: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.tt_change_recommendation_create_help {
|
||||
display: none;
|
||||
max-width: 150px;
|
||||
@ -190,6 +235,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
.diff-box-title {
|
||||
margin-bottom: 10px;
|
||||
.description {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.motion-text-with-diffs.line-numbers-inline .diff-box, .motion-text-with-diffs.line-numbers-none .diff-box {
|
||||
margin-right: -220px;
|
||||
}
|
||||
|
@ -272,6 +272,22 @@ angular.module('OpenSlidesApp.motions', [
|
||||
title += this.getTitle();
|
||||
return title;
|
||||
},
|
||||
getTitleWithChanges: function (changeRecommendationMode, versionId) {
|
||||
var titleChange = this.getTitleChangeRecommendation(versionId);
|
||||
var title;
|
||||
if (titleChange) {
|
||||
if (changeRecommendationMode === "changed") {
|
||||
title = titleChange.text;
|
||||
} else if (changeRecommendationMode === 'agreed' && !titleChange.rejected) {
|
||||
title = titleChange.text;
|
||||
} else {
|
||||
title = this.getTitle();
|
||||
}
|
||||
} else {
|
||||
title = this.getTitle();
|
||||
}
|
||||
return title;
|
||||
},
|
||||
getSequentialNumber: function () {
|
||||
var id = this.id + '';
|
||||
var zeros = Math.max(0, OpenSlidesSettings.MOTION_IDENTIFIER_MIN_DIGITS - id.length);
|
||||
@ -359,7 +375,7 @@ angular.module('OpenSlidesApp.motions', [
|
||||
_getTextWithChangeRecommendations: function (versionId, highlight, lineBreaks, statusCompareCb) {
|
||||
var lineLength = Config.get('motions_line_length').value,
|
||||
html = this.getVersion(versionId).text,
|
||||
changes = this.getChangeRecommendations(versionId, 'DESC');
|
||||
changes = this.getTextChangeRecommendations(versionId, 'DESC');
|
||||
|
||||
for (var i = 0; i < changes.length; i++) {
|
||||
var change = changes[i];
|
||||
@ -405,7 +421,7 @@ angular.module('OpenSlidesApp.motions', [
|
||||
}
|
||||
break;
|
||||
case 'diff':
|
||||
var changes = this.getChangeRecommendations(versionId, 'ASC');
|
||||
var changes = this.getTextChangeRecommendations(versionId, 'ASC');
|
||||
text = '';
|
||||
for (var i = 0; i < changes.length; i++) {
|
||||
text += this.getTextBetweenChangeRecommendations(versionId, (i === 0 ? null : changes[i - 1]), changes[i], highlight);
|
||||
@ -500,7 +516,7 @@ angular.module('OpenSlidesApp.motions', [
|
||||
}
|
||||
return foundSomething;
|
||||
},
|
||||
getChangeRecommendations: function (versionId, order) {
|
||||
getTextChangeRecommendations: function (versionId, order) {
|
||||
/*
|
||||
* Returns all change recommendations for this given version, sorted by line
|
||||
* @param versionId
|
||||
@ -516,8 +532,26 @@ angular.module('OpenSlidesApp.motions', [
|
||||
orderBy: [
|
||||
['line_from', order]
|
||||
]
|
||||
}).filter(function(change) {
|
||||
return change.isTextRecommendation();
|
||||
});
|
||||
},
|
||||
getTitleChangeRecommendation: function (versionId) {
|
||||
/**
|
||||
* Returns the change recommendation affecting the title, or null
|
||||
* @param versionId
|
||||
* @returns MotionChangeRecommendation|null
|
||||
*/
|
||||
versionId = versionId || this.active_version;
|
||||
var changes = MotionChangeRecommendation.filter({
|
||||
where: {
|
||||
motion_version_id: versionId,
|
||||
line_from: 0,
|
||||
line_to: 0
|
||||
}
|
||||
});
|
||||
return (changes.length > 0 ? changes[0] : null);
|
||||
},
|
||||
hasAmendments: function () {
|
||||
return DS.filter('motions/motion', {parent_id: this.id}).length > 0;
|
||||
},
|
||||
@ -861,6 +895,12 @@ angular.module('OpenSlidesApp.motions', [
|
||||
saveStatus: function() {
|
||||
this.DSSave();
|
||||
},
|
||||
isTitleRecommendation: function() {
|
||||
return (this.line_from === 0 && this.line_to === 0);
|
||||
},
|
||||
isTextRecommendation: function() {
|
||||
return (this.line_from !== 0 || this.line_to !== 0);
|
||||
},
|
||||
getDiff: function(motion, version, highlight) {
|
||||
var lineLength = Config.get('motions_line_length').value,
|
||||
html = lineNumberingService.insertLineNumbers(motion.getVersion(version).text, lineLength),
|
||||
|
@ -49,7 +49,7 @@ angular.module('OpenSlidesApp.motions.docx', ['OpenSlidesApp.core.docx'])
|
||||
|
||||
// motions
|
||||
data.tableofcontents_translation = gettextCatalog.getString('Table of contents');
|
||||
data.motions_list = getMotionShortData(motions);
|
||||
data.motions_list = getMotionShortData(motions, params);
|
||||
data.no_motions = gettextCatalog.getString('No motions available.');
|
||||
|
||||
return $q(function (resolve) {
|
||||
@ -77,11 +77,11 @@ angular.module('OpenSlidesApp.motions.docx', ['OpenSlidesApp.core.docx'])
|
||||
return _.orderBy(categories, [sortKey]);
|
||||
};
|
||||
|
||||
var getMotionShortData = function (motions) {
|
||||
var getMotionShortData = function (motions, params) {
|
||||
return _.map(motions, function (motion) {
|
||||
return {
|
||||
identifier: motion.identifier || '',
|
||||
title: motion.getTitle(),
|
||||
title: motion.getTitleWithChanges(params.changeRecommendationMode),
|
||||
};
|
||||
});
|
||||
};
|
||||
@ -97,6 +97,7 @@ angular.module('OpenSlidesApp.motions.docx', ['OpenSlidesApp.core.docx'])
|
||||
var sequential_enabled = Config.get('motions_export_sequential_number').value;
|
||||
// promises for create the actual motion data
|
||||
var promises = _.map(motions, function (motion) {
|
||||
var title = motion.getTitleWithChanges(params.changeRecommendationMode);
|
||||
var text = params.include.text ? motion.getTextByMode(params.changeRecommendationMode, null, null, false) : '';
|
||||
var reason = params.include.reason ? motion.getReason() : '';
|
||||
var comments = getMotionComments(motion, params.includeComments);
|
||||
@ -114,7 +115,7 @@ angular.module('OpenSlidesApp.motions.docx', ['OpenSlidesApp.core.docx'])
|
||||
// Actual data
|
||||
id: motion.id,
|
||||
identifier: motion.identifier || '',
|
||||
title: motion.getTitle(),
|
||||
title: title,
|
||||
submitters: params.include.submitters ? _.map(motion.submitters, function (submitter) {
|
||||
return submitter.get_full_name();
|
||||
}).join(', ') : '',
|
||||
|
@ -43,7 +43,7 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
|
||||
obj.editor = CKEDITOR.inline(selector, ckeditorOptions);
|
||||
obj.editor.on('change', function () {
|
||||
$timeout(function() {
|
||||
if (obj.editor.getData() != obj.originalHtml) {
|
||||
if (obj.editor.getData() !== obj.originalHtml) {
|
||||
obj.changed = true;
|
||||
} else {
|
||||
obj.changed = false;
|
||||
@ -183,11 +183,14 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
|
||||
|
||||
.factory('ChangeRecommendationCreate', [
|
||||
'ngDialog',
|
||||
'ChangeRecommendationForm',
|
||||
function(ngDialog, ChangeRecommendationForm) {
|
||||
'ChangeRecommendationTitleForm',
|
||||
'ChangeRecommendationTextForm',
|
||||
function(ngDialog, ChangeRecommendationTitleForm, ChangeRecommendationTextForm) {
|
||||
var MODE_INACTIVE = 0,
|
||||
MODE_SELECTING_FROM = 1,
|
||||
MODE_SELECTING_TO = 2;
|
||||
MODE_SELECTING_TO = 2,
|
||||
|
||||
TITLE_DUMMY_LINE_NUMBER = 0;
|
||||
|
||||
var obj = {
|
||||
mode: MODE_INACTIVE,
|
||||
@ -200,7 +203,7 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
|
||||
var $scope, motion, version;
|
||||
|
||||
obj._getAffectedLineNumbers = function () {
|
||||
var changeRecommendations = motion.getChangeRecommendations(version),
|
||||
var changeRecommendations = motion.getTextChangeRecommendations(version.id),
|
||||
affectedLines = [];
|
||||
for (var i = 0; i < changeRecommendations.length; i++) {
|
||||
var change = changeRecommendations[i];
|
||||
@ -211,23 +214,29 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
|
||||
return affectedLines;
|
||||
};
|
||||
|
||||
// startCreating is called right at the beginning after the users interacts with the text for the first time.
|
||||
// This ensures all necessary nodes have been initialized
|
||||
obj.startCreating = function () {
|
||||
if (obj.mode > MODE_SELECTING_FROM || !motion.isAllowed('can_manage')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$(".tt_change_recommendation_create_help").removeClass("opened");
|
||||
var $lineNumbers = $(".motion-text-original .os-line-number");
|
||||
var $lineNumbers = $(".motion-text-original .os-line-number"),
|
||||
$title = $(".motion-title .change-title");
|
||||
if ($lineNumbers.filter(".selectable").length === 0) {
|
||||
obj.mode = MODE_SELECTING_FROM;
|
||||
var alreadyAffectedLines = obj._getAffectedLineNumbers();
|
||||
$lineNumbers.each(function () {
|
||||
var $this = $(this),
|
||||
lineNumber = $this.data("line-number");
|
||||
if (alreadyAffectedLines.indexOf(lineNumber) == -1) {
|
||||
if (alreadyAffectedLines.indexOf(lineNumber) === -1) {
|
||||
$(this).addClass("selectable");
|
||||
}
|
||||
});
|
||||
if (alreadyAffectedLines.indexOf(TITLE_DUMMY_LINE_NUMBER) === -1) {
|
||||
$title.addClass("selectable");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -253,7 +262,7 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
|
||||
$(".motion-text-original .os-line-number").each(function () {
|
||||
var $this = $(this);
|
||||
if ($this.data("line-number") >= line && !foundCollission) {
|
||||
if (alreadyAffectedLines.indexOf($this.data("line-number")) == -1) {
|
||||
if (alreadyAffectedLines.indexOf($this.data("line-number")) === -1) {
|
||||
$(this).addClass("selectable");
|
||||
} else {
|
||||
$(this).removeClass("selectable");
|
||||
@ -268,18 +277,22 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
|
||||
$(".tt_change_recommendation_create_help").css("top", tt_pos).addClass("opened");
|
||||
};
|
||||
|
||||
obj.titleClicked = function () {
|
||||
ngDialog.open(ChangeRecommendationTitleForm.getCreateDialog(motion, version));
|
||||
|
||||
obj.mode = MODE_INACTIVE;
|
||||
obj.lineFrom = 0;
|
||||
obj.lineTo = 0;
|
||||
$(".motion-text-original .os-line-number").removeClass("selected selectable");
|
||||
obj.startCreating();
|
||||
};
|
||||
|
||||
obj.setToLine = function (line) {
|
||||
if (line < obj.lineFrom) {
|
||||
return;
|
||||
}
|
||||
obj.mode = MODE_INACTIVE;
|
||||
obj.lineTo = line + 1;
|
||||
ngDialog.open(ChangeRecommendationForm.getCreateDialog(
|
||||
motion,
|
||||
version,
|
||||
obj.lineFrom,
|
||||
obj.lineTo
|
||||
));
|
||||
ngDialog.open(ChangeRecommendationTextForm.getCreateDialog(motion, version, obj.lineFrom, line + 1));
|
||||
|
||||
obj.lineFrom = 0;
|
||||
obj.lineTo = 0;
|
||||
@ -288,19 +301,19 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
|
||||
};
|
||||
|
||||
obj.lineClicked = function (ev) {
|
||||
if (obj.mode == MODE_INACTIVE) {
|
||||
if (obj.mode === MODE_INACTIVE) {
|
||||
return;
|
||||
}
|
||||
if (obj.mode == MODE_SELECTING_FROM) {
|
||||
if (obj.mode === MODE_SELECTING_FROM) {
|
||||
obj.setFromLine($(ev.target).data("line-number"));
|
||||
$(ev.target).addClass("selected");
|
||||
} else if (obj.mode == MODE_SELECTING_TO) {
|
||||
} else if (obj.mode === MODE_SELECTING_TO) {
|
||||
obj.setToLine($(ev.target).data("line-number"));
|
||||
}
|
||||
};
|
||||
|
||||
obj.mouseOver = function (ev) {
|
||||
if (obj.mode != MODE_SELECTING_TO) {
|
||||
if (obj.mode !== MODE_SELECTING_TO) {
|
||||
return;
|
||||
}
|
||||
var hoverLine = $(ev.target).data("line-number");
|
||||
@ -316,31 +329,37 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
|
||||
|
||||
obj.setVersion = function (_motion, _version) {
|
||||
motion = _motion;
|
||||
version = _version;
|
||||
version = motion.getVersion(_version);
|
||||
};
|
||||
|
||||
obj.editDialog = function(change_recommendation) {
|
||||
ngDialog.open(ChangeRecommendationForm.getEditDialog(change_recommendation));
|
||||
obj.editTextDialog = function(change_recommendation) {
|
||||
ngDialog.open(ChangeRecommendationTextForm.getEditDialog(change_recommendation));
|
||||
};
|
||||
|
||||
obj.editTitleDialog = function(change_recommendation) {
|
||||
ngDialog.open(ChangeRecommendationTitleForm.getEditDialog(change_recommendation));
|
||||
};
|
||||
|
||||
obj.init = function (_scope, _motion) {
|
||||
$scope = _scope;
|
||||
motion = _motion;
|
||||
version = $scope.version;
|
||||
version = motion.getVersion($scope.version);
|
||||
|
||||
var $content = $("#content");
|
||||
$content.on("click", ".line-numbers-outside .os-line-number.selectable", obj.lineClicked);
|
||||
$content.on("click", ".motion-title .change-title.selectable", obj.titleClicked);
|
||||
$content.on("click", obj.cancelCreating);
|
||||
$content.on("mouseover", ".line-numbers-outside .os-line-number.selectable", obj.mouseOver);
|
||||
$content.on("mouseover", ".motion-text-original", obj.startCreating);
|
||||
$content.on("mouseover", ".motion-text-original, .motion-title", obj.startCreating);
|
||||
|
||||
$scope.$watch(function () {
|
||||
return $scope.change_recommendations.length;
|
||||
}, function () {
|
||||
if (obj.mode == MODE_INACTIVE || obj.mode == MODE_SELECTING_FROM) {
|
||||
if (obj.mode === MODE_INACTIVE || obj.mode === MODE_SELECTING_FROM) {
|
||||
// Recalculate the affected lines so we cannot select lines affected by a recommendation
|
||||
// that has just been created
|
||||
$(".motion-text-original .os-line-number").removeClass("selected selectable");
|
||||
$(".motion-title .change-title").removeClass("selected selectable");
|
||||
obj.startCreating();
|
||||
}
|
||||
});
|
||||
@ -353,9 +372,10 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
|
||||
obj.destroy = function () {
|
||||
var $content = $("#content");
|
||||
$content.off("click", ".line-numbers-outside .os-line-number.selectable", obj.lineClicked);
|
||||
$content.off("click", ".motion-title .change-title.selectable", obj.titleClicked);
|
||||
$content.off("click", obj.cancelCreating);
|
||||
$content.off("mouseover", ".line-numbers-outside .os-line-number.selectable", obj.mouseOver);
|
||||
$content.off("mouseover", ".motion-text-original", obj.startCreating);
|
||||
$content.off("mouseover", ".motion-text-original, .motion-title", obj.startCreating);
|
||||
};
|
||||
|
||||
return obj;
|
||||
|
@ -63,10 +63,8 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
|
||||
|
||||
// title
|
||||
var identifier = motion.identifier ? ' ' + motion.identifier : '';
|
||||
var title = PDFLayout.createTitle(
|
||||
gettextCatalog.getString('Motion') + identifier + ': ' +
|
||||
motion.getTitle(motionVersion)
|
||||
);
|
||||
var titlePlain = motion.getTitleWithChanges(params.changeRecommendationMode, motionVersion);
|
||||
var title = PDFLayout.createTitle(gettextCatalog.getString('Motion') + identifier + ': ' + titlePlain);
|
||||
|
||||
// subtitle and sequential number
|
||||
var subtitleLines = [];
|
||||
@ -251,10 +249,15 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
|
||||
}
|
||||
|
||||
// summary of change recommendations (for motion diff version only)
|
||||
if (params.changeRecommendationMode == "diff" && motion.changeRecommendations.length) {
|
||||
if (params.changeRecommendationMode === "diff" && motion.changeRecommendations.length) {
|
||||
var columnLineNumbers = [];
|
||||
var columnChangeType = [];
|
||||
angular.forEach(_.orderBy(motion.changeRecommendations, ['line_from']), function(change) {
|
||||
if (change.isTitleRecommendation()) {
|
||||
columnLineNumbers.push(
|
||||
gettextCatalog.getString('Title') + ': '
|
||||
);
|
||||
} else {
|
||||
// line numbers column
|
||||
var line;
|
||||
if (change.line_from >= change.line_to - 1) {
|
||||
@ -265,6 +268,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
|
||||
columnLineNumbers.push(
|
||||
gettextCatalog.getString('Line') + ' ' + line + ': '
|
||||
);
|
||||
}
|
||||
// change type column
|
||||
if (change.getType(motion.getVersion(motionVersion).text) === 0) {
|
||||
columnChangeType.push(gettextCatalog.getString("Replacement"));
|
||||
@ -322,7 +326,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
|
||||
var motionTitle = function() {
|
||||
if (params.include.text) {
|
||||
return [{
|
||||
text: motion.getTitle(motionVersion),
|
||||
text: titlePlain,
|
||||
style: 'heading3'
|
||||
}];
|
||||
}
|
||||
@ -338,10 +342,20 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
|
||||
}
|
||||
};
|
||||
|
||||
var escapeHtml = function(text) {
|
||||
return text.replace(/&/, "&").replace(/</, "<").replace(/>/, ">");
|
||||
};
|
||||
|
||||
// motion text (with line-numbers)
|
||||
var motionText = function() {
|
||||
if (params.include.text) {
|
||||
var motionTextContent = motion.getTextByMode(params.changeRecommendationMode, motionVersion);
|
||||
var motionTextContent = '';
|
||||
var titleChange = motion.getTitleChangeRecommendation();
|
||||
if (params.changeRecommendationMode === 'diff' && titleChange) {
|
||||
motionTextContent += '<p><strong>' + gettextCatalog.getString('New title') + ':</strong> ' +
|
||||
escapeHtml(titleChange.text) + '</p>';
|
||||
}
|
||||
motionTextContent += motion.getTextByMode(params.changeRecommendationMode, motionVersion);
|
||||
return converter.convertHTML(motionTextContent, params.lineNumberMode);
|
||||
}
|
||||
};
|
||||
|
@ -159,17 +159,88 @@ angular.module('OpenSlidesApp.motions.site', [
|
||||
}
|
||||
])
|
||||
|
||||
.factory('ChangeRecommendationForm', [
|
||||
.factory('ChangeRecommendationTitleForm', [
|
||||
'gettextCatalog',
|
||||
'Editor',
|
||||
'Config',
|
||||
function(gettextCatalog, Editor, Config) {
|
||||
function(gettextCatalog) {
|
||||
return {
|
||||
// ngDialog for motion form
|
||||
getCreateDialog: function (motion, version) {
|
||||
return {
|
||||
template: 'static/templates/motions/change-recommendation-form.html',
|
||||
controller: 'ChangeRecommendationTitleCreateCtrl',
|
||||
className: 'ngdialog-theme-default wide-form',
|
||||
closeByEscape: false,
|
||||
closeByDocument: false,
|
||||
resolve: {
|
||||
motion: function() {
|
||||
return motion;
|
||||
},
|
||||
version: function() {
|
||||
return version;
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
getEditDialog: function(change) {
|
||||
return {
|
||||
template: 'static/templates/motions/change-recommendation-form.html',
|
||||
controller: 'ChangeRecommendationTitleUpdateCtrl',
|
||||
className: 'ngdialog-theme-default wide-form',
|
||||
closeByEscape: false,
|
||||
closeByDocument: false,
|
||||
resolve: {
|
||||
change: function() {
|
||||
return change;
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
// angular-formly fields for motion form
|
||||
getFormFields: function () {
|
||||
return [
|
||||
{
|
||||
key: 'identifier',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Identifier')
|
||||
},
|
||||
hide: true
|
||||
},
|
||||
{
|
||||
key: 'motion_version_id',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('Motion')
|
||||
},
|
||||
hide: true
|
||||
},
|
||||
{
|
||||
key: 'text',
|
||||
type: 'input',
|
||||
templateOptions: {
|
||||
label: gettextCatalog.getString('New title'),
|
||||
required: false
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.factory('ChangeRecommendationTextForm', [
|
||||
'gettextCatalog',
|
||||
'Editor',
|
||||
'Config',
|
||||
function(gettextCatalog, Editor) {
|
||||
return {
|
||||
// ngDialog for motion form
|
||||
getCreateDialog: function (motion, version, lineFrom, lineTo) {
|
||||
return {
|
||||
template: 'static/templates/motions/change-recommendation-form.html',
|
||||
controller: 'ChangeRecommendationCreateCtrl',
|
||||
controller: 'ChangeRecommendationTextCreateCtrl',
|
||||
className: 'ngdialog-theme-default wide-form',
|
||||
closeByEscape: false,
|
||||
closeByDocument: false,
|
||||
@ -192,7 +263,7 @@ angular.module('OpenSlidesApp.motions.site', [
|
||||
getEditDialog: function(change) {
|
||||
return {
|
||||
template: 'static/templates/motions/change-recommendation-form.html',
|
||||
controller: 'ChangeRecommendationUpdateCtrl',
|
||||
controller: 'ChangeRecommendationTextUpdateCtrl',
|
||||
className: 'ngdialog-theme-default wide-form',
|
||||
closeByEscape: false,
|
||||
closeByDocument: false,
|
||||
@ -1362,9 +1433,19 @@ angular.module('OpenSlidesApp.motions.site', [
|
||||
$scope.$watch(function () {
|
||||
return MotionChangeRecommendation.lastModified();
|
||||
}, function () {
|
||||
$scope.change_recommendations = MotionChangeRecommendation.filter({
|
||||
$scope.change_recommendations = [];
|
||||
$scope.title_change_recommendation = null;
|
||||
MotionChangeRecommendation.filter({
|
||||
'where': {'motion_version_id': {'==': motion.active_version}}
|
||||
}).forEach(function(change) {
|
||||
if (change.isTextRecommendation()) {
|
||||
$scope.change_recommendations.push(change);
|
||||
}
|
||||
if (change.isTitleRecommendation()) {
|
||||
$scope.title_change_recommendation = change;
|
||||
}
|
||||
});
|
||||
|
||||
if ($scope.change_recommendations.length === 0) {
|
||||
$scope.setProjectionMode($scope.projectionModes[0]);
|
||||
}
|
||||
@ -1399,6 +1480,8 @@ angular.module('OpenSlidesApp.motions.site', [
|
||||
}
|
||||
webpageTitle += $scope.motion.getTitle();
|
||||
WebpageTitle.updateTitle(webpageTitle);
|
||||
|
||||
$scope.createChangeRecommendation.setVersion(motion, motion.active_version);
|
||||
});
|
||||
$scope.projectionModes = [
|
||||
{mode: 'original',
|
||||
@ -1783,27 +1866,26 @@ angular.module('OpenSlidesApp.motions.site', [
|
||||
}
|
||||
])
|
||||
|
||||
.controller('ChangeRecommendationUpdateCtrl', [
|
||||
.controller('ChangeRecommendationTitleUpdateCtrl', [
|
||||
'$scope',
|
||||
'MotionChangeRecommendation',
|
||||
'ChangeRecommendationForm',
|
||||
'ChangeRecommendationTitleForm',
|
||||
'diffService',
|
||||
'change',
|
||||
'ErrorMessage',
|
||||
function ($scope, MotionChangeRecommendation, ChangeRecommendationForm, diffService, change, ErrorMessage) {
|
||||
function ($scope, MotionChangeRecommendation, ChangeRecommendationTitleForm, diffService, change, ErrorMessage) {
|
||||
$scope.alert = {};
|
||||
$scope.model = angular.copy(change);
|
||||
|
||||
// get all form fields
|
||||
$scope.formFields = ChangeRecommendationForm.getFormFields(change.line_from, change.line_to);
|
||||
$scope.formFields = ChangeRecommendationTitleForm.getFormFields();
|
||||
// save motion
|
||||
$scope.save = function (change) {
|
||||
change.text = diffService.removeDuplicateClassesInsertedByCkeditor(change.text);
|
||||
// inject the changed change recommendation (copy) object back into DS store
|
||||
MotionChangeRecommendation.inject(change);
|
||||
// save changed change recommendation object on server
|
||||
MotionChangeRecommendation.save(change).then(
|
||||
function(success) {
|
||||
function() {
|
||||
$scope.closeThisDialog();
|
||||
},
|
||||
function (error) {
|
||||
@ -1815,22 +1897,87 @@ angular.module('OpenSlidesApp.motions.site', [
|
||||
}
|
||||
])
|
||||
|
||||
.controller('ChangeRecommendationCreateCtrl', [
|
||||
.controller('ChangeRecommendationTitleCreateCtrl', [
|
||||
'$scope',
|
||||
'Motion',
|
||||
'MotionChangeRecommendation',
|
||||
'ChangeRecommendationForm',
|
||||
'ChangeRecommendationTitleForm',
|
||||
'Config',
|
||||
'diffService',
|
||||
'motion',
|
||||
'version',
|
||||
function($scope, Motion, MotionChangeRecommendation, ChangeRecommendationTitleForm, Config, diffService, motion,
|
||||
version) {
|
||||
$scope.alert = {};
|
||||
|
||||
$scope.model = {
|
||||
text: version.title,
|
||||
motion_version_id: version.id
|
||||
};
|
||||
|
||||
// get all form fields
|
||||
$scope.formFields = ChangeRecommendationTitleForm.getFormFields();
|
||||
// save motion
|
||||
$scope.save = function (change) {
|
||||
change.line_from = 0;
|
||||
change.line_to = 0;
|
||||
MotionChangeRecommendation.create(change).then(
|
||||
function() {
|
||||
$scope.closeThisDialog();
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.controller('ChangeRecommendationTextUpdateCtrl', [
|
||||
'$scope',
|
||||
'MotionChangeRecommendation',
|
||||
'ChangeRecommendationTextForm',
|
||||
'diffService',
|
||||
'change',
|
||||
'ErrorMessage',
|
||||
function ($scope, MotionChangeRecommendation, ChangeRecommendationTextForm, diffService, change, ErrorMessage) {
|
||||
$scope.alert = {};
|
||||
$scope.model = angular.copy(change);
|
||||
|
||||
// get all form fields
|
||||
$scope.formFields = ChangeRecommendationTextForm.getFormFields(change.line_from, change.line_to);
|
||||
// save motion
|
||||
$scope.save = function (change) {
|
||||
change.text = diffService.removeDuplicateClassesInsertedByCkeditor(change.text);
|
||||
// inject the changed change recommendation (copy) object back into DS store
|
||||
MotionChangeRecommendation.inject(change);
|
||||
// save changed change recommendation object on server
|
||||
MotionChangeRecommendation.save(change).then(
|
||||
function() {
|
||||
$scope.closeThisDialog();
|
||||
},
|
||||
function (error) {
|
||||
MotionChangeRecommendation.refresh(change);
|
||||
$scope.alert = ErrorMessage.forAlert(error);
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
])
|
||||
|
||||
.controller('ChangeRecommendationTextCreateCtrl', [
|
||||
'$scope',
|
||||
'Motion',
|
||||
'MotionChangeRecommendation',
|
||||
'ChangeRecommendationTextForm',
|
||||
'Config',
|
||||
'diffService',
|
||||
'motion',
|
||||
'version',
|
||||
'lineFrom',
|
||||
'lineTo',
|
||||
function($scope, Motion, MotionChangeRecommendation, ChangeRecommendationForm, Config, diffService, motion,
|
||||
function($scope, Motion, MotionChangeRecommendation, ChangeRecommendationTextForm, Config, diffService, motion,
|
||||
version, lineFrom, lineTo) {
|
||||
$scope.alert = {};
|
||||
|
||||
var html = motion.getTextWithLineBreaks(version),
|
||||
var html = motion.getTextWithLineBreaks(version.id),
|
||||
lineData = diffService.extractRangeByLineNumbers(html, lineFrom, lineTo);
|
||||
|
||||
$scope.model = {
|
||||
@ -1838,17 +1985,17 @@ angular.module('OpenSlidesApp.motions.site', [
|
||||
lineData.html + lineData.innerContextEnd + lineData.outerContextEnd,
|
||||
line_from: lineFrom,
|
||||
line_to: lineTo,
|
||||
motion_version_id: version,
|
||||
motion_version_id: version.id,
|
||||
type: 0
|
||||
};
|
||||
|
||||
// get all form fields
|
||||
$scope.formFields = ChangeRecommendationForm.getFormFields(lineFrom, lineTo);
|
||||
$scope.formFields = ChangeRecommendationTextForm.getFormFields(lineFrom, lineTo);
|
||||
// save motion
|
||||
$scope.save = function (motion) {
|
||||
motion.text = diffService.removeDuplicateClassesInsertedByCkeditor(motion.text);
|
||||
MotionChangeRecommendation.create(motion).then(
|
||||
function(success) {
|
||||
function() {
|
||||
$scope.closeThisDialog();
|
||||
}
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div class="header">
|
||||
<div class="header motion-header">
|
||||
<div class="title">
|
||||
<div class="submenu">
|
||||
<a ui-sref="motions.motion.list" class="btn btn-sm btn-default">
|
||||
@ -59,12 +59,21 @@
|
||||
<translate>PDF</translate>
|
||||
</a>
|
||||
</div>
|
||||
<h1>
|
||||
{{ motion.getTitle() }}
|
||||
|
||||
<h1 class="motion-title">
|
||||
<span class="title-change-indicator"
|
||||
ng-if="viewChangeRecommendations.mode == 'original' && title_change_recommendation"
|
||||
ng-click="viewChangeRecommendations.scrollToDiffBox(title_change_recommendation.id)"></span>
|
||||
<span class="change-title"
|
||||
ng-if="motion.isAllowed('update') && viewChangeRecommendations.mode == 'original' && !title_change_recommendation"></span>
|
||||
|
||||
<span>{{ motion.getTitleWithChanges(viewChangeRecommendations.mode) }}</span>
|
||||
|
||||
<i class="fa pointer" ng-class="motion.personalNote.star ? 'fa-star' : 'fa-star-o'"
|
||||
ng-if="operator.user"
|
||||
title="{{ 'Set as favorite' | translate }}" ng-click="toggleStar()"></i>
|
||||
</h1>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<h2>
|
||||
|
@ -11,7 +11,16 @@
|
||||
<translate>Reject all change recommendations</translate>
|
||||
</button>
|
||||
|
||||
<ul ng-if="change_recommendations.length > 0">
|
||||
<ul ng-if="change_recommendations.length > 0 || title_change_recommendation">
|
||||
<li ng-if="title_change_recommendation">
|
||||
<a href='' ng-click="viewChangeRecommendations.scrollToDiffBox(title_change_recommendation.id)">
|
||||
<span class="line-number"><translate>Title</translate>:</span>
|
||||
<span class="operation"><translate>Replacement</translate></span>
|
||||
<span class="status">
|
||||
<translate ng-if="title_change_recommendation.rejected">Rejected</translate>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li ng-repeat="change in (changes = (change_recommendations | filter:{motion_version_id:version}:true | orderBy: 'line_from')) ">
|
||||
<a href='' ng-click="viewChangeRecommendations.scrollToDiffBox(change.id)">
|
||||
<span ng-if="change.line_from >= change.line_to - 1" class="line-number">
|
||||
@ -35,7 +44,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div ng-if="change_recommendations.length == 0" class="no-changes">
|
||||
<div ng-if="change_recommendations.length == 0 && !title_change_recommendation" class="no-changes">
|
||||
<translate>No change recommendations yet</translate>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -92,7 +92,7 @@
|
||||
</div>
|
||||
|
||||
<!-- View Modes (Original, Diff, Changed) -->
|
||||
<div class="motion-toolbar" ng-if="change_recommendations.length > 0">
|
||||
<div class="motion-toolbar" ng-if="change_recommendations.length > 0 || title_change_recommendation">
|
||||
<div class="toolbar-left">
|
||||
|
||||
<!-- change recommendations for resonsive size medium/large (button group) -->
|
||||
|
@ -1,4 +1,48 @@
|
||||
<div ng-if="viewChangeRecommendations.mode == 'diff'">
|
||||
<!-- The changed title -->
|
||||
<div ng-if="title_change_recommendation" ng-class="motion.isAllowed('can_manage') ? 'diff-box' : ''"
|
||||
class="diff-box-{{ title_change_recommendation.id }} diff-box-title clearfix">
|
||||
<div class="action-row" ng-if="motion.isAllowed('can_manage')">
|
||||
<div class="btn-group" data-toggle="buttons">
|
||||
<label class="btn btn-sm btn-default" ng-class="{active: !title_change_recommendation.rejected}"
|
||||
title="{{ 'Not rejected' | translate }}"
|
||||
ng-click="title_change_recommendation.rejected = false; title_change_recommendation.saveStatus();">
|
||||
<input type="radio" name="changeRecommendationRejected[{{ title_change_recommendation.id }}]" value="0"
|
||||
ng-change="title_change_recommendation.saveStatus()" ng-model="change.rejected"
|
||||
ng-checked="title_change_recommendation.rejected == false">
|
||||
<i class="fa fa-thumbs-up"></i>
|
||||
</label>
|
||||
<label class="btn btn-sm btn-default" ng-class="{active: title_change_recommendation.rejected}"
|
||||
title="{{ 'Rejected' | translate }}" ng-click="title_change_recommendation.rejected = true; title_change_recommendation.saveStatus();">
|
||||
<input type="radio" name="changeRecommendationRejected[{{ title_change_recommendation.id }}]" value="1"
|
||||
ng-change="title_change_recommendation.saveStatus()" ng-model="change.rejected"
|
||||
ng-checked="title_change_recommendation.rejected == true">
|
||||
<i class="fa fa-thumbs-down"></i>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-default btn-sm pull-right btn-delete"
|
||||
ng-bootbox-confirm="{{ 'Are you sure you want to delete this change recommendation?' | translate }}"
|
||||
ng-bootbox-confirm-action="viewChangeRecommendations.delete(title_change_recommendation.id)"
|
||||
title="{{ 'Delete' | translate }}">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-default btn-sm pull-right btn-edit"
|
||||
ng-click="createChangeRecommendation.editTitleDialog(title_change_recommendation)"
|
||||
title="{{ 'Edit' | translate }}">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="status-row" ng-if="!motion.isAllowed('can_manage') && title_change_recommendation.rejected">
|
||||
<translate>Rejected</translate>
|
||||
</div>
|
||||
|
||||
<div class="motion-text motion-text-diff line-numbers-{{ lineNumberMode }}">
|
||||
<div class="description"><translate>New title</translate>:</div>
|
||||
<div>{{ title_change_recommendation.text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- The actual diff view -->
|
||||
<div class="motion-text-with-diffs line-numbers-{{ lineNumberMode }}">
|
||||
@ -31,7 +75,7 @@
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-default btn-sm pull-right btn-edit" ng-click="createChangeRecommendation.editDialog(change)"
|
||||
<button class="btn btn-default btn-sm pull-right btn-edit" ng-click="createChangeRecommendation.editTextDialog(change)"
|
||||
title="{{ 'Edit' | translate }}">
|
||||
<i class="fa fa-pencil"></i>
|
||||
</button>
|
||||
|
Loading…
Reference in New Issue
Block a user