Collapsable agenda, one panel for each motion comment and hide closed agenda items in the item slide.

This commit is contained in:
FinnStutzenstein 2018-02-06 12:12:52 +01:00
parent 8042beda60
commit 44fcfb447e
12 changed files with 132 additions and 75 deletions

View File

@ -15,6 +15,8 @@ Agenda:
- Fixed multiple request on creation of agenda related items [#3341].
- Added possibility to mark speakers [#3570].
- New DOCX export of agenda [#3569].
- Hide closed agenda items in the item slide [#3567].
- Agenda is now collapsable for a better overview [#3567].
Motions:
- New export dialog [#3185].

View File

@ -6,10 +6,6 @@
p {
font-size: 140%;
&.done {
color: #9a9898;
}
}
.mainitem {

View File

@ -2,12 +2,21 @@
#agenda-table {
.icon-column {
padding: 3px;
width: 5%;
}
.title-column {
padding: 0px 10px;
width: 95%;
padding-left: 3px;
padding-right: 10px;
width: calc(95% - 15px );
}
.caret-spacer {
width: 15px;
padding: 3px;
color: #337ab7;
font-size: 115%;
}
}

View File

@ -298,6 +298,7 @@ angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users'])
function generateFlatTree(tree, parentCount) {
_.each(tree, function (item) {
item.item.childrenCount = item.children.length;
item.item.parentCount = parentCount;
flatItems.push(item.item);
generateFlatTree(item.children, parentCount + 1);

View File

@ -120,10 +120,7 @@ angular.module('OpenSlidesApp.agenda.site', [
return item.list_view_title;
});
$scope.items = AgendaTree.getFlatTree(allowedItems);
var subitems = $filter('filter')($scope.items, {'parent_id': ''});
if (subitems.length) {
$scope.agendaHasSubitems = true;
}
$scope.agendaHasSubitems = $filter('filter')($scope.items, {'parent_id': ''}).length;
});
Projector.bindAll({}, $scope, 'projectors');
$scope.mainListTree = true;
@ -169,6 +166,12 @@ angular.module('OpenSlidesApp.agenda.site', [
},
};
// Expand all items during searching.
$scope.filter.changed = function () {
$scope.collapseState = true;
$scope.toggleCollapseState();
};
// pagination
$scope.pagination = osTablePagination.createInstance('AgendaTablePagination');
@ -235,6 +238,14 @@ angular.module('OpenSlidesApp.agenda.site', [
}
};
// Agenda collapse function
$scope.toggleCollapseState = function () {
$scope.collapseState = !$scope.collapseState;
_.forEach($scope.items, function (item) {
item.hideChildren = $scope.collapseState;
});
};
/** Agenda item functions **/
// open dialog for new topics // TODO Remove this. Don't forget import button in template.
$scope.newDialog = function () {
@ -278,6 +289,7 @@ angular.module('OpenSlidesApp.agenda.site', [
$scope.edit = function (item) {
ngDialog.open(item.getContentObjectForm().getDialog({id: item.content_object.id}));
};
// export
$scope.pdfExport = function () {
AgendaPdfExport.export($scope.itemsFiltered);
@ -393,6 +405,33 @@ angular.module('OpenSlidesApp.agenda.site', [
}
])
// filter to hide collapsed items. Items has to be a flat tree.
.filter('collapsedItemFilter', [
function () {
return function (items) {
return _.filter(items, function (item) {
var index = _.findIndex(items, item);
var parentId = item.parent_id;
// Search for parents, if one has the hideChildren attribute set. All parents
// have a higher index as this item, because items is a flat tree.
// If a parent has this attribute, we should remove this item. Else if we hit
// the top or an item on the first layer, the item is not collapsed.
for (--index; index >= 0 && parentId !== null; index--) {
var p = items[index];
if (p.id === parentId) {
if (p.hideChildren) {
return false;
} else {
parentId = p.parent_id;
}
}
}
return true;
});
};
}
])
.controller('ItemDetailCtrl', [
'$scope',
'$filter',

View File

@ -148,6 +148,13 @@
</span>
</span>
</span>
<span ng-if="items.length === itemsFiltered.length">
&middot;
<a href="" ng-click="toggleCollapseState()">
<span ng-if="collapseState" translate>Expand all</span>
<span ng-if="!collapseState" translate>Collapse all</span>
</a>
</span>
</div>
<div class="col-md-6" ng-show="itemsFiltered.length > pagination.itemsPerPage">
<span class="pull-right">
@ -232,6 +239,7 @@
| osFilter: filter.filterString : filter.getObjectQueryString
| filter: {closed: filter.booleanFilters.closed.value}
| filter: {is_hidden: filter.booleanFilters.is_hidden.value})
| collapsedItemFilter
| limitTo : pagination.itemsPerPage : pagination.limitBegin">
<!-- select column -->
@ -279,11 +287,18 @@
<div class="no-projector-spacer" os-perms="!core.can_manage_projector"></div>
<!-- main content column -->
<div class="col-xs-6 content" style="padding-left: calc({{ item.parentCount }}*15px)">
<div class="col-xs-6 content"
style="padding-left: calc({{ items.length === itemsFiltered.length ? item.parentCount : 0 }}*25px)">
<div class="icon-column">
<i class="fa fa-ban" ng-style="{'visibility': item.is_hidden ? 'visible' : 'hidden'}"
title="{{ 'Internal item' | translate }}"></i>
</div>
<div class="caret-spacer" ng-if="items.length === itemsFiltered.length">
<i class="fa pointer"
ng-style="{visibility: item.childrenCount ? 'visible' : 'hidden'}"
ng-class="item.hideChildren ? 'fa-caret-right' : 'fa-caret-down'"
ng-click="item.hideChildren = !item.hideChildren"></i>
</div>
<div class="title-column">
<!-- ID and title -->
<div>
@ -295,7 +310,8 @@
</span>
</div>
<!-- hover menu -->
<div os-perms="agenda.can_see" ng-class="{'hiddenDiv': !item.hover}">
<div os-perms="agenda.can_see" ng-class="{'hiddenDiv': !item.hover}"
ng-style="{'padding-left': items.length === itemsFiltered.length ? '15px' : '0px'}">
<small>
<a ui-sref="agenda.item.detail({id: item.id})" translate>List of speakers</a>
<span os-perms="agenda.can_manage"> &middot;

View File

@ -11,18 +11,18 @@
<!-- Nested node template -->
<script type="text/ng-template" id="projector_agenda_renderer.html">
<td class="number">
<p ng-class="{mainitem: node.item.parent_id === null, subitem: node.item.parent_id !== null, done: node.item.closed}">
<td class="number" ng-if="!node.item.closed">
<p ng-class="{mainitem: node.item.parent_id === null, subitem: node.item.parent_id !== null}">
{{ node.item.item_number }}
</p>
</td>
<td>
<td ng-if="!node.item.closed">
<p ng-class="{mainitem: node.item.parent_id === null}" ng-if="node.item.item_number">
&middot;
</p>
</td>
<td>
<p ng-class="{mainitem: node.item.parent_id === null, subitem: node.item.parent_id !== null, done: node.item.closed}">
<td ng-if="!node.item.closed">
<p ng-class="{mainitem: node.item.parent_id === null, subitem: node.item.parent_id !== null}">
{{ node.item.title }}
</p>
<table ng-if="node.children.length" class="agendalist-table">

View File

@ -511,6 +511,7 @@ angular.module('OpenSlidesApp.core.site', [
self.existsStorageEntry = existsStorageEntry;
self.save = function () {
$sessionStorage[tableName] = self;
self.changed();
};
self.areFiltersSet = function () {
var areFiltersSet = _.find(self.multiselectFilters, function (filterList) {
@ -567,6 +568,8 @@ angular.module('OpenSlidesApp.core.site', [
});
return stringList.join(' ');
};
// Stub for callback
self.changed = function () {};
return self;
};

View File

@ -131,7 +131,7 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
function (MotionInlineEditing, Editor) {
var createInstances = function ($scope, motion) {
var commentsInlineEditing = {
editors: []
editors: {}, // Map comment id to editor instance.
};
var options = Editor.getOptions('inline', 'YOffset');
_.forEachRight($scope.noSpecialCommentsFields, function (field, id) {
@ -141,20 +141,20 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
return motion['comment_' + id];
},
function (obj) {
motion['comment_' + id] = obj.editor.getData();
if (obj.editor) {
motion['comment_' + id] = obj.editor.getData();
}
}
);
commentsInlineEditing.editors.push(inlineEditing);
commentsInlineEditing.editors[id] = 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.active = function (commentId) {
return commentsInlineEditing.editors[commentId].active;
};
commentsInlineEditing.save = function () {
_.forEach(commentsInlineEditing.editors, function (instance) {
@ -166,15 +166,11 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
instance.revert();
});
};
commentsInlineEditing.enable = function () {
_.forEach(commentsInlineEditing.editors, function (instance) {
instance.enable();
});
commentsInlineEditing.enable = function (commentId) {
commentsInlineEditing.editors[commentId].enable();
};
commentsInlineEditing.disable = function () {
_.forEach(commentsInlineEditing.editors, function (instance) {
instance.disable();
});
commentsInlineEditing.disable = function (commentId) {
commentsInlineEditing.editors[commentId].disable();
};
return commentsInlineEditing;

View File

@ -1069,26 +1069,23 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
});
});
},
exportComments: function (motion, filename) {
var fields = MotionComment.getNoSpecialCommentsFields();
var content = [];
_.forEach(fields, function (field, id) {
if (motion.comments[id]) {
var title = field.name;
if (!field.public) {
title += ' (' + gettextCatalog.getString('internal') + ')';
}
content.push({
heading: title,
text: motion.comments[id],
});
exportComment: function (motion, commentId, filename) {
var field = MotionComment.getNoSpecialCommentsFields()[commentId];
if (field && motion.comments[commentId]) {
var title = field.name;
if (!field.public) {
title += ' (' + gettextCatalog.getString('internal') + ')';
}
});
MotionPartialContentProvider.createInstance(motion, content).then(function (contentProvider) {
PdfMakeDocumentProvider.createInstance(contentProvider).then(function (documentProvider) {
PdfCreate.download(documentProvider.getDocument(), filename);
var content = [{
heading: title,
text: motion.comments[commentId],
}];
MotionPartialContentProvider.createInstance(motion, content).then(function (contentProvider) {
PdfMakeDocumentProvider.createInstance(contentProvider).then(function (documentProvider) {
PdfCreate.download(documentProvider.getDocument(), filename);
});
});
});
}
},
};
}

View File

@ -1765,11 +1765,11 @@ angular.module('OpenSlidesApp.motions.site', [
$scope.createPollPdf = function () {
MotionPdfExport.createPollPdf($scope.motion, $scope.version);
};
$scope.exportComments = function () {
$scope.exportComment = function (commentId) {
var identifier = $scope.motion.identifier ? '-' + $scope.motion.identifier : '';
var commentsString = ' - ' + gettextCatalog.getString('Comments');
var filename = gettextCatalog.getString('Motion') + identifier + commentsString + '.pdf';
MotionPdfExport.exportComments($scope.motion, filename);
MotionPdfExport.exportComment($scope.motion, commentId, filename);
};
$scope.exportPersonalNote = function () {
var identifier = $scope.motion.identifier ? '-' + $scope.motion.identifier : '';

View File

@ -1,48 +1,46 @@
<div class="details" ng-if="commentFieldsAvailable()">
<div class="details" ng-repeat="(id, field) in noSpecialCommentsFields">
<div class="row">
<!-- inline editing toolbar -->
<div class="motion-toolbar">
<div class="pull-right inline-editing-activator">
<button ng-click="exportComments()" class="btn btn-default btn-sm"
<button ng-click="exportComment(id)" class="btn btn-default btn-sm"
uib-tooltip="{{ 'Export comments only' | translate }}">
<i class="fa fa-file-pdf-o"></i>
<translate>PDF</translate>
</button>
<span ng-if="motion.isAllowed('change_comments')">
<button ng-if="!commentsInlineEditing.active()" ng-click="commentsInlineEditing.enable()"
<button ng-if="!commentsInlineEditing.active(id)" ng-click="commentsInlineEditing.enable(id)"
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()"
<button ng-if="commentsInlineEditing.active(id)" ng-click="commentsInlineEditing.disable(id)"
class="btn btn-sm btn-default">
<i class="fa fa-times-circle"></i>
<translate>Inline editing</translate>
</button>
</span>
</div>
<h1 class="toolbar-left" translate>Comments</h1>
<h1 class="toolbar-left" translate>Comments
{{ field.name }}
<span ng-if="!field.public" class="label label-warning" translate>internal</span>
</h1>
</div>
<!-- comment fields -->
<div class="col-sm-12">
<div ng-repeat="(id, field) in noSpecialCommentsFields">
<h4>
{{ field.name }}
<span ng-if="!field.public" class="label label-warning" translate>internal</span>
</h4>
<div id="view-original-comment-inline-editor-{{ id }}" style="min-height: 14px;"
ng-bind-html="motion.comments[id] | trusted" contenteditable="{{ commentsInlineEditing.editors[$index].isEditable }}"></div>
</div>
<!-- save toolbar -->
<div class="motion-save-toolbar" ng-class="{ 'visible': commentsInlineEditing.saveToolbarVisible() }">
<div class="changed-hint" translate>A 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-default" translate>
Revert
</button>
</div>
<div id="view-original-comment-inline-editor-{{ id }}" style="min-height: 14px;"
ng-bind-html="motion.comments[id] | trusted" contenteditable="{{ commentsInlineEditing.editors[$index].isEditable }}"></div>
</div>
</div>
</div>
<!-- save toolbar -->
<div class="motion-save-toolbar" ng-class="{ 'visible': commentsInlineEditing.saveToolbarVisible() }">
<div class="changed-hint" translate>A 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-default" translate>
Revert
</button>
</div>