diff --git a/CHANGELOG b/CHANGELOG
index 55d13353b..7d3fcbf2d 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -45,6 +45,8 @@ Motions:
- Clear identifier on state reset [#3356].
- New config options to hide reason and recommendation on projector [#3432].
- Show motion identifier in (current) list of speakers [#3442]
+- Added navigation between single motions [#3459].
+- Improved the multiselect state filter [#3459].
Elections:
- Added pagination for list view [#3393].
diff --git a/openslides/assignments/static/js/assignments/site.js b/openslides/assignments/static/js/assignments/site.js
index e0295952d..8a0da0c43 100644
--- a/openslides/assignments/static/js/assignments/site.js
+++ b/openslides/assignments/static/js/assignments/site.js
@@ -319,8 +319,10 @@ angular.module('OpenSlidesApp.assignments.site', [
};
// Sorting
- $scope.sort = osTableSort.createInstance();
- $scope.sort.column = 'title';
+ $scope.sort = osTableSort.createInstance('AssignmentTableSort');
+ if (!$scope.sort.column) {
+ $scope.sort.column = 'title';
+ }
$scope.sortOptions = [
{name: 'agenda_item.getItemNumberWithAncestors()',
display_name: gettext('Item')},
diff --git a/openslides/assignments/static/templates/assignments/assignment-list.html b/openslides/assignments/static/templates/assignments/assignment-list.html
index 50512ea81..e396c2a23 100644
--- a/openslides/assignments/static/templates/assignments/assignment-list.html
+++ b/openslides/assignments/static/templates/assignments/assignment-list.html
@@ -215,7 +215,7 @@
| osFilter: filter.filterString : filter.getObjectQueryString
| MultiselectFilter: filter.multiselectFilters.tag : getItemId.tag
| MultiselectFilter: filter.multiselectFilters.phase : getItemId.phase
- | orderBy: sort.column : sort.reverse)
+ | orderByEmptyLast: sort.column : sort.reverse)
| limitTo : itemsPerPage : limitBegin">
diff --git a/openslides/core/static/css/projector.css b/openslides/core/static/css/projector.css
index cb1e5458a..9cc6b2db6 100644
--- a/openslides/core/static/css/projector.css
+++ b/openslides/core/static/css/projector.css
@@ -48,6 +48,14 @@ body {
color: #222;
}
+/* override booststrap's label class to fix linebreak and add spacing */
+.label {
+ display: inline-block;
+ padding: .4em .6em;
+ margin-right: .2em;
+ white-space: normal;
+}
+
/*** ProjectorContainer ***/
.pContainer {
background-color: #222;
@@ -573,6 +581,18 @@ p.os-split-after {
display: none;
}
+/*** Motion blocks ***/
+.motion-block {
+ display: flex;
+ flex-wrap: wrap;
+ padding: 10px;
+}
+.motion-block > div {
+ width: 50%;
+ margin-bottom: 15px;
+ line-height: 1em;
+}
+
/*** Video and Image projection ***/
img.projector-image {
width: 100%;
diff --git a/openslides/core/static/js/core/base.js b/openslides/core/static/js/core/base.js
index baa3a32b5..9455bdbb1 100644
--- a/openslides/core/static/js/core/base.js
+++ b/openslides/core/static/js/core/base.js
@@ -1420,8 +1420,8 @@ angular.module('OpenSlidesApp.core', [
])
// filters the requesting object (id=selfid) from a list of input objects
-.filter('notself', function() {
- return function(input, selfid) {
+.filter('notself', function () {
+ return function (input, selfid) {
var result;
if (selfid) {
result = [];
@@ -1438,6 +1438,26 @@ angular.module('OpenSlidesApp.core', [
};
})
+// Wraps the orderBy filter. But puts ("", null, undefined) last.
+.filter('orderByEmptyLast', [
+ '$filter',
+ function ($filter) {
+ return function (array, sortPredicate, reverseOrder, compareFn) {
+ var falsyItems = [];
+ var truthyItems = _.filter(array, function (item) {
+ var falsy = item[sortPredicate] === void 0 ||
+ item[sortPredicate] === null || item[sortPredicate] === '';
+ if (falsy) {
+ falsyItems.push(item);
+ }
+ return !falsy;
+ });
+ truthyItems = $filter('orderBy')(truthyItems, sortPredicate, reverseOrder, compareFn);
+ return _.concat(truthyItems, falsyItems);
+ };
+ }
+])
+
// Make sure that the DS factories are loaded by making them a dependency
.run([
'ChatMessage',
diff --git a/openslides/core/static/js/core/site.js b/openslides/core/static/js/core/site.js
index b033c7f13..dd00d3e73 100644
--- a/openslides/core/static/js/core/site.js
+++ b/openslides/core/static/js/core/site.js
@@ -573,17 +573,27 @@ angular.module('OpenSlidesApp.core.site', [
* instance.column='title')
*/
.factory('osTableSort', [
- function () {
- var createInstance = function () {
+ '$sessionStorage',
+ function ($sessionStorage) {
+ var createInstance = function (tableName) {
var self = {
column: '',
reverse: false,
};
+ var storage = $sessionStorage[tableName];
+ if (storage) {
+ self = storage;
+ }
+
+ self.save = function () {
+ $sessionStorage[tableName] = self;
+ };
self.toggle = function (column) {
if (self.column === column) {
self.reverse = !self.reverse;
}
self.column = column;
+ self.save();
};
return self;
};
diff --git a/openslides/mediafiles/static/js/mediafiles/list.js b/openslides/mediafiles/static/js/mediafiles/list.js
index c9c7f1282..363f67c34 100644
--- a/openslides/mediafiles/static/js/mediafiles/list.js
+++ b/openslides/mediafiles/static/js/mediafiles/list.js
@@ -89,8 +89,10 @@ angular.module('OpenSlidesApp.mediafiles.list', [
function (mediafile) {return mediafile.mediafile.name;},
];
// Sorting
- $scope.sort = osTableSort.createInstance();
- $scope.sort.column = 'title_or_filename';
+ $scope.sort = osTableSort.createInstance('MediafileTableSort');
+ if (!$scope.sort.column) {
+ $scope.sort.column = 'title_or_filename';
+ }
$scope.sortOptions = [
{name: 'title_or_filename',
display_name: gettext('Title')},
diff --git a/openslides/mediafiles/static/templates/mediafiles/mediafile-list.html b/openslides/mediafiles/static/templates/mediafiles/mediafile-list.html
index 5669d8a6d..a9262b510 100644
--- a/openslides/mediafiles/static/templates/mediafiles/mediafile-list.html
+++ b/openslides/mediafiles/static/templates/mediafiles/mediafile-list.html
@@ -240,7 +240,7 @@
| osFilter: filter.filterString : filter.getObjectQueryString
| filter: {filetype: (filter.booleanFilters.isPdf.value ? 'application/pdf' : (filter.booleanFilters.isPdf.value === false ? '!application/pdf' : ''))}
| filter: {hidden: filter.booleanFilters.isHidden.value}
- | orderBy: sort.column : sort.reverse )">
+ | orderByEmptyLast: sort.column : sort.reverse )">
diff --git a/openslides/motions/static/js/motions/site.js b/openslides/motions/static/js/motions/site.js
index e2adecc5e..155df53df 100644
--- a/openslides/motions/static/js/motions/site.js
+++ b/openslides/motions/static/js/motions/site.js
@@ -907,6 +907,7 @@ angular.module('OpenSlidesApp.motions.site', [
motion.star = false;
}
});
+ $scope.collectStatesAndRecommendations();
});
$scope.alert = {};
@@ -917,40 +918,69 @@ angular.module('OpenSlidesApp.motions.site', [
};
// collect all states and all recommendations of all workflows
- $scope.states = [];
- $scope.recommendations = [];
- var workflows = Workflow.getAll();
- _.forEach(workflows, function (workflow) {
- var workflowHeader = {
- headername: workflow.name,
- workflowHeader: true,
- };
- $scope.states.push(workflowHeader);
- $scope.recommendations.push(workflowHeader);
- _.forEach(workflow.states, function (state) {
- $scope.states.push(state);
- if (state.recommendation_label) {
- $scope.recommendations.push(state);
+ $scope.collectStatesAndRecommendations = function () {
+ $scope.states = [];
+ $scope.recommendations = [];
+ var workflows = $scope.collectAllUsedWorkflows();
+ _.forEach(workflows, function (workflow) {
+ if (workflows.length > 1) {
+ var workflowHeader = {
+ headername: workflow.name,
+ workflowHeader: true,
+ };
+ $scope.states.push(workflowHeader);
+ $scope.recommendations.push(workflowHeader);
}
+
+ var firstEndStateSeen = false;
+ _.forEach(_.orderBy(workflow.states, 'id'), function (state) {
+ if (state.next_states_id.length === 0 && !firstEndStateSeen) {
+ $scope.states.push({divider: true});
+ firstEndStateSeen = true;
+ }
+ $scope.states.push(state);
+ if (state.recommendation_label) {
+ $scope.recommendations.push(state);
+ }
+ });
});
- });
+ };
+ $scope.collectAllUsedWorkflows = function () {
+ return _.filter(Workflow.getAll(), function (workflow) {
+ return _.some($scope.motions, function (motion) {
+ return motion.state.workflow_id === workflow.id;
+ });
+ });
+ };
$scope.stateFilter = [];
var updateStateFilter = function () {
- if (_.indexOf($scope.filter.multiselectFilters.state, -1) > -1) { // contains -1
- $scope.stateFilter = _.filter($scope.filter.multiselectFilters.state, function (id) {
- return id >= 0;
- }); // remove -1
+ $scope.stateFilter = _.clone($scope.filter.multiselectFilters.state);
+
+ var doneIndex = _.indexOf($scope.stateFilter, -1);
+ if (doneIndex > -1) { // contains -1 (done)
+ $scope.stateFilter.splice(doneIndex, 1); // remove -1
_.forEach($scope.states, function (state) {
- if (!state.workflowHeader) {
- if (state.getNextStates().length === 0) { // done state
+ if (!state.workflowHeader && !state.divider) {
+ if (state.next_states_id.length === 0) { // add all done state
$scope.stateFilter.push(state.id);
}
}
});
- } else {
- $scope.stateFilter = _.clone($scope.filter.multiselectFilters.state);
}
+
+ var undoneIndex = _.indexOf($scope.stateFilter, -2);
+ if (undoneIndex > -1) { // contains -2 (undone)
+ $scope.stateFilter.splice(undoneIndex, 1); // remove -2
+ _.forEach($scope.states, function (state) {
+ if (!state.workflowHeader && !state.divider) {
+ if (state.next_states_id.length !== 0) { // add all undone state
+ $scope.stateFilter.push(state.id);
+ }
+ }
+ });
+ }
+ $scope.stateFilter = _.uniq($scope.stateFilter);
};
// Filtering
@@ -1025,8 +1055,10 @@ angular.module('OpenSlidesApp.motions.site', [
updateStateFilter();
};
// Sorting
- $scope.sort = osTableSort.createInstance();
- $scope.sort.column = 'identifier';
+ $scope.sort = osTableSort.createInstance('MotionTableSort');
+ if (!$scope.sort.column) {
+ $scope.sort.column = 'identifier';
+ }
$scope.sortOptions = [
{name: 'identifier',
display_name: gettext('Identifier')},
@@ -1193,6 +1225,7 @@ angular.module('OpenSlidesApp.motions.site', [
'$http',
'$timeout',
'$window',
+ '$filter',
'operator',
'ngDialog',
'gettextCatalog',
@@ -1219,11 +1252,12 @@ angular.module('OpenSlidesApp.motions.site', [
'PersonalNoteManager',
'WebpageTitle',
'EditingWarning',
- function($scope, $http, $timeout, $window, operator, ngDialog, gettextCatalog, MotionForm,
- ChangeRecommmendationCreate, ChangeRecommmendationView, MotionChangeRecommendation,
- Motion, MotionComment, Category, Mediafile, Tag, User, Workflow, Config, motionId, MotionInlineEditing,
- MotionCommentsInlineEditing, Editor, Projector, ProjectionDefault, MotionBlock, MotionPdfExport,
- PersonalNoteManager, WebpageTitle, EditingWarning) {
+ function($scope, $http, $timeout, $window, $filter, operator, ngDialog, gettextCatalog,
+ MotionForm, ChangeRecommmendationCreate, ChangeRecommmendationView,
+ MotionChangeRecommendation, Motion, MotionComment, Category, Mediafile, Tag, User,
+ Workflow, Config, motionId, MotionInlineEditing, MotionCommentsInlineEditing, Editor,
+ Projector, ProjectionDefault, MotionBlock, MotionPdfExport, PersonalNoteManager,
+ WebpageTitle, EditingWarning) {
var motion = Motion.get(motionId);
Category.bindAll({}, $scope, 'categories');
Mediafile.bindAll({}, $scope, 'mediafiles');
@@ -1263,6 +1297,7 @@ angular.module('OpenSlidesApp.motions.site', [
$scope.recommendationExtension = $scope.motion.comments[$scope.commentFieldForRecommendationId];
}
$scope.motion.personalNote = PersonalNoteManager.getNote($scope.motion);
+ $scope.navigation.evaluate();
var webpageTitle = gettextCatalog.getString('Motion') + ' ';
if ($scope.motion.identifier) {
@@ -1355,6 +1390,17 @@ angular.module('OpenSlidesApp.motions.site', [
$scope.save = function (motion) {
Motion.save(motion, {method: 'PATCH'});
};
+ // Navigation buttons
+ $scope.navigation = {
+ evaluate: function () {
+ var motions = $filter('orderByEmptyLast')(Motion.getAll(), 'identifier');
+ var thisIndex = _.findIndex(motions, function (motion) {
+ return motion.id === $scope.motion.id;
+ });
+ this.nextMotion = thisIndex < motions.length-1 ? motions[thisIndex+1] : _.head(motions);
+ this.previousMotion = thisIndex > 0 ? motions[thisIndex-1] : _.last(motions);
+ },
+ };
// support
$scope.support = function () {
$http.post('/rest/motions/motion/' + motion.id + '/support/');
diff --git a/openslides/motions/static/templates/motions/motion-detail.html b/openslides/motions/static/templates/motions/motion-detail.html
index d7ca5c3ff..2c4e3c052 100644
--- a/openslides/motions/static/templates/motions/motion-detail.html
+++ b/openslides/motions/static/templates/motions/motion-detail.html
@@ -65,18 +65,36 @@
ng-if="operator.user"
title="{{ 'Set as favorite' | translate }}" ng-click="toggleStar()">
-
diff --git a/openslides/motions/static/templates/motions/motion-list.html b/openslides/motions/static/templates/motions/motion-list.html
index 0443817fc..508c87378 100644
--- a/openslides/motions/static/templates/motions/motion-list.html
+++ b/openslides/motions/static/templates/motions/motion-list.html
@@ -157,11 +157,11 @@