Browse Source

Ajax Alerts. #44

Rafał Pitoń 12 years ago
parent
commit
36b795b0d4

+ 33 - 5
misago/apps/alerts.py

@@ -4,16 +4,31 @@ from django.template import RequestContext
 from django.utils import timezone
 from django.utils.timezone import localtime
 from django.utils.translation import ugettext as _
-from misago.decorators import block_guest
-from misago.shortcuts import render_to_response
+from misago.apps.errors import error404
+from misago.decorators import block_guest, check_csrf
+from misago.shortcuts import render_to_response, json_response
+from misago.template.loader import render_to_string
 
 @block_guest
 def alerts(request):
+    if not request.user.alerts_date:
+        request.user.alerts_date = request.user.join_date
+
+    if request.is_ajax():
+        alerts_qs = request.user.alert_set.filter(date__gte=request.session.start).order_by('-id')
+        response_html = render_to_string('alerts/modal.html',
+                                         {'alerts': alerts_qs},
+                                         context_instance=RequestContext(request))
+        if request.user.alerts_date:
+            request.user.alerts = 0
+            request.user.alerts_date = timezone.now()
+            request.user.save(force_update=True)
+        return json_response(request,
+                             json={'html': response_html})
+
     now = localtime(timezone.now())
     yesterday = now - timedelta(days=1)
     alerts = {}
-    if not request.user.alerts_date:
-        request.user.alerts_date = request.user.join_date
 
     for alert in request.user.alert_set.order_by('-id'):
         alert.new = alert.date > request.user.alerts_date
@@ -49,9 +64,22 @@ def alerts(request):
     request.user.alerts = 0
     request.user.alerts_date = now
     request.user.save(force_update=True)
-    return render_to_response('alerts.html',
+    return render_to_response('alerts/list.html',
                               {
                               'new_alerts': new_alerts,
                               'alerts': alerts,
                               },
                               context_instance=RequestContext(request))
+
+
+@block_guest
+@check_csrf
+def clear_recent(request):
+    if not request.is_ajax() or not request.method == 'POST':
+        return error404(request)
+
+    del request.session['recent_alerts']
+    response_html = render_to_string('alerts/cleared.html',
+                                     context_instance=RequestContext(request))
+    return json_response(request,
+                         json={'html': response_html})

+ 2 - 1
misago/context_processors.py

@@ -32,6 +32,7 @@ def common(request):
             'settings': SafeSettings(),
             'stopwatch': request.stopwatch.time(),
             'user': request.user,
+            'recent_alerts': request.session.get('recent_alerts'),
             'version': __version__,
             'disable_search': False,
         })
@@ -44,7 +45,7 @@ def common(request):
             'reports': Forum.objects.special_model('reports'),
         })
     except AttributeError as e:
-        pass 
+        pass
     return context
 
 

+ 2 - 0
misago/middleware/user.py

@@ -17,6 +17,8 @@ def set_timezone(new_tz):
 class UserMiddleware(object):
     def process_request(self, request):
         if request.user.is_authenticated():
+            if request.user.alerts > 0:
+                request.session['recent_alerts'] = True
             request.session.rank = request.user.rank_id
             set_timezone(request.user.timezone)
             if request.session.remember_me:

+ 4 - 3
misago/sessions.py

@@ -117,7 +117,7 @@ class CrawlerSession(MisagoSession):
                 continue
             except IntegrityError:
                 try:
-                    self._session_rk =  Session.objects.get(id=self._session_key)                    
+                    self._session_rk =  Session.objects.get(id=self._session_key)
                 except Session.DoesNotExist:
                     continue
 
@@ -158,12 +158,12 @@ class HumanSession(MisagoSession):
             # IP invalid
             if settings.sessions_validate_ip and self._session_rk.ip != self._ip:
                 raise IncorrectSessionException()
-            
+
             # Session expired
             if timezone.now() - self._session_rk.last > timedelta(seconds=settings.SESSION_LIFETIME):
                 self.expired = True
                 raise IncorrectSessionException()
-            
+
             # Change session to matched and extract session user
             if self._session_rk.matched:
                 self.matched = True
@@ -182,6 +182,7 @@ class HumanSession(MisagoSession):
                 # Autolog failed
                 self.create(request)
         self.id = self._session_rk.id
+        self.start = self._session_rk.start
 
         # Make cookie live longer
         if request.firewall.admin:

+ 1 - 0
misago/urls.py

@@ -11,6 +11,7 @@ urlpatterns = patterns('misago.apps',
     url(r'^category/(?P<slug>(\w|-)+)-(?P<forum>\d+)/$', 'category.category', name="category"),
     url(r'^redirect/(?P<slug>(\w|-)+)-(?P<forum>\d+)/$', 'redirect.redirect', name="redirect"),
     url(r'^alerts/$', 'alerts.alerts', name="alerts"),
+    url(r'^alerts/clear-recent/$', 'alerts.clear_recent', name="alerts_clear_recent"),
     url(r'^news/$', 'newsfeed.newsfeed', name="newsfeed"),
     url(r'^tos/$', 'tos.tos', name="tos"),
     url(r'^markdown/$', 'help.markdown', name="help_md"),

+ 9 - 0
static/cranefly/css/cranefly.css

@@ -768,6 +768,12 @@ footer .container .credits p{margin-bottom:0;color:#555;font-size:90%}footer .co
 .navbar-modbar .navbar-inner .navbar-search-form{background-color:#0d0d0d;border-radius:3px;margin-top:6px;padding:1px 0}.navbar-modbar .navbar-inner .navbar-search-form input{background-color:#0d0d0d;border:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;margin:0;color:#eee;text-shadow:0 1px 0 #000}
 .navbar-modbar .navbar-inner .navbar-search-form button{margin:0;opacity:.6;filter:alpha(opacity=60)}.navbar-modbar .navbar-inner .navbar-search-form button i{background-image:url("../img/glyphicons-halflings-white.png");opacity:1;filter:alpha(opacity=100)}
 .navbar-modbar .navbar-inner .navbar-search-form button:hover,.navbar-modbar .navbar-inner .navbar-search-form button:active{opacity:1;filter:alpha(opacity=100)}
+.midman{background-color:#222;border-bottom:1px solid #fff;box-shadow:inset 0 4px 14px #000;display:none;padding:30px 0;color:#eee;text-shadow:0 1px 2px #000}.midman a:link,.midman a:visited{color:#e6e6e6}
+.midman a:hover,.midman a:active{color:#fff}
+.midman .table td{border-color:#555}
+.midman .ajax-error{display:none;color:#cf402e;font-size:35px;text-align:center}
+.midman .ajax-loader{padding-top:6px;font-size:21px;text-align:center}.midman .ajax-loader img{margin-top:-6px;margin-right:7px}
+.midman .btn{background-color:#999;border-color:#222;color:#eee !important;text-shadow:0 1px 1px #555}.midman .btn:hover,.midman .btn:active{color:#333 !important;text-shadow:0 1px 1px #fff}
 footer .breadcrumb{background:none;border:none;margin:0;padding:0;font-weight:bold;text-shadow:none}footer .breadcrumb li{text-shadow:none}footer .breadcrumb li a:link,footer .breadcrumb li a:active,footer .breadcrumb li a:visited,footer .breadcrumb li a:hover{color:#333}
 footer .breadcrumb li .divider{color:#999}
 footer .breadcrumb li.active{color:#555}
@@ -904,6 +910,9 @@ a.btn-link:hover,a.btn-link:active,a.btn-link:focus{color:#333;text-decoration:n
 .user-alerts td{vertical-align:middle}.user-alerts td.alert-icon{color:#d5d5d5;font-size:17.5px}.user-alerts td.alert-icon.alert-new{color:#cf402e}
 .user-alerts td.alert-message{color:#555;font-size:16.8px}.user-alerts td.alert-message a:link,.user-alerts td.alert-message a:visited{color:#333;font-weight:bold}
 .user-alerts td.alert-date{color:#999;text-align:right}
+.midman-alerts td{vertical-align:middle}.midman-alerts td.alert-icon{width:1%;font-size:17.5px;text-align:center}.midman-alerts td.alert-icon.alert-new{color:#cf402e}
+.midman-alerts td.alert-message{color:#999}.midman-alerts td.alert-message a:link,.midman-alerts td.alert-message a:visited{font-weight:bold}
+.midman-alerts td.alert-date{color:#999;font-size:11.9px;text-align:right}
 .news-feed .media{overflow:auto}.news-feed .media .media-object{border-radius:3px;width:52px;height:52px}
 .news-feed .media .media-body{margin-left:66px}.news-feed .media .media-body .post-preview:link,.news-feed .media .media-body .post-preview:active,.news-feed .media .media-body .post-preview:visited,.news-feed .media .media-body .post-preview:hover{display:block;margin-top:7px;color:#333;font-size:16.8px;text-decoration:none}
 .news-feed .media .media-body .media-footer{margin:0;color:#999;font-size:10.5px;font-weight:normal}.news-feed .media .media-body .media-footer a{color:#555}

+ 1 - 0
static/cranefly/css/cranefly.less

@@ -67,6 +67,7 @@
 @import "cranefly/header.less";
 @import "cranefly/scaffolding.less";
 @import "cranefly/navbar.less";
+@import "cranefly/midman.less";
 @import "cranefly/breadcrumbs.less";
 @import "cranefly/messages.less";
 @import "cranefly/forms.less";

+ 31 - 0
static/cranefly/css/cranefly/alerts.less

@@ -29,4 +29,35 @@
       text-align: right;
     }
   }
+}
+
+.midman-alerts {
+  td {
+    vertical-align: middle;
+
+    &.alert-icon {
+    	width: 1%;
+
+    	font-size: @fontSizeLarge;
+    	text-align: center;
+
+    	&.alert-new {
+    		color: @itemNewColor;
+    	}
+    }
+
+    &.alert-message {
+    	color: @grayLight;
+
+      a:link, a:visited {
+        font-weight: bold;
+      }
+    }
+
+    &.alert-date {
+      color: @grayLight;
+      font-size: @fontSizeSmall;
+      text-align: right;
+    }
+  }
 }

+ 60 - 0
static/cranefly/css/cranefly/midman.less

@@ -0,0 +1,60 @@
+// Fancy Ajax "modal" alt
+// -------------------------
+
+.midman {
+	background-color: @midmanBackground;
+	border-bottom: 1px solid lighten(@bodyBackground, 8%);
+	box-shadow: inset 0px 4px 14px @black;
+	display: none;
+	padding: (@baseLineHeight * 1.5) 0px;
+
+	color: @grayLighter;
+	text-shadow: 0px 1px 2px @black;
+
+	a:link, a:visited {
+		color: darken(@white, 10%);
+	}
+
+	a:hover, a:active {
+		color: @white;
+	}
+
+	.table {
+		td {
+			border-color: @gray;
+		}
+	}
+
+	.ajax-error {
+		display: none;
+
+		color: @red;
+		font-size: @fontSizeLarge * 2;
+		text-align: center;
+	}
+
+	.ajax-loader {
+		padding-top: 6px;
+
+		font-size: @fontSizeLarge * 1.2;
+		text-align: center;
+
+		img {
+			margin-top: -6px;
+			margin-right: 7px;
+		}
+	}
+
+	.btn {
+		background-color: @grayLight;
+		border-color: @grayDarker;
+
+		color: @grayLighter !important;
+		text-shadow: 0px 1px 1px @gray;
+
+		&:hover, &:active {
+			color: @textColor !important;
+			text-shadow: 0px 1px 1px @white;
+		}
+	}
+}

+ 4 - 0
static/cranefly/css/variables.less

@@ -259,6 +259,10 @@
 @navbarSearchWidth:               300px;
 @navbarSearchPadding:             8px;
 
+// Midman
+// -------------------------
+@midmanBackground: @grayDarker;
+
 // Footer
 // -------------------------
 @footerHeight:                        90px;

BIN
static/cranefly/img/ajax-loader.gif


+ 79 - 0
static/cranefly/js/cranefly.js

@@ -192,6 +192,85 @@ function youtube_player(element, movie_id, startfrom) {
   return true;
 }
 
+// Ajax: Reports and Alerts
+$(function() {
+  var midman = $('.midman');
+  var midman_loader = midman.find('.ajax-loader');
+  var midman_error = midman.find('.ajax-error');
+  var midman_content = midman.find('.loaded-content');
+  var midman_content_id = false;
+  var midman_cache = new Array();
+  var midman_request = false;
+
+  function midman_open(content_id) {
+    midman_error.slideUp(200);
+
+    if (midman_content_id != false) {
+      midman_close();
+      if (midman_request != false) {
+        midman_request.abort();
+      }
+    }
+
+    midman_content_id = content_id;
+    $(midman_content_id).parent().addClass('active');
+
+    if (midman_content_id in midman_cache) {
+      midman_loader.hide();
+      midman_content.show();
+      midman_content.html(midman_cache[midman_content_id]);
+      midman.slideDown(200);
+      return;
+    }
+
+    midman_loader.show();
+    midman_content.hide();
+    midman.slideDown(200);
+
+    midman_request = $.ajax({
+      url: $(midman_content_id).attr('href')
+    }).done(function(data) {
+      midman_cache[midman_content_id] = data.html;
+      midman_content.html(data.html);
+      midman_content.slideDown(200);
+      midman_loader.fadeOut(200);
+    });
+  }
+
+  function midman_close() {
+    if (midman_content_id != false) {
+      $(midman_content_id).parent().removeClass('active');
+      midman_content_id = false;
+      midman.slideUp(200);
+    }
+  }
+
+  $('.midman-close').live('click', function() {
+    midman_close()
+  });
+
+  $('.midman form').live('submit', function() {
+    var csrf_token = $(this).find('input[name="_csrf_token"]').val();
+    $.post(this.action, {'_csrf_token': csrf_token}, "json").done(function(data, textStatus, jqXHR) {
+      midman_cache[midman_content_id] = data.html;
+      midman_content.fadeOut(100);
+      midman_content.html(data.html);
+      midman_content.fadeIn(100);
+    });
+    return false;
+  });
+
+  $('.nav-alerts').click(function() {
+    this_content_id = '.nav-alerts';
+    if (midman_content_id == this_content_id) {
+      midman_close(this_content_id)
+    } else {
+      midman_open(this_content_id)
+    }
+    return false;
+  });
+});
+
 // Ajax: Post votes
 $(function() {
   $('.post-rating-actions').each(function() {

+ 10 - 0
templates/cranefly/alerts/cleared.html

@@ -0,0 +1,10 @@
+<div class="row midman-alerts">
+  <div class="span10 offset1">
+    <h2>{% trans %}Your Notifications{% endtrans %}</h2>
+    <p class="lead">{% trans %}Your recent notifications list has been cleared.{% endtrans %}</p>
+    <p class="lead">
+      <a href="{{ url('alerts') }}" class="btn">{% trans %}All Notifications{% endtrans %}</a>
+      <a href="#" class="btn midman-close">{% trans %}Close{% endtrans %}</a>
+    </p>
+  </div>
+</div>

+ 0 - 0
templates/cranefly/alerts.html → templates/cranefly/alerts/list.html


+ 43 - 0
templates/cranefly/alerts/modal.html

@@ -0,0 +1,43 @@
+<div class="row midman-alerts">
+  <div class="span10 offset1">
+    {% if alerts %}
+    {% if user.alerts %}
+    <h2>{% trans alerts=user.alerts %}You have one new alert{% pluralize %}You have {{ alerts }} new alerts{% endtrans %}</h2>
+    {% else %}
+    <h2>{% trans %}Your Notifications{% endtrans %}</h2>
+    {% endif %}
+    <table class="table">
+      <tbody>
+        {% for alert in alerts %}
+        <tr>
+          <td class="alert-icon{% if alert.date > user.alerts_date %} alert-new{% endif %}">
+            {% if alert.date > user.alerts_date %}
+            <i class="icon-star tooltip-top" title="{% trans %}New notification{% endtrans %}"></i>
+            {% else %}
+            <i class="icon-star-empty tooltip-top" title="{% trans %}Old notification{% endtrans %}"></i>
+            {% endif %}
+          </td>
+          <td class="alert-message">{{ (_(alert.message) % alert.vars())|safe }}</td>
+          <td class="alert-date">{{ alert.date|reltimesince }}</td>
+        </tr>
+        {% endfor %}
+      </tbody>
+    </table>
+    <p class="lead">
+      <form action="{{ url('alerts_clear_recent') }}" method="post" class="form-inline">
+        <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+        <a href="{{ url('alerts') }}" class="btn">{% trans %}All Notifications{% endtrans %}</a>
+        <button type="submit" class="btn">{% trans %}Clear List{% endtrans %}</button>
+        <a href="#" class="btn midman-close">{% trans %}Close{% endtrans %}</a>
+      </form>
+    </p>
+    {% else %}
+    <h2>{% trans %}Your Notifications{% endtrans %}</h2>
+    <p class="lead">{% trans %}You have no recent notifications.{% endtrans %}</p>
+    <p class="lead">
+      <a href="{{ url('alerts') }}" class="btn">{% trans %}All Notifications{% endtrans %}</a>
+      <a href="#" class="btn midman-close">{% trans %}Close{% endtrans %}</a>
+    </p>
+    {% endif %}
+  </div>
+</div>

+ 12 - 4
templates/cranefly/layout.html

@@ -76,10 +76,12 @@
         <ul id="fancy-user-nav" class="nav navbar-blocks navbar-compact pull-right">
           {{ hook_user_menu_important_prepend|safe }}
           {% if acl.reports.can_handle() and monitor.reported_posts %}
-          <li><a href="{{ url('reports') }}" title="{% trans %}There are unresolved reports!{% endtrans %}" class="tooltip-bottom fluid"><i class="icon-warning-sign"></i><span class="label label-important">{{ monitor.reported_posts }}</span></a></li>
+          <li><a href="{{ url('reports') }}" title="{% trans %}There are unresolved reports!{% endtrans %}" class="tooltip-bottom nav-reports fluid"><i class="icon-warning-sign"></i><span class="label label-important">{{ monitor.reported_posts }}</span></a></li>
           {% endif %}
           {% if user.alerts %}
-          <li><a href="{{ url('alerts') }}" title="{% trans %}You have new notifications!{% endtrans %}" class="tooltip-bottom fluid"><i class="icon-asterisk"></i><span class="label label-important">{{ user.alerts }}</span></a></li>
+          <li><a href="{{ url('alerts') }}" title="{% trans %}You have new notifications!{% endtrans %}" class="tooltip-bottom nav-alerts fluid"><i class="icon-asterisk"></i><span class="label label-important">{{ user.alerts }}</span></a></li>
+          {% elif recent_alerts %}
+          <li><a href="{{ url('alerts') }}" title="{% trans %}Your Notifications{% endtrans %}" class="tooltip-bottom nav-alerts fluid"><i class="icon-asterisk"></i></a></li>
           {% endif %}
           {% if settings.enable_private_threads and acl.private_threads.can_participate() and user.unread_pds %}
           <li><a href="{{ url('private_threads') }}" title="{% trans %}There are unread Private Threads!{% endtrans %}" class="tooltip-bottom fluid"><i class="icon-inbox"></i><span class="label label-important">{{ user.unread_pds }}</span></a></li>
@@ -111,9 +113,9 @@
           <ul class="nav navbar-blocks pull-right">
             {{ hook_user_menu_prepend|safe }}
             {% if acl.reports.can_handle() %}
-            <li><a href="{{ url('reports') }}" title="{% if monitor.reported_posts %}{% trans %}There are unresolved reports!{% endtrans %}{% else %}{% trans %}Reports{% endtrans %}{% endif %}" class="tooltip-bottom fluid"><i class="icon-warning-sign"></i>{% if monitor.reported_posts %}<span class="label label-important">{{ monitor.reported_posts }}</span>{% endif %}</a></li>
+            <li><a href="{{ url('reports') }}" title="{% if monitor.reported_posts %}{% trans %}There are unresolved reports!{% endtrans %}{% else %}{% trans %}Reports{% endtrans %}{% endif %}" class="tooltip-bottom nav-reports fluid"><i class="icon-warning-sign"></i>{% if monitor.reported_posts %}<span class="label label-important">{{ monitor.reported_posts }}</span>{% endif %}</a></li>
             {% endif %}
-            <li><a href="{{ url('alerts') }}" title="{% if user.alerts %}{% trans %}You have new notifications!{% endtrans %}{% else %}{% trans %}Your Notifications{% endtrans %}{% endif %}" class="tooltip-bottom fluid"><i class="icon-asterisk"></i>{% if user.alerts %}<span class="label label-important">{{ user.alerts }}</span>{% endif %}</a></li>
+            <li><a href="{{ url('alerts') }}" title="{% if user.alerts %}{% trans %}You have new notifications!{% endtrans %}{% else %}{% trans %}Your Notifications{% endtrans %}{% endif %}" class="tooltip-bottom nav-alerts fluid"><i class="icon-asterisk"></i>{% if user.alerts %}<span class="label label-important">{{ user.alerts }}</span>{% endif %}</a></li>
             {% if settings.enable_private_threads and acl.private_threads.can_participate() %}
             <li><a href="{{ url('private_threads') }}" title="{% if user.unread_pds %}{% trans %}There are unread Private Threads!{% endtrans %}{% else %}{% trans %}Your Private Threads{% endtrans %}{% endif %}" class="tooltip-bottom fluid"><i class="icon-inbox"></i>{% if user.unread_pds %}<span class="label label-important">{{ user.unread_pds }}</span>{% endif %}</a></li>
             {% endif %}
@@ -138,6 +140,12 @@
     </div>
   </div>
 
+  <div class="midman">
+    <div class="loaded-content"></div>
+    <div class="ajax-loader"><img src="{{ STATIC_URL }}cranefly/img/ajax-loader.gif" alt="{% trans %}Loading...{% endtrans %}">{% trans %}Loading...{% endtrans %}</div>
+    <div class="ajax-error"><i class="icon-remove"></i></div>
+  </div>
+
   {% block container %}
   <div class="container container-primary">
     {{ messages_list(messages) }}