diff --git a/notifications/managers.py b/notifications/managers.py index 4ec6b22b904ec620247661c0314472a25ade2ba7..1ac9a2e2e0a9f527127475ed4906cdca2477b07c 100644 --- a/notifications/managers.py +++ b/notifications/managers.py @@ -13,6 +13,10 @@ class NotificationQuerySet(models.query.QuerySet): """Return only unread items in the current queryset""" return self.filter(unread=True) + def pseudo_unread(self): + """Return only unread items in the current queryset""" + return self.filter(pseudo_unread=True) + def read(self): """Return only read items in the current queryset""" return self.filter(unread=False) @@ -27,6 +31,16 @@ class NotificationQuerySet(models.query.QuerySet): return qs.update(unread=False) + def mark_all_as_pseudo_read(self, recipient=None): + """Mark as read any unread messages in the current queryset.""" + # We want to filter out read ones, as later we will store + # the time they were marked as read. + qs = self.pseudo_unread() + if recipient: + qs = qs.filter(recipient=recipient) + + return qs.update(pseudo_unread=False) + def mark_all_as_unread(self, recipient=None): """Mark as unread any read messages in the current queryset.""" qs = self.read() diff --git a/notifications/migrations/0003_notification_pseudo_unread.py b/notifications/migrations/0003_notification_pseudo_unread.py new file mode 100644 index 0000000000000000000000000000000000000000..6e6953bef863e3fd5fd39d9818696faffc1cefa5 --- /dev/null +++ b/notifications/migrations/0003_notification_pseudo_unread.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.4 on 2017-10-29 20:37 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notifications', '0002_auto_20171021_1821'), + ] + + operations = [ + migrations.AddField( + model_name='notification', + name='pseudo_unread', + field=models.BooleanField(default=True), + ), + ] diff --git a/notifications/models.py b/notifications/models.py index 4e54bf7c86e9642511b916be8a31d93e499e2a1f..9ba39d7c684158db8ec5de3fc6fbc4f8e11dc644 100644 --- a/notifications/models.py +++ b/notifications/models.py @@ -43,6 +43,7 @@ class Notification(models.Model): recipient = models.ForeignKey(settings.AUTH_USER_MODEL, blank=False, related_name='notifications') unread = models.BooleanField(default=True) + pseudo_unread = models.BooleanField(default=True) # Used to keep notification-bg "active" actor_content_type = models.ForeignKey(ContentType, related_name='notify_actor') actor_object_id = models.CharField(max_length=255) @@ -105,19 +106,22 @@ class Notification(models.Model): return id2slug(self.id) def mark_toggle(self): - if self.unread: + if self.pseudo_unread: self.unread = False - self.save() + self.pseudo_unread = False else: self.unread = True - self.save() + self.pseudo_unread = True + self.save() def mark_as_read(self): - if self.unread: + if self.unread or self.pseudo_unread: self.unread = False + self.pseudo_unread = False self.save() def mark_as_unread(self): - if not self.unread: + if not self.unread or not self.pseudo_unread: self.unread = True + self.pseudo_unread = True self.save() diff --git a/notifications/templatetags/notifications_tags.py b/notifications/templatetags/notifications_tags.py index 42c62d09bfa42bf4735008e895623171f7f500ec..900fa7c96459869ac6522ed42bb86a3c0781e87b 100644 --- a/notifications/templatetags/notifications_tags.py +++ b/notifications/templatetags/notifications_tags.py @@ -1,35 +1,55 @@ # -*- coding: utf-8 -*- +from django.core.urlresolvers import reverse from django.template import Library from django.utils.html import format_html register = Library() -@register.assignment_tag(takes_context=True) -def notifications_unread(context): - user = user_context(context) - if not user: - return '' - return user.notifications.unread().count() +@register.simple_tag(takes_context=True) +def live_notify_badge(context, classes=''): + html = "<span class='live_notify_badge {classes}'>0</span>".format(classes=classes) + return format_html(html) @register.simple_tag(takes_context=True) -def live_notify_badge(context, badge_class='live_notify_badge', classes=''): +def live_notify_list(context): user = user_context(context) if not user: return '' - html = "<span class='{badge_class} {classes}' data-count='{unread}'>{unread}</span>".format( - badge_class=badge_class, unread=user.notifications.unread().count(), - classes=classes - ) - return format_html(html) + html = '<div class="dropdown-menu notifications">' + + # User default links + html += '<h6 class="dropdown-header">Welcome {first_name} {last_name}</h6>'.format( + first_name=user.first_name, last_name=user.last_name) + + if hasattr(user, 'contributor'): + html += '<a class="dropdown-item" href="{url}">Personal Page</a>'.format( + url=reverse('scipost:personal_page')) + + # User specific links + if user.has_perm('scipost.can_read_partner_page'): + html += '<a class="dropdown-item" href="{url}">Partner Page</a>'.format( + url=reverse('partners:dashboard')) + if user.has_perm('scipost.can_view_timesheets'): + html += '<a class="dropdown-item" href="{url}">Financial Administration</a>'.format( + url=reverse('finances:finance')) + if user.has_perm('scipost.can_view_production'): + html += '<a class="dropdown-item" href="{url}">Production</a>'.format( + url=reverse('production:production')) + if user.has_perm('scipost.can_view_pool'): + html += '<a class="dropdown-item" href="{url}">Submission Pool</a>'.format( + url=reverse('submissions:pool')) + # Logout links + html += '<div class="dropdown-divider my-0"></div>' + html += '<a class="dropdown-item" href="{url}">Logout</a>'.format( + url=reverse('scipost:logout')) -@register.simple_tag -def live_notify_list(list_class='live_notify_list', classes=''): - html = "<ul class='{list_class} {classes}'></ul>".format(list_class=list_class, - classes=classes) + # Notifications + html += '<div class="dropdown-divider my-0"></div><h6 class="dropdown-header">Inbox</h6>' + html += '<div class="live_notify_list"></div></div>' return format_html(html) diff --git a/notifications/urls.py b/notifications/urls.py index 7fd8627126d17a50579d3d39bdd523c1841ab137..a80a7a6769ffa3f1c17e446232a00976a5840819 100644 --- a/notifications/urls.py +++ b/notifications/urls.py @@ -5,9 +5,7 @@ from . import views urlpatterns = [ url(r'^redirect/(?P<slug>\d+)$', views.forward, name='forward'), - url(r'^mark-all-as-read/$', views.mark_all_as_read, name='mark_all_as_read'), url(r'^mark-toggle/(?P<slug>\d+)/$', views.mark_toggle, name='mark_toggle'), - url(r'^delete/(?P<slug>\d+)/$', views.delete, name='delete'), url(r'^api/unread_count/$', views.live_unread_notification_count, name='live_unread_notification_count'), url(r'^api/list/$', views.live_notification_list, name='live_unread_notification_list'), diff --git a/notifications/views.py b/notifications/views.py index bcdf7e3b03fc93946b0d254b3136d9c9b342fc5a..b36eaab4806253da91d5c18cfc1aa033983249b9 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -24,22 +24,6 @@ def forward(request, slug): return redirect(notification.target.get_absolute_url()) -@login_required -@user_passes_test(is_test_user) -def mark_all_as_read(request): - request.user.notifications.mark_all_as_read() - - _next = request.GET.get('next') - - if _next: - return redirect(_next) - - if request.GET.get('json'): - return JsonResponse({'unread': 0}) - - return redirect('notifications:all') - - @login_required @user_passes_test(is_test_user) def mark_toggle(request, slug=None): @@ -58,22 +42,6 @@ def mark_toggle(request, slug=None): return redirect('notifications:all') -@login_required -@user_passes_test(is_test_user) -def delete(request, slug=None): - id = slug2id(slug) - - notification = get_object_or_404(Notification, recipient=request.user, id=id) - notification.delete() - - _next = request.GET.get('next') - - if _next: - return redirect(_next) - - return redirect('notifications:all') - - def live_unread_notification_count(request): if not request.user.is_authenticated(): data = {'unread_count': 0} @@ -101,6 +69,7 @@ def live_notification_list(request): for n in request.user.notifications.all()[:num_to_fetch]: struct = model_to_dict(n) + struct['unread'] = struct['pseudo_unread'] struct['slug'] = id2slug(n.id) if n.actor: if isinstance(n.actor, User): @@ -119,8 +88,11 @@ def live_notification_list(request): struct['timesince'] = n.timesince() list.append(struct) - if request.GET.get('mark_as_read'): - n.mark_as_read() + + if request.GET.get('mark_as_read'): + # Mark all as read + request.user.notifications.mark_all_as_read() + data = { 'unread_count': request.user.notifications.unread().count(), 'list': list diff --git a/production/views.py b/production/views.py index 49bfa8926e18bd7ba4c338287cb27d08c1c6110d..cc7e0d4063e42b191d0b4074367e3d58ee4b259e 100644 --- a/production/views.py +++ b/production/views.py @@ -1,4 +1,3 @@ -import datetime import mimetypes from django.contrib import messages diff --git a/scipost/static/scipost/assets/config/preconfig.scss b/scipost/static/scipost/assets/config/preconfig.scss index 2712abe464f69603c63da3aa904402da35af0717..63ef8702d5ef1fb7c096e2ffaeeee6a3f3a3704a 100644 --- a/scipost/static/scipost/assets/config/preconfig.scss +++ b/scipost/static/scipost/assets/config/preconfig.scss @@ -65,6 +65,13 @@ $card-shadow-color: #ccc; $card-grey-border-bottom-color: #d0d1d5; $card-grey-border-color: #e5e6e9 #dfe0e4 $card-grey-border-bottom-color; +// Dropdown +// +$dropdown-link-active-color: $scipost-darkblue; +$dropdown-link-active-bg: $scipost-lightestblue; +$dropdown-header-color: $scipost-darkblue; + + // breadcrumb // $breadcrumb-bg: #f9f9f9; diff --git a/scipost/static/scipost/assets/css/_breadcrumb.scss b/scipost/static/scipost/assets/css/_breadcrumb.scss index cbb8d8e6be656517b8849ca6ee3e78ea67bd6646..4e3915ebcfbd361517c401b2fa6e389afcc6d538 100644 --- a/scipost/static/scipost/assets/css/_breadcrumb.scss +++ b/scipost/static/scipost/assets/css/_breadcrumb.scss @@ -9,6 +9,7 @@ .container { width: 100%; display: flex; + flex-wrap: nowrap; } .breadcrumb-item { diff --git a/scipost/static/scipost/assets/css/_popover.scss b/scipost/static/scipost/assets/css/_dropdown.scss similarity index 54% rename from scipost/static/scipost/assets/css/_popover.scss rename to scipost/static/scipost/assets/css/_dropdown.scss index 5ef240e8b434fe68d4c9fd09f3c5052a61ad69f6..e1ca413f3a08e1c96d252ce5e4a33d5fc8dd60c1 100644 --- a/scipost/static/scipost/assets/css/_popover.scss +++ b/scipost/static/scipost/assets/css/_dropdown.scss @@ -1,43 +1,18 @@ -.popover { - width: 500px; - box-shadow: #ccc 0px 1px 2px 1px; -} .navbar-counter .nav-link:hover { background-color: $white; } -.popover.bs-popover-bottom, -.popover.bs-popover-auto[x-placement^="bottom"] { - .arrow::before { - border-bottom-color: rgb(221, 221, 221); - } - - .arrow::after { - border-bottom-color: #f7f7f7; - } -} - .notifications { - border: 0; - border-radius: 0; - - .popover-header { - font-size: 100%; - padding: 0.3rem 1rem; - font-weight: 600; - text-transform: uppercase; - - a { - color: $scipost-darkblue; - } - } + padding-top: 0; + padding-bottom: 0; - .popover-body { - padding: 0; + .dropdown-header { + padding: 1rem 1rem 0.5rem 1rem; + background-color: #f9f9f9; } - &.popover .list-group-item { + .dropdown-item { padding: 0.4rem 1rem; border-radius: 0; border-top: 1px solid #fff; @@ -47,18 +22,33 @@ justify-content: space-between; display: flex; + > div { + white-space: normal; + } + &:last-child { border-bottom: 0; } } + a.dropdown-item, + .dropdown-item a { + color: $scipost-lightblue; + + &:hover { + text-decoration: underline; + } + } + .actions { - display: block; + display: none; opacity: 0.0; transition: opacity 0.1s; width: 20px; height: 100%; - padding-left: 0.25rem; + padding-left: 1rem; + padding-right: 0.5rem; + padding-bottom: 0.1rem; .fa[data-toggle="tooltip"] { font-size: 1em; @@ -75,11 +65,17 @@ } } - .list-group-item:hover .actions { + .dropdown-item:hover .actions { opacity: 1.0; } } -.popover-header { - font-size: initial; +@media (min-width: 768px) { + .notifications { + min-width: 500px; + + .actions { + display: block; + } + } } diff --git a/scipost/static/scipost/assets/css/_homepage.scss b/scipost/static/scipost/assets/css/_homepage.scss index 16da6e2fd48b0948f08dec3ed1fe31c34d22b41c..2940939bb54bee8eaa06fa6383e895e80e042b68 100644 --- a/scipost/static/scipost/assets/css/_homepage.scss +++ b/scipost/static/scipost/assets/css/_homepage.scss @@ -24,8 +24,6 @@ max-height: 100%; height: 100%; -webkit-overflow-scrolling: touch; - padding-left: 1rem; - padding-right: 1rem; } .main-panel { @@ -44,6 +42,8 @@ border-left: 1px solid #ddd; position: relative; padding-top: 0.25rem; + padding-left: 1rem; + padding-right: 1rem; margin-top: 2rem; .card { @@ -70,10 +70,15 @@ padding-right: 0; } } +} + - @media (min-width: 768px) { +@media (min-width: 768px) { + .has-sidebar { .main-panel { width: calc(100% - 350px); + padding-left: 1rem; + padding-right: 1rem; } .sidebar { width: 350px; @@ -94,8 +99,10 @@ } } } +} - @media (min-width: 1280px) { +@media (min-width: 1280px) { + .has-sidebar { .main-panel { width: calc(100% - 400px); } diff --git a/scipost/static/scipost/assets/css/_nav.scss b/scipost/static/scipost/assets/css/_nav.scss index 9551795390051441481d3a22f222af65cf3f28a7..d2057d4236ebf4978611841c3eef8e4f2a484e61 100644 --- a/scipost/static/scipost/assets/css/_nav.scss +++ b/scipost/static/scipost/assets/css/_nav.scss @@ -68,6 +68,10 @@ nav.main-nav { } } +.nav { + flex-wrap: nowrap; +} + nav.submenu { margin-top: -1.5rem; margin-bottom: 1rem; @@ -78,11 +82,27 @@ nav.submenu { border-bottom: 1px solid #ddd; .item { - padding: 0 0.5rem; - border-right: 2px solid #fff; + padding: 0.1rem 0.5rem; + white-space: nowrap; + display: block; } .item:first-child, .item:last-child { border-right: 0; } } + +@media (min-width: 768px) { + nav.submenu { + display: flex; + flex-wrap: nowrap; + flex-direction: row; + + .item { + padding-top: 0; + padding-bottom: 0; + display: inline; + border-right: 2px solid #fff; + } + } +} diff --git a/scipost/static/scipost/assets/css/_navbar.scss b/scipost/static/scipost/assets/css/_navbar.scss index a1ffa8e02c7f39a7152f6c0ff12fa3e724f444cd..cf73379ead6176dcab645e6d2434bfc8923b5caf 100644 --- a/scipost/static/scipost/assets/css/_navbar.scss +++ b/scipost/static/scipost/assets/css/_navbar.scss @@ -4,6 +4,8 @@ */ .navbar { margin-bottom: 1.5rem; + display: block; + flex-wrap: nowrap; &.main-nav { border-bottom: 1px solid #ddd; @@ -25,10 +27,6 @@ } } - .navbar-nav { - flex-direction: row; - } - .active > .nav-link { border-color: $scipost-darkblue; box-shadow: 0 1px 0 0 #ccc; @@ -37,9 +35,25 @@ .highlighted > .nav-link { background-color: rgba(255, 255, 255, 0.6); } +} + +@media (min-width: 768px) { + .navbar { + display: flex; + + .navbar-nav { + margin-right: auto; + display: flex; + flex-direction: row; + } + + .nav-item { + margin-right: 0.5rem; + } - .nav-item { - margin-right: 0.5rem; + [data-toggle="collapse"] { + display: none; + } } } @@ -92,47 +106,55 @@ content: "\f0f3"; } } +} - .badge { - vertical-align: top; - margin-left: -15px; - margin-top: -5px; - height: 16px; - min-width: 16px; - line-height: 10px; - display: none; - padding: 0.25em; - border-radius: 99px; - border: 1px solid #f9f9f9; - } +.live_notify_badge { + vertical-align: top; + margin-left: -15px; + margin-top: -5px; + height: 16px; + min-width: 16px; + line-height: 10px; + display: none; + padding: 0.25em; + border-radius: 99px; + border: 1px solid #f9f9f9; } .notifications_container { - color: $scipost-lightestblue; + .badge_link { + color: $scipost-lightestblue; + + // &::after { + // content: none; + // } + } .user { color: $scipost-darkblue; } - .fa { + .fa-inbox { font-size: 150%; vertical-align: bottom; margin: 0 0.25rem; } + &.show .fa-inbox { + color: $scipost-darkblue; + } + &.positive_count { - color: $scipost-orange; + .badge_link { + color: $scipost-orange; + } .user { color: $scipost-darkblue; } - .badge { + .live_notify_badge { display: inline-block; } } - - &::after { - content: none; - } } diff --git a/scipost/static/scipost/assets/css/_type.scss b/scipost/static/scipost/assets/css/_type.scss index 056fe6dcb5bb8c21f77aee7affb8a68a0cfec105..fbe387a334e6b23a3e336d1cedcb3930c08a2171 100644 --- a/scipost/static/scipost/assets/css/_type.scss +++ b/scipost/static/scipost/assets/css/_type.scss @@ -31,7 +31,7 @@ h4 { line-height: normal; } -h5, h6 { +h5 { font-weight: 300; } diff --git a/scipost/static/scipost/assets/css/style.scss b/scipost/static/scipost/assets/css/style.scss index be68d319863f3b5d962c823aca10301be2f28b73..0c8543be7bae804e5d6b33003dea8c56748b2c20 100644 --- a/scipost/static/scipost/assets/css/style.scss +++ b/scipost/static/scipost/assets/css/style.scss @@ -21,6 +21,7 @@ @import "buttons"; @import "cards"; @import "code"; +@import "dropdown"; @import "form"; @import "grid"; @import "homepage"; @@ -32,7 +33,6 @@ @import "nav"; @import "page_header"; @import "pool"; -@import "popover"; @import "tables"; @import "tooltip"; @import "type"; diff --git a/scipost/static/scipost/assets/js/notifications.js b/scipost/static/scipost/assets/js/notifications.js index 49fbe7f24e3f96a1d8a2c82cd9191f42a3f1cfd6..a2d966ccd609d80ae51585dffda11ec2798b24c3 100644 --- a/scipost/static/scipost/assets/js/notifications.js +++ b/scipost/static/scipost/assets/js/notifications.js @@ -1,195 +1,150 @@ -var notify_container_class = "notifications_container"; -var notify_badge_class = "live_notify_badge"; -var notify_menu_class = "live_notify_list"; -var notify_api_url_count = "/notifications/api/unread_count/"; -var notify_api_url_list = "/notifications/api/list/"; -var notify_api_url_toggle_read = "/notifications/mark-toggle/"; -var notify_api_url_mark_all_read = "/notifications/mark-all-as-read/"; -var notify_fetch_count = "5"; -var notify_refresh_period = 60000; -var consecutive_misfires = 0; -var registered_functions = [fill_notification_badge]; - - -function initiate_popover(reinitiate) { - if(typeof reinitiate == 'undefined') { - reinitiate = false; - } - - var notification_template = '<div class="popover notifications" role="tooltip"><div class="arrow"></div><p class="popover-header"></p><div class="popover-body"></div></div>'; - - function get_notifications_title() { - return 'My inbox'; - } - - function get_notifications() { - var _str = '<ul id="notification-list" class="update_notifications list-group"><div class="w-100 text-center py-4"><i class="fa fa-circle-o-notch fa-2x fa-spin fa-fw"></i><span class="sr-only">Loading...</span></div></ul>'; - get_notification_list(); - return _str; +function fetch_api_data(callback, url, args) { + if (!url) { + var url = notify_api_url_count; } - $('.popover [data-toggle="tooltip"]').tooltip('dispose') - $('#notifications_badge').popover('dispose').popover({ - animation: false, - trigger: 'click', - title: get_notifications_title, - template: notification_template, - content: get_notifications, - container: 'body', - offset: '0, 9px', - placement: "bottom", - html: true - }).on('inserted.bs.popover', function() { - // Bloody js - setTimeout(function() { - $('.popover [data-toggle="tooltip"]').tooltip({ - animation: false, - delay: {"show": 500, "hide": 100}, - fallbackPlacement: 'clockwise', - placement: 'bottom' - }); - $('.popover .actions a').on('click', function() { - mark_toggle(this) - }) - $('.popover a.mark_all_read').on('click', function() { - mark_all_read(this) - }) - }, 1000); - }); - if (reinitiate) { - $('#notifications_badge').popover('show') + if (callback) { + //only fetch data if a function is setup + var r = new XMLHttpRequest(); + r.addEventListener('readystatechange', function(event){ + if (this.readyState === 4){ + if (this.status === 200){ + consecutive_misfires = 0; + var data = JSON.parse(r.responseText); + return callback(data, args); + } else { + consecutive_misfires++; + } + } + }) + r.open("GET", url + '?max=5', true); + r.send(); } } -function request_reinitiate(url) { - var r = new XMLHttpRequest(); - r.addEventListener('readystatechange', function(event){ - if (this.readyState == 4 && this.status == 200) { - fetch_api_data() - initiate_popover(reinitiate=true) - } - }) - r.open("GET", url, true); - r.send(); -} -function mark_all_read(el) { - request_reinitiate(notify_api_url_mark_all_read + '?json=1') -} - -function mark_toggle(el) { - request_reinitiate(notify_api_url_toggle_read + $(el).data('slug') + '/?json=1') -} +function update_counter_callback(data, args) { + var counter = data['unread_count']; + var el = $(args['element']); + if (typeof counter == 'undefined') { + counter = 0; + } -function fill_notification_badge(data) { - var badges = document.getElementsByClassName(notify_badge_class); - var container = $('.' + notify_container_class); - if (badges) { - for(var i = 0; i < badges.length; i++){ - badges[i].innerHTML = data.unread_count; - if (data.unread_count > 0) { - container.addClass('positive_count'); - } else { - container.removeClass('positive_count'); - } - } + el.html(counter); + if (counter > 0) { + el.parents('.notifications_container').addClass('positive_count') + } else { + el.parents('.notifications_container').removeClass('positive_count') } } -function get_notification_list() { - fetch_api_data(notify_api_url_list, true, function(data) { +function update_list_callback(data, args) { + var items = data['list']; + var el = $(args['element']); - var messages = data.list.map(function (item) { - var message = "<div>"; - if(typeof item.actor !== 'undefined'){ - message += '<strong>' + item.actor + '</strong>'; - } - if(typeof item.verb !== 'undefined'){ - message += " " + item.verb; - } - if(typeof item.target !== 'undefined'){ - if(typeof item.forward_link !== 'undefined') { - message += " <a href='" + item.forward_link + "'>" + item.target + "</a>"; - } else { - message += " " + item.target; - } + var messages = items.map(function (item) { + // Notification content + var message = ''; + if(typeof item.actor !== 'undefined'){ + message += '<strong>' + item.actor + '</strong>'; + } + if(typeof item.verb !== 'undefined'){ + message += " " + item.verb; + } + if(typeof item.target !== 'undefined'){ + if(typeof item.forward_link !== 'undefined') { + message += " <a href='" + item.forward_link + "'>" + item.target + "</a>"; + } else { + message += " " + item.target; } - if(typeof item.timesince !== 'undefined'){ - message += "<br><small class='text-muted'>" + item.timesince + " ago</small>"; + } + if(typeof item.timesince !== 'undefined'){ + message += "<br><small>"; + if(typeof item.forward_link !== 'undefined') { + message += " <a href='" + item.forward_link + "'>Direct link</a> · "; } - message += "</div>"; + message += "<span class='text-muted'>" + item.timesince + " ago</span></small>"; + } - if(item.unread) { - var mark_as_read = '<div class="actions"><a href="javascript:;" data-slug="' + item.slug + '"><i class="fa fa-circle" data-toggle="tooltip" data-placement="auto" title="Mark as read" aria-hidden="true"></i></a></div>'; - } else { - var mark_as_read = '<div class="actions"><a href="javascript:;" data-slug="' + item.slug + '"><i class="fa fa-circle-o" data-toggle="tooltip" data-placement="auto" title="Mark as unread" aria-hidden="true"></i></a></div>'; - } - return '<li class="list-group-item ' + (item.unread ? ' active' : '') + '">' + message + mark_as_read + '</li>'; - }).join(''); + // Notification actions + if(item.unread) { + var mark_toggle = '<a href="javascript:;" class="mark-toggle" data-slug="' + item.slug + '"><i class="fa fa-circle" data-toggle="tooltip" data-placement="auto" title="Mark as read" aria-hidden="true"></i></a>'; + } else { + var mark_toggle = '<a href="javascript:;" class="mark-toggle" data-slug="' + item.slug + '"><i class="fa fa-circle-o" data-toggle="tooltip" data-placement="auto" title="Mark as unread" aria-hidden="true"></i></a>'; + } - if (messages == '') { - messages = '<div class="text-center px-2 py-3"><i class="fa fa-star-o fa-2x" aria-hidden="true"></i><h3>You have no new notifications</h3></div>' + if(typeof item.forward_link !== 'undefined') { + mark_toggle += "<br><a href='" + item.forward_link + "' data-toggle='tooltip' data-placement='auto' title='Go to item'><i class='fa fa-share' aria-hidden='true'></i></a>"; } - document.getElementById('notification-list').innerHTML = messages; + // Complete list html + return '<li class="dropdown-item ' + (item.unread ? ' active' : '') + '"><div>' + message + '</div><div class="actions">' + mark_toggle + '</div></li>'; + }).join(''); + + if (messages == '') { + messages = '<li class="dropdown-item px-5"><em>You have no new notifications</em></li>' + } + + // Fill DOM + el.find('.live_notify_list').html(messages).trigger('refresh_notify_list'); +} + +function update_mark_callback(data, args) { + var el = $(args['element']); + $(el).parents('.dropdown-item').toggleClass('active'); + + $('.live_notify_badge').each(function(index, el) { + update_counter(el); }); } -function fetch_api_data(url, once, callback) { - if (!url) { - var url = notify_api_url_count; - } - if (!once) { - var once = false; - } - if (registered_functions.length > 0) { - //only fetch data if a function is setup - var r = new XMLHttpRequest(); - r.addEventListener('readystatechange', function(event){ - if (this.readyState === 4){ - if (this.status === 200){ - consecutive_misfires = 0; - var data = JSON.parse(r.responseText); - registered_functions.forEach(function (func) { func(data); }); - if (callback) { - return callback(data); - } - }else{ - consecutive_misfires++; - } - } - }) - r.open("GET", url + '?max=' + notify_fetch_count, true); - r.send(); - } - var timer = null; - if (!once) { - if (consecutive_misfires < 10 && !once) { - timer = setTimeout(fetch_api_data, notify_refresh_period); - } else { - var badges = document.getElementsByClassName(notify_badge_class); - if (badges) { - for (var i=0; i < badges.length; i++){ - badges[i].innerHTML = "!"; - badges[i].title = "Connection lost!" - } - } - } - } +function update_counter(el) { + fetch_api_data(update_counter_callback, "/notifications/api/unread_count/", {'element': el}); +} - return stop; - function stop() { - if (timer) { - clearTimeout(timer); - timer = 0; - } - } +function mark_toggle(el) { + var url = "/notifications/mark-toggle/" + $(el).data('slug') + "?json=1"; + fetch_api_data(update_mark_callback, url, {'element': el}); +} + +function update_list(el) { + fetch_api_data(update_list_callback, "/notifications/api/list/?mark_as_read=1", {'element': el}); } -setTimeout(fetch_api_data, 1000); +// setTimeout(fetch_api_data, 1000); $(function(){ - initiate_popover(); + $('.notifications_container') + .on('show.bs.dropdown', function() { + $(this).trigger('notification_open_list'); + }) + .on('notification_open_list', function() { + update_list(this); + }) + + $('.live_notify_badge').on('notification_count_updated', function() { + update_counter(this); + }).trigger('notification_count_updated'); + + + $('.live_notify_list').on('refresh_notify_list', function() { + // Bloody js + $(this).find('li.dropdown-item').on('click', function(e) { + e.stopPropagation(); + }); + $(this).find('[data-toggle="tooltip"]').tooltip({ + animation: false, + delay: {"show": 500, "hide": 100}, + fallbackPlacement: 'clockwise', + placement: 'bottom' + }); + $(this).find('.actions a.mark-toggle').on('click', function(e) { + e.stopPropagation(); + mark_toggle(this); + }); + }); + + // initiate_popover(); }); diff --git a/scipost/templates/scipost/navbar.html b/scipost/templates/scipost/navbar.html index 93ccffea2081b2623fabd849169e3cf6546e36a8..ff16f034b5a542138434c8a923496925b2e6e533 100644 --- a/scipost/templates/scipost/navbar.html +++ b/scipost/templates/scipost/navbar.html @@ -3,74 +3,75 @@ {% load scipost_extras %} -<nav class="navbar navbar-scroll navbar-light main-nav navbar-expand-lg"> - <div class="navbar-scroll-inner"> - <ul class="navbar-nav mr-auto"> - <li class="nav-item{% if request.path == '/' %} active{% endif %}"> - <a href="{% url 'scipost:index' %}" class="nav-link">Home</a> - </li> - <li class="nav-item{% if '/journals/' in request.path %} active{% endif %}"> - <a href="{% url 'journals:journals' %}" class="nav-link">Journals</a> - </li> - <li class="nav-item{% if '/submissions/' in request.path %} active{% endif %}"> - <a class="nav-link" href="{% url 'submissions:submissions' %}">Submissions</a> - </li> - <li class="nav-item{% if '/commentaries/' in request.path %} active{% endif %}"> - <a class="nav-link" href="{% url 'commentaries:commentaries' %}">Commentaries</a> +<nav class="navbar navbar-light main-nav navbar-expand-lg"> + <ul id="menu-navbar" class="navbar-nav collapse"> + <li class="nav-item{% if request.path == '/' %} active{% endif %}"> + <a href="{% url 'scipost:index' %}" class="nav-link">Home</a> + </li> + <li class="nav-item{% if '/journals/' in request.path %} active{% endif %}"> + <a href="{% url 'journals:journals' %}" class="nav-link">Journals</a> + </li> + <li class="nav-item{% if '/submissions/' in request.path %} active{% endif %}"> + <a class="nav-link" href="{% url 'submissions:submissions' %}">Submissions</a> + </li> + <li class="nav-item{% if '/commentaries/' in request.path %} active{% endif %}"> + <a class="nav-link" href="{% url 'commentaries:commentaries' %}">Commentaries</a> + </li> + <li class="nav-item{% if '/theses/' in request.path %} active{% endif %}"> + <a class="nav-link" href="{% url 'theses:theses' %}">Theses</a> + </li> + <li class="nav-item{% if '/about' in request.path %} active{% endif %}"> + <a class="nav-link" href="{% url 'scipost:about' %}">About SciPost</a> + </li> + + + + {% if user.is_authenticated %} + {% if request.user|is_in_group:'Testers' %} + <li class="nav-item highlighted dropdown navbar-counter"> + <div class="nav-link notifications_container"> + <a href="javascript:;" class="d-inline-block ml-1 dropdown-toggle badge_link" id="notifications_badge" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + <span class="user">{% if user.last_name %}{% if user.contributor %}{{ user.contributor.get_title_display }} {% endif %}{{ user.first_name }} {{ user.last_name }}{% else %}{{ user.username }}{% endif %}</span> + <i class="fa fa-inbox" aria-hidden="true"></i> + {% live_notify_badge classes="badge badge-pill badge-primary" %} + </a> + {% live_notify_list %} + </div> + </li> - <li class="nav-item{% if '/theses/' in request.path %} active{% endif %}"> - <a class="nav-link" href="{% url 'theses:theses' %}">Theses</a> + {% else %} + <li class="nav-item highlighted"> + <span class="nav-link">Logged in as {{ user.username }}</span> </li> - <li class="nav-item{% if '/about' in request.path %} active{% endif %}"> - <a class="nav-link" href="{% url 'scipost:about' %}">About SciPost</a> + {% endif %} + <li class="nav-item"> + <a class="nav-link" href="{% url 'scipost:logout' %}">Logout</a> </li> - - {% if user.is_authenticated %} - {% if request.user|is_in_group:'Testers' %} - <li class="nav-item highlighted dropdown navbar-counter"> - <div class="nav-link"> - <a href="javascript:;" class="d-inline-block ml-1 dropdown-toggle notifications_container" id="notifications_badge"> - <span class="user">{% if user.last_name %}{% if user.contributor %}{{ user.contributor.get_title_display }} {% endif %}{{ user.first_name }} {{ user.last_name }}{% else %}{{ user.username }}{% endif %}</span> - <i class="fa fa-inbox" aria-hidden="true"></i> - {% live_notify_badge classes="badge badge-pill badge-primary" %} - </a> - {% live_notify_list classes="update_notifications d-none" %} - </div> - - </li> - {% else %} - <li class="nav-item highlighted"> - <span class="nav-link">Logged in as {{ user.username }}</span> + {% if perms.scipost.can_view_production %} + <li class="nav-item{% if '/production' in request.path %} active{% endif %}"> + <a class="nav-link" href="{% url 'production:production' %}">Production</a> </li> {% endif %} - <li class="nav-item"> - <a class="nav-link" href="{% url 'scipost:logout' %}">Logout</a> + {% if user.contributor %} + <li class="nav-item{% if '/personal_page' in request.path %} active{% endif %}"> + <a class="nav-link" href="{% url 'scipost:personal_page' %}">Personal Page</a> </li> - {% if perms.scipost.can_view_production %} - <li class="nav-item{% if '/production' in request.path %} active{% endif %}"> - <a class="nav-link" href="{% url 'production:production' %}">Production</a> - </li> - {% endif %} - {% if user.contributor %} - <li class="nav-item{% if '/personal_page' in request.path %} active{% endif %}"> - <a class="nav-link" href="{% url 'scipost:personal_page' %}">Personal Page</a> - </li> - {% endif %} - {% if user.partner_contact %} - <li class="nav-item{% if '/partners/dashboard' in request.path %} active{% endif %}"> - <a class="nav-link" href="{% url 'partners:dashboard' %}">Partner Page</a> - </li> - {% endif %} - {% else %} - <li class="nav-item{% if request.path == '/login/' %} active{% endif %}"> - <a class="nav-link" href="{% url 'scipost:login' %}">Login</a> + {% endif %} + {% if user.partner_contact %} + <li class="nav-item{% if '/partners/dashboard' in request.path %} active{% endif %}"> + <a class="nav-link" href="{% url 'partners:dashboard' %}">Partner Page</a> </li> {% endif %} + {% else %} + <li class="nav-item{% if request.path == '/login/' %} active{% endif %}"> + <a class="nav-link" href="{% url 'scipost:login' %}">Login</a> + </li> + {% endif %} - </ul> - <form action="{% url 'scipost:search' %}" method="get" class="form-inline search-nav-form"> - <input class="form-control" id="id_q" maxlength="100" name="q" type="text" required="required" value="{{search_query|default:''}}"> - <input class="btn btn-secondary" type="submit" value="Search"> - </form> - </div> + </ul> + <a class="btn btn-block my-2" data-toggle="collapse" href="#menu-navbar" aria-expanded="false" aria-controls="menu-navbar">Show menu <i class="fa fa-angle-down" aria-hidden="true"></i></a> + <form action="{% url 'scipost:search' %}" method="get" class="form-inline search-nav-form"> + <input class="form-control" id="id_q" maxlength="100" name="q" type="text" required="required" value="{{search_query|default:''}}"> + <input class="btn btn-secondary" type="submit" value="Search"> + </form> </nav>