Merge pull request #3567 from FinnStutzenstein/layoutChanges
Collapsable agenda, one panel for each motion comment and hide closed…
This commit is contained in:
commit
d22a2c3c58
@ -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].
|
||||
|
@ -6,10 +6,6 @@
|
||||
|
||||
p {
|
||||
font-size: 140%;
|
||||
|
||||
&.done {
|
||||
color: #9a9898;
|
||||
}
|
||||
}
|
||||
|
||||
.mainitem {
|
||||
|
@ -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%;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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',
|
||||
|
@ -148,6 +148,13 @@
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
<span ng-if="items.length === itemsFiltered.length">
|
||||
·
|
||||
<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"> ·
|
||||
|
@ -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">
|
||||
·
|
||||
</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">
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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) {
|
||||
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;
|
||||
|
@ -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]) {
|
||||
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') + ')';
|
||||
}
|
||||
content.push({
|
||||
var content = [{
|
||||
heading: title,
|
||||
text: motion.comments[id],
|
||||
});
|
||||
}
|
||||
});
|
||||
text: motion.comments[commentId],
|
||||
}];
|
||||
MotionPartialContentProvider.createInstance(motion, content).then(function (contentProvider) {
|
||||
PdfMakeDocumentProvider.createInstance(contentProvider).then(function (documentProvider) {
|
||||
PdfCreate.download(documentProvider.getDocument(), filename);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@ -1768,11 +1768,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 : '';
|
||||
|
@ -1,40 +1,41 @@
|
||||
<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>
|
||||
</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
|
||||
@ -42,7 +43,4 @@
|
||||
<button type="button" ng-click="commentsInlineEditing.revert()" class="btn btn-default" translate>
|
||||
Revert
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user