Merge pull request #2796 from FinnStutzenstein/Issue2548

Project change recommendations
This commit is contained in:
Emanuel Schütze 2017-01-09 09:27:46 +01:00 committed by GitHub
commit 6502f36a90
17 changed files with 304 additions and 24 deletions

View File

@ -38,6 +38,7 @@ Motions:
motion text. motion text.
- Allowed to add change recommendations for special motion text lines - Allowed to add change recommendations for special motion text lines
(with diff mode). (with diff mode).
- Added projection support for change recommendations.
- Added button to sort and number all motions in a category. - Added button to sort and number all motions in a category.
- Added recommendations for motions. - Added recommendations for motions.
- Added options to calculate percentages on different bases. - Added options to calculate percentages on different bases.

View File

@ -28,7 +28,7 @@
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu" role="menu" aria-labelledby="split-button"> <ul class="dropdown-menu" role="menu" aria-labelledby="split-button">
<li role="menuitem" ng-repeat="projector in projectors"> <li role="menuitem" ng-repeat="projector in projectors | orderBy:'id'">
<a href="" ng-click="projectCurrentLoS(projector.id)" <a href="" ng-click="projectCurrentLoS(projector.id)"
ng-class="{ 'projected': inArray(isCurrentLoSProjected(), projector.id) }"> ng-class="{ 'projected': inArray(isCurrentLoSProjected(), projector.id) }">
<i class="fa fa-video-camera" ng-show="inArray(isCurrentLoSProjected(), projector.id) "></i> <i class="fa fa-video-camera" ng-show="inArray(isCurrentLoSProjected(), projector.id) "></i>

View File

@ -27,7 +27,7 @@
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu" ng-if="projectors.length > 1"> <ul class="dropdown-menu" ng-if="projectors.length > 1">
<li role="menuitem" ng-repeat="projector in projectors"> <li role="menuitem" ng-repeat="projector in projectors | orderBy:'id'">
<a href="" ng-click="item.projectListOfSpeakers(projector.id)" <a href="" ng-click="item.projectListOfSpeakers(projector.id)"
ng-class="{ 'projected': inArray(item.isListOfSpeakersProjected(), projector.id) }"> ng-class="{ 'projected': inArray(item.isListOfSpeakersProjected(), projector.id) }">
<i class="fa fa-video-camera" ng-show="inArray(item.isListOfSpeakersProjected(), projector.id) "></i> <i class="fa fa-video-camera" ng-show="inArray(item.isListOfSpeakersProjected(), projector.id) "></i>

View File

@ -44,7 +44,7 @@
</a> </a>
</li> </li>
<li class="divider" ng-show="agendaHasSubitems && projectors.length > 1"></li> <li class="divider" ng-show="agendaHasSubitems && projectors.length > 1"></li>
<li role="menuitem" ng-repeat="projector in projectors" ng-show="projectors.length > 1"> <li role="menuitem" ng-repeat="projector in projectors | orderBy:'id'" ng-show="projectors.length > 1">
<a href="" ng-click="projectAgenda(projectorId=projector.id, tree=mainListTree)" <a href="" ng-click="projectAgenda(projectorId=projector.id, tree=mainListTree)"
ng-class="{ 'projected': inArray(isAgendaProjected(mainListTree), projector.id) }"> ng-class="{ 'projected': inArray(isAgendaProjected(mainListTree), projector.id) }">
<i class="fa fa-video-camera" ng-show="inArray(isAgendaProjected(mainListTree), projector.id) "></i> <i class="fa fa-video-camera" ng-show="inArray(isAgendaProjected(mainListTree), projector.id) "></i>
@ -245,7 +245,7 @@
</a> </a>
</li> </li>
<li class="divider" ng-show="item.hasSubitems(items)"></li> <li class="divider" ng-show="item.hasSubitems(items)"></li>
<li role="menuitem" ng-repeat="projector in projectors"> <li role="menuitem" ng-repeat="projector in projectors | orderBy:'id'">
<a href="" ng-click="item.project(projector.id, item.tree)" <a href="" ng-click="item.project(projector.id, item.tree)"
ng-class="{ 'projected': inArray(item.isProjected(item.tree), projector.id) }"> ng-class="{ 'projected': inArray(item.isProjected(item.tree), projector.id) }">
<i class="fa fa-video-camera" ng-show="inArray(item.isProjected(item.tree), projector.id)"></i> <i class="fa fa-video-camera" ng-show="inArray(item.isProjected(item.tree), projector.id)"></i>

View File

@ -623,7 +623,6 @@ img {
color: grey; color: grey;
} }
.diff-box { .diff-box {
background-color: #f9f9f9; background-color: #f9f9f9;
border: solid 1px #eee; border: solid 1px #eee;

View File

@ -433,7 +433,14 @@ tr.elected td {
.motion-text .highlight { .motion-text .highlight {
background-color: #ff0; background-color: #ff0;
} }
.motion-text ins {
color: green;
text-decoration: underline;
}
.motion-text del {
color: red;
text-decoration: line-through;
}
.motion-text.line-numbers-outside { .motion-text.line-numbers-outside {
padding-left: 0; padding-left: 0;
margin-left: 25px; margin-left: 25px;
@ -478,6 +485,122 @@ tr.elected td {
visibility: hidden; visibility: hidden;
} }
/** Diff view */
.change-recommendation-overview {
background-color: #eee;
border: solid 1px #ddd;
border-radius: 3px;
margin-bottom: 5px;
margin-top: -15px;
padding-top: 5px;
}
.change-recommendation-overview {
margin-bottom: 50px;
padding: 10px;
}
.change-recommendation-overview h2 {
margin-top: 0;
}
.change-recommendation-overview ul {
list-style: none;
display: table;
}
.change-recommendation-overview li {
display: table-row;
cursor: pointer;
}
.change-recommendation-overview li:hover {
text-decoration: underline;
}
.change-recommendation-overview li > * {
display: table-cell;
padding: 4px;
}
.change-recommendation-overview .status {
color: gray;
font-style: italic;
}
.change-recommendation-overview .status > *:before {
content: '(';
}
.change-recommendation-overview .status > *:after {
content: ')';
}
.change-recommendation-overview .no-changes {
font-style: italic;
color: grey;
}
.diff-box {
background-color: #f9f9f9;
border: solid 1px #eee;
border-radius: 3px;
margin-bottom: 0;
margin-top: -25px;
padding-top: 0;
padding-right: 155px;
}
.motion-text-with-diffs .original-text {
min-height: 30px; // Spacer between .diff-box, in case .original-text is empty
}
.motion-text-with-diffs .original-text ul:last-child {
padding-bottom: 16px;
}
.motion-text-with-diffs.line-numbers-inline .diff-box, .motion-text-with-diffs.line-numbers-none .diff-box {
margin-right: -220px;
}
.diff-box:hover {
background-color: #f0f0f0;
}
.diff-box .action-row {
font-size: 0.8em;
padding-top: 5px;
padding-bottom: 5px;
float: right;
width: 150px;
text-align: right;
margin-right: -150px;
opacity: 0.5;
}
.diff-box:hover .action-row {
opacity: 1;
}
.diff-box .action-row .btn-delete {
margin-left: 5px;
color: red;
}
.diff-box .action-row .btn-edit {
margin-left: 5px;
}
.diff-box .status-row {
font-style: italic;
color: gray;
}
.diff-box .status-row > *:after {
content: ':';
}
.motion-text-diff .delete {
color: red;
text-decoration: line-through;
}
.motion-text-diff .insert {
color: green;
text-decoration: underline;
}
.motion-text-diff p {
padding-bottom: 0;
margin-top: 0;
margin-bottom: 0;
}
.motion-text-diff.line-numbers-outside .insert .os-line-number {
display: none;
}
.motion-text-diff.line-numbers-inline .insert .os-line-number {
display: none;
}
/*** Video and Image projection ***/ /*** Video and Image projection ***/
img.projector-image { img.projector-image {
width: 100%; width: 100%;

View File

@ -1021,6 +1021,7 @@ angular.module('OpenSlidesApp.core.site', [
'$interval', '$interval',
'$state', '$state',
'$q', '$q',
'$filter',
'Config', 'Config',
'Projector', 'Projector',
'CurrentListOfSpeakersItem', 'CurrentListOfSpeakersItem',
@ -1031,7 +1032,7 @@ angular.module('OpenSlidesApp.core.site', [
'gettextCatalog', 'gettextCatalog',
'ngDialog', 'ngDialog',
'ProjectorMessageForm', 'ProjectorMessageForm',
function($scope, $http, $interval, $state, $q, Config, Projector, CurrentListOfSpeakersItem, function($scope, $http, $interval, $state, $q, $filter, Config, Projector, CurrentListOfSpeakersItem,
ListOfSpeakersOverlay, ProjectionDefault, ProjectorMessage, Countdown, gettextCatalog, ListOfSpeakersOverlay, ProjectionDefault, ProjectorMessage, Countdown, gettextCatalog,
ngDialog, ProjectorMessageForm) { ngDialog, ProjectorMessageForm) {
ProjectorMessage.bindAll({}, $scope, 'messages'); ProjectorMessage.bindAll({}, $scope, 'messages');
@ -1072,7 +1073,7 @@ angular.module('OpenSlidesApp.core.site', [
}, function () { }, function () {
$scope.projectors = Projector.getAll(); $scope.projectors = Projector.getAll();
if (!$scope.active_projector) { if (!$scope.active_projector) {
$scope.changeProjector($scope.projectors[0]); $scope.changeProjector($filter('orderBy')($scope.projectors, 'id')[0]);
} }
$scope.messageDefaultProjectorId = ProjectionDefault.filter({name: 'messages'})[0].projector_id; $scope.messageDefaultProjectorId = ProjectionDefault.filter({name: 'messages'})[0].projector_id;

View File

@ -17,7 +17,7 @@
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu" uib-dropdown-menu aria-labelledby="menuListOfSpeakers"> <ul class="dropdown-menu" uib-dropdown-menu aria-labelledby="menuListOfSpeakers">
<li ng-repeat="projector in projectors"> <li ng-repeat="projector in projectors | orderBy:'id'">
<a href ng-click="setListOfSpeakers(projector)"> <a href ng-click="setListOfSpeakers(projector)">
<i class="fa fa-check" ng-if="projector.id == currentListOfSpeakers"></i> <i class="fa fa-check" ng-if="projector.id == currentListOfSpeakers"></i>
{{ projector.name | translate }} {{ projector.name | translate }}

View File

@ -38,7 +38,7 @@
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu dropdown-entries" aria-labelledby="menuProjector"> <ul class="dropdown-menu dropdown-entries" aria-labelledby="menuProjector">
<li ng-repeat="projector in projectors"> <li ng-repeat="projector in projectors | orderBy:'id'">
<a href ng-class="{'projected': projector === active_projector}" <a href ng-class="{'projected': projector === active_projector}"
ng-click="changeProjector(projector)"> ng-click="changeProjector(projector)">
<i ng-show="projector === active_projector" class="fa fa-check"></i> <i ng-show="projector === active_projector" class="fa fa-check"></i>

View File

@ -15,7 +15,7 @@
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu" ng-if="projectors.length > 1"> <ul class="dropdown-menu" ng-if="projectors.length > 1">
<li role="menuitem" ng-repeat="projector in projectors"> <li role="menuitem" ng-repeat="projector in projectors | orderBy:'id'">
<a href="" ng-click="model.project(projector.id, arg)" <a href="" ng-click="model.project(projector.id, arg)"
ng-class="{ 'projected': inArray(model.isProjected(arg), projector.id) }"> ng-class="{ 'projected': inArray(model.isProjected(arg), projector.id) }">
<i class="fa fa-video-camera" ng-show="inArray(model.isProjected(arg), projector.id)"></i> <i class="fa fa-video-camera" ng-show="inArray(model.isProjected(arg), projector.id)"></i>

View File

@ -265,7 +265,7 @@
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu" ng-if="projectors.length > 1"> <ul class="dropdown-menu" ng-if="projectors.length > 1">
<li role="menuitem" ng-repeat="projector in projectors"> <li role="menuitem" ng-repeat="projector in projectors | orderBy:'id'">
<a href="" ng-click="showMediafile(projector.id, mediafile)" <a href="" ng-click="showMediafile(projector.id, mediafile)"
ng-class="{ 'projected': inArray(mediafile.isProjected(), projector.id) }"> ng-class="{ 'projected': inArray(mediafile.isProjected(), projector.id) }">
<i class="fa fa-video-camera" ng-show="inArray(mediafile.isProjected(), projector.id) "></i> <i class="fa fa-video-camera" ng-show="inArray(mediafile.isProjected(), projector.id) "></i>

View File

@ -1,7 +1,7 @@
from ..core.exceptions import ProjectorException from ..core.exceptions import ProjectorException
from ..utils.collection import CollectionElement from ..utils.collection import CollectionElement
from ..utils.projector import ProjectorElement from ..utils.projector import ProjectorElement
from .models import Motion, MotionBlock from .models import Motion, MotionBlock, MotionChangeRecommendation
class MotionSlide(ProjectorElement): class MotionSlide(ProjectorElement):
@ -26,6 +26,7 @@ class MotionSlide(ProjectorElement):
yield motion.state.workflow yield motion.state.workflow
yield from motion.submitters.all() yield from motion.submitters.all()
yield from motion.supporters.all() yield from motion.supporters.all()
yield from MotionChangeRecommendation.objects.filter(motion_version=motion.get_active_version().id)
def get_collection_elements_required_for_this(self, collection_element, config_entry): def get_collection_elements_required_for_this(self, collection_element, config_entry):
output = super().get_collection_elements_required_for_this(collection_element, config_entry) output = super().get_collection_elements_required_for_this(collection_element, config_entry)

View File

@ -191,6 +191,7 @@ angular.module('OpenSlidesApp.motions', [
.factory('Motion', [ .factory('Motion', [
'DS', 'DS',
'$http',
'MotionPoll', 'MotionPoll',
'MotionChangeRecommendation', 'MotionChangeRecommendation',
'MotionComment', 'MotionComment',
@ -201,8 +202,9 @@ angular.module('OpenSlidesApp.motions', [
'Config', 'Config',
'lineNumberingService', 'lineNumberingService',
'diffService', 'diffService',
function(DS, MotionPoll, MotionChangeRecommendation, MotionComment, jsDataModel, gettext, gettextCatalog, operator, Config, 'Projector',
lineNumberingService, diffService) { function(DS, $http, MotionPoll, MotionChangeRecommendation, MotionComment, jsDataModel, gettext, gettextCatalog,
operator, Config, lineNumberingService, diffService, Projector) {
var name = 'motions/motion'; var name = 'motions/motion';
return DS.defineResource({ return DS.defineResource({
name: name, name: name,
@ -469,7 +471,43 @@ angular.module('OpenSlidesApp.motions', [
default: default:
return false; return false;
} }
} },
// Overrides from jsDataModel factory
project: function (projectorId, mode) {
// if this object is already projected on projectorId, delete this element from this projector
var isProjectedIds = this.isProjected(mode);
_.forEach(isProjectedIds, function (id) {
$http.post('/rest/core/projector/' + id + '/clear_elements/');
});
mode = mode || 'original';
// Show the element, if it was not projected before on the given projector
if (_.indexOf(isProjectedIds, projectorId) === -1) {
return $http.post(
'/rest/core/projector/' + projectorId + '/prune_elements/',
[{name: name,
id: this.id,
mode: mode}]
);
}
},
isProjected: function (mode) {
var self = this;
var predicate = function (element) {
var value = element.name === name &&
element.id === self.id;
if (mode) {
value = value && (element.mode === mode);
}
return value;
};
var projectorIds = [];
_.forEach(Projector.getAll(), function (projector) {
if (typeof _.findKey(projector.elements, predicate) === 'string') {
projectorIds.push(projector.id);
}
});
return projectorIds;
},
}, },
relations: { relations: {
belongsTo: { belongsTo: {

View File

@ -21,12 +21,13 @@ angular.module('OpenSlidesApp.motions.projector', [
'$rootScope', '$rootScope',
'$http', '$http',
'Motion', 'Motion',
'MotionChangeRecommendation',
'User', 'User',
'Config', 'Config',
'Projector', 'Projector',
'$timeout', '$timeout',
'ProjectorID', 'ProjectorID',
function($scope, $rootScope, $http, Motion, User, Config, Projector, $timeout, ProjectorID) { function($scope, $rootScope, $http, Motion, MotionChangeRecommendation, User, Config, Projector, $timeout, ProjectorID) {
// Attention! Each object that is used here has to be dealt on server side. // Attention! Each object that is used here has to be dealt on server side.
// Add it to the coresponding get_requirements method of the ProjectorElement // Add it to the coresponding get_requirements method of the ProjectorElement
// class. // class.
@ -58,9 +59,11 @@ angular.module('OpenSlidesApp.motions.projector', [
} }
}; };
$scope.mode = $scope.element.mode || 'original';
Motion.bindOne(id, $scope, 'motion'); Motion.bindOne(id, $scope, 'motion');
User.bindAll({}, $scope, 'users'); User.bindAll({}, $scope, 'users');
MotionChangeRecommendation.bindAll({}, $scope, 'change_recommendations');
} }
]); ]);

View File

@ -1116,6 +1116,7 @@ angular.module('OpenSlidesApp.motions.site', [
$scope.$watch(function () { $scope.$watch(function () {
return Projector.lastModified(); return Projector.lastModified();
}, function () { }, function () {
$scope.projectors = Projector.getAll();
$scope.defaultProjectorId = ProjectionDefault.filter({name: 'motions'})[0].projector_id; $scope.defaultProjectorId = ProjectionDefault.filter({name: 'motions'})[0].projector_id;
}); });
$scope.$watch(function () { $scope.$watch(function () {
@ -1124,6 +1125,44 @@ angular.module('OpenSlidesApp.motions.site', [
$scope.motion = Motion.get(motion.id); $scope.motion = Motion.get(motion.id);
MotionComment.populateFields($scope.motion); MotionComment.populateFields($scope.motion);
}); });
$scope.projectionModes = [
{mode: 'original',
label: 'Original version'},
{mode: 'changed',
label: 'Changed version'},
{mode: 'diff',
label: 'Diff version'},
{mode: 'agreed',
label: 'Resolution'},
];
var getProjectionMode = function () {
var projectedIds = motion.isProjected();
if (projectedIds.length) {
var element = _.find(Projector.get(projectedIds[0]).elements, function (element) {
return element.name === 'motions/motion' && element.id === motion.id;
});
var modeName = element.mode || 'original', mode;
_.forEach($scope.projectionModes, function (_mode) {
if (_mode.mode === modeName) {
mode = _mode;
}
});
return mode || $scope.projectionModes[0];
} else {
return $scope.projectionModes[0];
}
};
$scope.projectionMode = getProjectionMode();
$scope.setProjectionMode = function (mode, event) {
$scope.projectionMode = mode;
var projectedIds = motion.isProjected();
_.forEach(projectedIds, function (id) {
motion.project(id, mode.mode);
});
event.stopPropagation();
};
$scope.commentsFields = Config.get('motions_comments').value; $scope.commentsFields = Config.get('motions_comments').value;
$scope.commentFieldForState = MotionComment.getFieldNameForFlag('forState'); $scope.commentFieldForState = MotionComment.getFieldNameForFlag('forState');
$scope.commentFieldForRecommendation = MotionComment.getFieldNameForFlag('forRecommendation'); $scope.commentFieldForRecommendation = MotionComment.getFieldNameForFlag('forRecommendation');

View File

@ -11,9 +11,42 @@
<i class="fa fa-microphone fa-lg"></i> <i class="fa fa-microphone fa-lg"></i>
<translate>List of speakers</translate> <translate>List of speakers</translate>
</a> </a>
<!-- project --> <!-- project button -->
<projector-button model="motion" default-projector-id="defaultProjectorId"> <div class="btn-group button" uib-dropdown
</projector-button> uib-tooltip="{{ 'Projector' | translate }} {{ motion.isProjected()[0] || '' }} ({{ projectionMode.label | translate }})"
tooltip-enable="motion.isProjected().length"
os-perms="core.can_manage_projector">
<button type="button" class="btn btn-default btn-sm"
title="{{ 'Project motion' | translate }}"
ng-click="motion.project(defaultProjectorId, projectionMode.mode)"
ng-class="{ 'btn-primary': motion.isProjected().length && inArray(motion.isProjected(), defaultProjectorId)}">
<i class="fa fa-video-camera"></i>
</button>
<button type="button" class="btn btn-default btn-sm slimDropDown" uib-dropdown-toggle
ng-if="projectors.length > 1 || change_recommendations.length"
ng-class="{ 'btn-primary': motion.isProjected().length && !inArray(motion.isProjected(), defaultProjectorId)}">
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="split-button"
ng-if="projectors.length > 1 || change_recommendations.length">
<li role="menuitem" ng-repeat="mode in projectionModes" ng-if="change_recommendations.length">
<a href="" ng-click="setProjectionMode(mode, $event);">
<i class="fa" ng-class="mode.mode == $parent.projectionMode.mode ? 'fa-check-square-o' : 'fa-square-o'"></i>
<span ng-if="mode.mode!='agreed'">{{ mode.label | translate }}</span>
<span ng-if="mode.mode=='agreed'"><translate translate-context="decision making">Resolution</translate></span
</a>
</li>
<li class="divider" ng-show="projectors.length > 1 && change_recommendations.length > 0"></li>
<li role="menuitem" ng-repeat="projector in projectors | orderBy:'id'" ng-show="projectors.length > 1">
<a href="" ng-click="motion.project(projector.id, projectionMode.mode)"
ng-class="{ 'projected': inArray(motion.isProjected(), projector.id) }">
<i class="fa fa-video-camera" ng-show="inArray(motion.isProjected(), projector.id) "></i>
{{ projector.name | translate }}
<span ng-if="projector.id === defaultProjectorId">(<translate>Default</translate>)</span>
</a>
</li>
</ul>
</div>
<!-- edit --> <!-- edit -->
<a ng-if="motion.isAllowed('update')" ng-click="openDialog(motion)" <a ng-if="motion.isAllowed('update')" ng-click="openDialog(motion)"
class="btn btn-default btn-sm" class="btn btn-default btn-sm"

View File

@ -76,11 +76,53 @@
</div> </div>
<!-- Preamble --> <!-- Preamble -->
<div><p>{{ config('motions_preamble') | translate }}</p></div> <div><p>{{ config('motions_preamble') | translate }}</p></div><br>
<!-- Text --> <!-- Text -->
<div ng-bind-html="motion.getTextWithLineBreaks(null, line, scroll) | trusted" <!-- <div ng-bind-html="motion.getTextWithLineBreaks(null, line, scroll) | trusted"
class="motion-text line-numbers-{{ config('motions_default_line_numbering') }}"></div> class="motion-text line-numbers-{{ config('motions_default_line_numbering') }}"></div> -->
<!-- Original view -->
<div ng-if="mode == 'original'">
<div id="view-original-inline-editor" ng-bind-html="motion.getTextWithLineBreaks(null, line, scroll) | trusted"
class="motion-text motion-text-original line-numbers-{{ config('motions_default_line_numbering') }}"
contenteditable="false"></div>
</div>
<!-- Diff View -->
<div ng-if="mode == 'diff'">
<ng-include src="'static/templates/motions/motion-detail/change-summary.html'"></ng-include>
<!-- The actual diff view -->
<div class="motion-text-with-diffs line-numbers-{{ config('motions_default_line_numbering') }}">
<div ng-repeat="change in (changes = (change_recommendations | orderBy: 'line_from')) ">
<div class="motion-text original-text line-numbers-{{ config('motions_default_line_numbering') }}"
ng-bind-html="motion.getTextBetweenChangeRecommendations(null, changes[$index - 1], change, line) | trusted">
</div>
<div class="diff-box diff-box-{{ change.id }} clearfix">
<div class="motion-text motion-text-diff line-numbers-{{ lineNumberMode }}"
ng-bind-html="change.getDiff(motion, null, line) | trusted">
</div>
</div>
<div class="motion-text original-text line-numbers-{{ config('motions_default_line_numbering') }}"
ng-bind-html="motion.getTextRemainderAfterLastChangeRecommendation(null, changes, line) | trusted">
</div>
</div>
</div>
</div>
<!-- Changed View -->
<div ng-if="mode == 'changed'">
<div ng-bind-html="motion.getTextByMode('changed', null, line) | trusted"
class="motion-text motion-text-changed line-numbers-{{ config('motions_default_line_numbering') }}"></div>
</div>
<!-- Agreed View -->
<div ng-if="mode == 'agreed'">
<div ng-bind-html="motion.getTextByMode('agreed', null, line) | trusted"
class="motion-text motion-text-changed line-numbers-{{ config('motions_default_line_numbering') }}"></div>
</div>
<!-- Reason --> <!-- Reason -->
<h3 ng-if="motion.getReason()" translate>Reason</h3> <h3 ng-if="motion.getReason()" translate>Reason</h3>