Merge pull request #3404 from FinnStutzenstein/Improvements

Goto top link, improved title, stylistic changes, and more
This commit is contained in:
Emanuel Schütze 2017-09-15 11:16:46 +02:00 committed by GitHub
commit 8e15b5dafe
16 changed files with 173 additions and 48 deletions

View File

@ -15,7 +15,7 @@ Agenda:
Motions:
- New export dialog [#3185].
- New feature: Personal notes for motions [#3190, #3267].
- New feature: Personal notes for motions [#3190, #3267, #3404].
- Fixed issue when creating/deleting motion comment fields in the
settings [#3187].
- Fixed empty motion comment field in motion update form [#3194].
@ -80,6 +80,8 @@ Core:
- Updated CKEditor to 4.7 [#3375].
- Reduced ckeditor toolbar for inline editing [#3368].
- Added custom translations in config [#3383].
- Added dynamic webpage title [#3404].
- Added 'go to top'-link [#3404].
Mediafiles:
- Fixed reloading of PDF on page change [#3274].

View File

@ -391,14 +391,19 @@ angular.module('OpenSlidesApp.agenda.site', [
'itemId',
'Projector',
'ProjectionDefault',
'gettextCatalog',
'WebpageTitle',
'ErrorMessage',
function ($scope, $filter, Agenda, itemId, Projector, ProjectionDefault, ErrorMessage) {
function ($scope, $filter, Agenda, itemId, Projector, ProjectionDefault, gettextCatalog, WebpageTitle,
ErrorMessage) {
$scope.alert = {};
$scope.$watch(function () {
return Agenda.lastModified(itemId);
}, function () {
$scope.item = Agenda.get(itemId);
WebpageTitle.updateTitle(gettextCatalog.getString('List of speakers') + ' ' +
gettextCatalog.getString('of') + ' ' + $scope.item.getTitle());
// all speakers
$scope.speakers = $filter('orderBy')($scope.item.speakers, 'weight');
// next speakers

View File

@ -406,7 +406,10 @@ angular.module('OpenSlidesApp.assignments', [])
return isProjectedIds;
},
isRelatedProjected: function () {
var listOfSpeakers = this.agenda_item.isListOfSpeakersProjected();
var listOfSpeakers = [];
if (this.agenda_item) {
listOfSpeakers = this.agenda_item.isListOfSpeakersProjected();
}
return listOfSpeakers.concat(this.isProjected(null, true));
},
},

View File

@ -418,10 +418,11 @@ angular.module('OpenSlidesApp.assignments.site', [
'gettextCatalog',
'AssignmentPhases',
'AssignmentPdfExport',
'WebpageTitle',
'ErrorMessage',
function($scope, $http, $filter, $timeout, filterFilter, gettext, ngDialog, AssignmentForm, operator,
Assignment, User, assignmentId, Projector, ProjectionDefault, gettextCatalog, AssignmentPhases,
AssignmentPdfExport, ErrorMessage) {
AssignmentPdfExport, WebpageTitle, ErrorMessage) {
User.bindAll({}, $scope, 'users');
var assignment = Assignment.get(assignmentId);
Assignment.loadRelations(assignment, 'agenda_item');
@ -447,6 +448,7 @@ angular.module('OpenSlidesApp.assignments.site', [
$scope.activeTab = $scope.assignment.polls.length - 1;
updateBallotTabsFlag = false;
}
WebpageTitle.updateTitle(gettextCatalog.getString('Election') + ' ' + $scope.assignment.title);
});
$scope.candidateSelectBox = {};
$scope.phases = AssignmentPhases;

View File

@ -296,6 +296,24 @@ img {
color: #555;
}
/** Goto top link **/
#goto-top {
position: fixed;
bottom: 15px;
right: 30px;
padding: 10px 30px;
background: white;
opacity: 0.6;
transition: opacity 250ms ease-out;
z-index: 100;
}
#goto-top:hover {
opacity: 1;
transition: opacity 250ms ease-in;
}
#goto-top a:hover {
text-decoration: none;
}
/** Content **/

View File

@ -818,7 +818,7 @@ angular.module('OpenSlidesApp.core.pdf', [])
// If this element is inside a list (happens if copied from word), do not set spaces
// and margins. Just leave the paragraph there..
if (!isInsideAList(element)) {
currentParagraph.margin = [20, 0, 0, 0];
currentParagraph.margin = [0, 0, 0, 0];
if (classes.indexOf('os-split-before') === -1) {
currentParagraph.margin[1] = 8;
}

View File

@ -134,20 +134,30 @@ angular.module('OpenSlidesApp.core.site', [
}
])
// Set up the activeAppTitle for the title from the webpage
.factory('WebpageTitle', [
'$rootScope',
function ($rootScope) {
$rootScope.activeAppTitle = '';
return {
updateTitle: function (text) {
$rootScope.activeAppTitle = text || '';
},
};
}
])
// Watch for the basePerm on a stateChange and initialize the WebpageTitle factory
.run([
'$rootScope',
'gettextCatalog',
'operator',
function ($rootScope, gettextCatalog, operator) {
$rootScope.activeAppTitle = '';
'WebpageTitle',
function ($rootScope, operator, WebpageTitle) {
$rootScope.$on('$stateChangeSuccess', function(event, toState) {
WebpageTitle.updateTitle(toState.data ? toState.data.title : '');
if (toState.data) {
$rootScope.activeAppTitle = toState.data.title || '';
$rootScope.baseViewPermissionsGranted = toState.data.basePerm ?
operator.hasPerms(toState.data.basePerm) : true;
} else {
$rootScope.activeAppTitle = '';
$rootScope.baseViewPermissionsGranted = true;
}
});
@ -987,6 +997,23 @@ angular.module('OpenSlidesApp.core.site', [
}
])
.controller('GotoTopCtrl', [
'$scope',
'$window',
'$timeout',
function ($scope, $window, $timeout) {
$scope.show = false;
angular.element($window).bind('scroll', function () {
$timeout(function () {
$scope.show = ($window.pageYOffset >= 150);
});
});
$scope.gotoTop = function () {
$window.scrollTo(0, 0);
};
}
])
// Projector Sidebar Controller
.controller('ProjectorSidebarCtrl', [
'$scope',

View File

@ -4,7 +4,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<base href="/">
<title ng-if="!activeAppTitle">OpenSlides</title> <!-- avoid {{ ... }} in title while angular is not loaded -->
<title ng-if="activeAppTitle">OpenSlides &ndash; {{ activeAppTitle | translate }}</title>
<title ng-if="activeAppTitle">{{ activeAppTitle | translate }} &ndash; OpenSlides</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<link rel="stylesheet" href="static/css/openslides-libs.css">
@ -258,6 +258,14 @@
<messaging></messaging>
<div ng-controller="GotoTopCtrl">
<div id="goto-top" class="pointer" ng-click="gotoTop()" ng-if="show">
<a>
<i class="fa fa-lg fa-arrow-up"></i>
</a>
</div>
</div>
</div><!--end wrapper-->
<script src="/webclient/site/"></script>

View File

@ -144,9 +144,18 @@ angular.module('OpenSlidesApp.motions.motionBlock', [])
'motionBlockId',
'Projector',
'ProjectionDefault',
'WebpageTitle',
'gettextCatalog',
'ErrorMessage',
function($scope, $http, ngDialog, Motion, MotionBlockForm, MotionBlock, motionBlockId, Projector, ProjectionDefault, ErrorMessage) {
MotionBlock.bindOne(motionBlockId, $scope, 'motionBlock');
function($scope, $http, ngDialog, Motion, MotionBlockForm, MotionBlock, motionBlockId, Projector,
ProjectionDefault, WebpageTitle, gettextCatalog, ErrorMessage) {
$scope.$watch(function () {
return MotionBlock.lastModified(motionBlockId);
}, function () {
$scope.motionBlock = MotionBlock.get(motionBlockId);
WebpageTitle.updateTitle(gettextCatalog.getString('Motion block') + ' ' +
$scope.motionBlock.agenda_item.getTitle());
});
Motion.bindAll({}, $scope, 'motions');
$scope.$watch(function () {
return Projector.lastModified();

View File

@ -134,7 +134,7 @@ angular.module('OpenSlidesApp.motions.motionservices', ['OpenSlidesApp.motions',
editors: []
};
var options = Editor.getOptions('inline', 'YOffset');
_.forEach($scope.noSpecialCommentsFields, function (field, id) {
_.forEachRight($scope.noSpecialCommentsFields, function (field, id) {
var inlineEditing = MotionInlineEditing.createInstance($scope, motion,
'view-original-comment-inline-editor-' + id, false, options,
function (obj) {

View File

@ -121,7 +121,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
text: gettextCatalog.getString('Category') + ':',
style: ['bold', 'grey'] },
{
text: motion.category.name,
text: motion.category.prefix + ' - ' + motion.category.name,
style: 'grey'
}
]);
@ -484,7 +484,7 @@ angular.module('OpenSlidesApp.motions.pdf', ['OpenSlidesApp.core.pdf'])
text: gettextCatalog.getString('Category') + ':',
style: ['bold', 'grey'] },
{
text: motion.category.name,
text: motion.category.prefix + ' - ' + motion.category.name,
style: 'grey'
}
]);

View File

@ -902,6 +902,7 @@ angular.module('OpenSlidesApp.motions.site', [
motion.personalNote = PersonalNoteManager.getNote(motion);
// For filtering, we cannot filter for .personalNote.star
motion.star = motion.personalNote ? motion.personalNote.star : false;
motion.hasPersonalNote = motion.personalNote ? !!motion.personalNote.note : false;
if (motion.star === undefined) {
motion.star = false;
}
@ -967,9 +968,13 @@ angular.module('OpenSlidesApp.motions.site', [
$scope.filter.booleanFilters = {
isFavorite: {
value: undefined,
displayName: gettext('Favorite'),
choiceYes: gettext('Is favorite'),
choiceNo: gettext('Is not favorite'),
choiceYes: gettext('Marked as favorite'),
choiceNo: gettext('Not marked as favorite'),
},
hasPersonalNote: {
value: undefined,
choiceYes: gettext('Personal note set'),
choiceNo: gettext('Personal note not set'),
},
};
}
@ -1213,12 +1218,13 @@ angular.module('OpenSlidesApp.motions.site', [
'MotionBlock',
'MotionPdfExport',
'PersonalNoteManager',
'WebpageTitle',
'EditingWarning',
function($scope, $http, $timeout, operator, ngDialog, gettextCatalog, MotionForm,
ChangeRecommmendationCreate, ChangeRecommmendationView, MotionChangeRecommendation,
Motion, MotionComment, Category, Mediafile, Tag, User, Workflow, Config, motionId, MotionInlineEditing,
MotionCommentsInlineEditing, Editor, Projector, ProjectionDefault, MotionBlock, MotionPdfExport,
PersonalNoteManager, EditingWarning) {
PersonalNoteManager, WebpageTitle, EditingWarning) {
var motion = Motion.get(motionId);
Category.bindAll({}, $scope, 'categories');
Mediafile.bindAll({}, $scope, 'mediafiles');
@ -1254,6 +1260,13 @@ angular.module('OpenSlidesApp.motions.site', [
$scope.motion = Motion.get(motionId);
MotionComment.populateFields($scope.motion);
$scope.motion.personalNote = PersonalNoteManager.getNote($scope.motion);
var webpageTitle = gettextCatalog.getString('Motion') + ' ';
if ($scope.motion.identifier) {
webpageTitle += $scope.motion.identifier + ' - ';
}
webpageTitle += $scope.motion.getTitle();
WebpageTitle.updateTitle(webpageTitle);
});
$scope.projectionModes = [
{mode: 'original',
@ -1537,7 +1550,7 @@ angular.module('OpenSlidesApp.motions.site', [
if (editingStoppedCallback) {
editingStoppedCallback();
}
if ($scope.motion.getReason($scope.version)) {
if ($scope.motion && $scope.motion.getReason($scope.version)) {
$scope.reasonInlineEditing.disable();
}
$scope.inlineEditing.disable();

View File

@ -238,13 +238,13 @@
<li ng-repeat="category in categories">
<a href ng-click="toggleCategory(category)">
<i class="fa fa-check" ng-if="category.id == motion.category.id"></i>
{{ category.name }}
{{ category.prefix }} &ndash; {{ category.name }}
</a>
</li>
</ul>
</span>
</div>
{{ motion.category.name }}
{{ motion.category.prefix }} &ndash; {{ motion.category.name }}
<!-- Motion block -->
<h3 class="heading" os-perms="!motions.can_manage" ng-show="motion.motionBlock" translate>Motion block</h3>
@ -424,6 +424,13 @@
<i class="fa fa-bar-chart fa-lg"></i>
<translate>New vote</translate>
</button>
<div class="spacer-top pull-right nobr">
<a href="#personalNote" translate>Personal note</a>
<span ng-click="pinPersonalNote()" class="spacer-left pointer" title="{{ 'Pin personal note' || translate }}">
<i class="fa fa-thumb-tack" ng-class="{'rotate-45-deg-right': !personalNotePinned}"></i>
</span>
</div>
</div>
</div>
</div>

View File

@ -86,7 +86,7 @@
<select ng-show="selectedAction == 'setCategory'" ng-model="selectedCategory" class="form-control input-sm">
<option value="" translate>--- Select category ---</option>
<option ng-repeat="category in categories" value="{{ category.id }}">
{{ category.name }}
{{ category.prefix }} &ndash; {{ category.name }}
</option>
<option value="no_category_selected" translate>No category</option>
</select>
@ -162,7 +162,7 @@
<li class="divider"></li>
<li>
<a href ng-click="operateStateFilter(-1, isSelectMode)">
<i class="fa fa-check" ng-if="filter.multiselectFilters.category.indexOf(-1) > -1"></i>
<i class="fa fa-check" ng-if="filter.multiselectFilters.state.indexOf(-1) > -1"></i>
<translate>done</translate>
</a>
</li>
@ -232,7 +232,7 @@
<li ng-repeat="category in categories">
<a href ng-click="filter.operateMultiselectFilter('category', category.id, isSelectMode)">
<i class="fa fa-check" ng-if="filter.multiselectFilters.category.indexOf(category.id) > -1"></i>
{{ category.name }}
{{ category.prefix }} &ndash; {{ category.name }}
</a>
</li>
<li class="divider"></li>
@ -292,26 +292,39 @@
</li>
</ul>
</span>
<!-- boolean Filters -->
<span ng-repeat="(name, booleanFilter) in filter.booleanFilters"
ng-if="operator.user.id" uib-dropdown>
<span class="pointer" id="dropdown{{ name }}" uib-dropdown-toggle
ng-class="{'bold': booleanFilter.value !== undefined, 'disabled': isSelectMode}"
<!-- boolean Filters (customized!) -->
<span ng-if="operator.user.id" uib-dropdown>
<span class="pointer" id="dropdownPrivate" uib-dropdown-toggle
ng-class="{'bold': (filter.booleanFilters.isFavorite.value !== undefined) ||
(filter.booleanFilters.hasPersonalNote.value !== undefined), 'disabled': isSelectMode}"
ng-disabled="isSelectMode">
{{ booleanFilter.displayName | translate }}
<translate>Private</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdown{{ name }}">
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownPrivate">
<li>
<a href ng-click="booleanFilter.value = (booleanFilter.value ? undefined : true); filter.save();">
<i class="fa" ng-class="{'fa-check': booleanFilter.value === true}"></i>
{{ booleanFilter.choiceYes | translate }}
<a href ng-click="filter.booleanFilters.isFavorite.value = (filter.booleanFilters.isFavorite.value ? undefined : true); filter.save();">
<i class="fa" ng-class="{'fa-check': filter.booleanFilters.isFavorite.value === true}"></i>
{{ filter.booleanFilters.isFavorite.choiceYes | translate }}
</a>
</li>
<li>
<a href ng-click="booleanFilter.value = (booleanFilter.value === false) ? undefined : false; filter.save();">
<i class="fa" ng-class="{'fa-check': booleanFilter.value === false}"></i>
{{ booleanFilter.choiceNo | translate }}
<a href ng-click="filter.booleanFilters.isFavorite.value = (filter.booleanFilters.isFavorite.value === false) ? undefined : false; filter.save();">
<i class="fa" ng-class="{'fa-check': filter.booleanFilters.isFavorite.value === false}"></i>
{{ filter.booleanFilters.isFavorite.choiceNo | translate }}
</a>
</li>
<li class="divider"></li>
<li>
<a href ng-click="filter.booleanFilters.hasPersonalNote.value = (filter.booleanFilters.hasPersonalNote.value ? undefined : true); filter.save();">
<i class="fa" ng-class="{'fa-check': filter.booleanFilters.hasPersonalNote.value === true}"></i>
{{ filter.booleanFilters.hasPersonalNote.choiceYes | translate }}
</a>
</li>
<li>
<a href ng-click="filter.booleanFilters.hasPersonalNote.value = (filter.booleanFilters.hasPersonalNote.value === false) ? undefined : false; filter.save();">
<i class="fa" ng-class="{'fa-check': filter.booleanFilters.hasPersonalNote.value === false}"></i>
{{ filter.booleanFilters.hasPersonalNote.choiceNo | translate }}
</a>
</li>
</ul>
@ -400,7 +413,7 @@
ng-class="{'disabled': isSelectMode}">
<span class="nobr">
<i class="fa fa-times-circle"></i>
{{ category.name }}
{{ category.prefix }} &ndash; {{ category.name }}
</span>
</span>
<span ng-if="filter.multiselectFilters.category.indexOf(-1) > -1" class="pointer spacer-left-lg"
@ -480,6 +493,7 @@
| MultiselectFilter: filter.multiselectFilters.recommendation : getItemId.recommendation
| MultiselectFilter: filter.multiselectFilters.tag : getItemId.tag
| filter: {star: filter.booleanFilters.isFavorite.value}
| filter: {hasPersonalNote: filter.booleanFilters.hasPersonalNote.value}
| toArray
| orderBy: sort.column : sort.reverse)
| limitTo : itemsPerPage : limitBegin">
@ -606,7 +620,7 @@
</span>
<span ng-if="motion.category != null">
<i class="fa fa-sitemap spacer-right"></i>
{{ motion.category.name }}
{{ motion.category.prefix }} &ndash; {{ motion.category.name }}
<i class="fa fa-cog fa-lg spacer-left" ng-show="motion.categoryHover"></i>
</span>
</span>
@ -614,7 +628,7 @@
<li ng-repeat="category in categories">
<a href ng-click="toggleCategory(motion, category)">
<i class="fa fa-check" ng-if="category.id == motion.category.id"></i>
{{ category.name }}
{{ category.prefix }} &ndash; {{ category.name }}
</a>
</li>
</ul>
@ -623,7 +637,7 @@
<!-- Category string for normal user -->
<div os-perms="!motions.can_manage" ng-show="motion.category != null">
<i class="fa fa-sitemap spacer-right"></i>
{{ motion.category.name }}
{{ motion.category.prefix }} &ndash; {{ motion.category.name }}
</div>
<!-- Motion block dropdown for manage user -->

View File

@ -155,8 +155,17 @@ angular.module('OpenSlidesApp.topics.site', ['OpenSlidesApp.topics', 'OpenSlides
'topicId',
'Projector',
'ProjectionDefault',
function($scope, ngDialog, TopicForm, Topic, topicId, Projector, ProjectionDefault) {
Topic.bindOne(topicId, $scope, 'topic');
'WebpageTitle',
'gettextCatalog',
function($scope, ngDialog, TopicForm, Topic, topicId, Projector, ProjectionDefault, WebpageTitle,
gettextCatalog) {
$scope.$watch(function () {
return Topic.lastModified(topicId);
}, function () {
$scope.topic = Topic.get(topicId);
WebpageTitle.updateTitle(gettextCatalog.getString('Topic') + ' ' +
$scope.topic.agenda_item.getTitle());
});
$scope.$watch(function () {
return Projector.lastModified();
}, function () {

View File

@ -749,9 +749,17 @@ angular.module('OpenSlidesApp.users.site', [
'Group',
'Projector',
'ProjectionDefault',
function($scope, ngDialog, UserForm, User, userId, Group, Projector, ProjectionDefault) {
User.bindOne(userId, $scope, 'user');
'gettextCatalog',
'WebpageTitle',
function($scope, ngDialog, UserForm, User, userId, Group, Projector, ProjectionDefault, gettextCatalog,
WebpageTitle) {
Group.bindAll({where: {id: {'>': 1}}}, $scope, 'groups');
$scope.$watch(function () {
return User.lastModified(userId);
}, function () {
$scope.user = User.get(userId);
WebpageTitle.updateTitle(gettextCatalog.getString('Participant') + ' ' + $scope.user.get_short_name());
});
$scope.$watch(function () {
return Projector.lastModified();
}, function () {