Commits

Anonymous committed cbda252

Added different notification types. Requires a migration that is yet
missing. That should unsuck the notifications we currently have.

Comments (0)

Files changed (10)

+Schema changes from first release to development version.  This will
+later be integrated into a proper update script:
+
+alter table user_messages add column type varchar(10) after text;

solace/application.py

             section_url=url_for('kb.overview', lang_code=key)
         ) for key, locale in list_languages()]
 
-    def flash(self, message):
+    def flash(self, message, error=False):
         """Flashes a message."""
-        self.session.setdefault('flashes', []).append(message)
+        type = error and 'error' or 'info'
+        self.session.setdefault('flashes', []).append((type, message))
 
     def pull_flash_messages(self):
         """Returns all flash messages.  They will be removed from the
         if self.user is not None:
             to_delete = set()
             for msg in UserMessage.query.filter_by(user=self.user).all():
-                msgs.append(msg.text)
+                msgs.append((msg.type, msg.text))
                 to_delete.add(msg.id)
             if to_delete:
                 UserMessage.query.filter(UserMessage.id.in_(to_delete)).delete()
     """A message for a user."""
     query = session.query_property()
 
-    def __init__(self, user, text):
+    def __init__(self, user, text, type='info'):
+        assert type in ('info', 'error'), 'invalid message type'
         self.user = user
         self.text = text
+        self.type = type
         session.add(self)
 
     @simple_repr
     # who was the message sent to?
     Column('user_id', Integer, ForeignKey('users.user_id')),
     # the text of the message
-    Column('text', String(512))
+    Column('text', String(512)),
+    # the type of the message
+    Column('type', String(10))
 )
 
 topics = Table('topics', metadata,

solace/static/layout.css

     left: 0;
     top: 0;
     right: 0;
-    padding: 5px;
+    padding: 0;
     z-index: 100000;
     border-bottom: 1px solid;
 }
 
 #flash_message p {
     margin: 0;
-    padding: 5px 10px;
+    padding: 7px 13px;
+    cursor: default;
 }
 
-#flash_message div.close {
-    float: right;
-    padding: 5px 10px;
+#flash_message p + p {
+    padding-top: 0;
 }
 
-#flash_message div.close a {
-    display: block;
-    color: white;
-    padding: 0;
-    height: 14px;
-    width: 14px;
-    text-decoration: none;
-    font-size: 10px;
+#flash_message p.error_message {
     font-weight: bold;
-    border: 1px solid;
-}
-
-#flash_message div.close a span {
-    display: none;
 }
 
 form dl dt {

solace/static/solace.js

   TRANSLATIONS : (new babel.Translations).install(),
 
   /* flash container enhanced? */
-  _flash_container_enhanced : false,
+  _flash_container_enhanced : false,  
 
   /* called by generated code if the UTC offset is not yet
      known to the server code */
           document.location.href = Solace.URL_ROOT + 'login?next='
             + encodeURIComponent(document.location.href);
         else if (response.message)
-          Solace.flash(response.message);
+          Solace.flash(response.message, 'error');
       }
       else {
         if (response.message)
     });
   },
 
+  /* adds the timeout behavior to one or multipe flashed items */
+  attachFlashTimeouts : function(items, container) {
+    items.each(function() {
+      var self = $(this), timeout = 0;
+      if (self.attr('class') == 'info_message')
+        window.setTimeout(function() {
+          self.animate({
+            height:   'hide'
+          }, 'fast', 'linear', function() {
+            self.remove();
+            if ($('p', container).length == 0)
+              container.remove();
+          });
+        }, 6000);
+    });
+  },
+
   /* return the flash container */
   getFlashContainer : function(nocreate) {
     var container = $('#flash_message');
       if (nocreate)
         return null;
       container = $('<div id="flash_message"></div>').insertAfter('ul.navigation').hide();
+      Solace._flash_container_enhanced = false;
     }
     if (!Solace._flash_container_enhanced) {
       Solace._flash_container_enhanced = true;
-      container.prepend($('<div class="close"><a href="#"><span>[x]</span></a>')
-        .bind('click', function() {
-          container.fadeOut('slow', function() {
-            $('p', container).remove();
-          });
-          return false;
-        }));
+      container.hide().bind('mouseenter', function() {
+        container.stop().animate({opacity: 0.3}, 'fast');
+      }).bind('mouseleave', function() {
+        container.stop().animate({opacity: 1.0}, 'fast');
+      });
+      Solace.attachFlashTimeouts($('p', container), container);
     }
     return container;
   },
   /* fade in the flash message */
   fadeInFlashMessages : function() {
     var container = Solace.getFlashContainer(true);
-    if (container && !container.is(':visible'))
+    if (container && !container.is(':visible')) {
       container.animate({
         height:   'show',
         opacity:  'show'
       }, 'fast');
+    }
   },
 
   /* flashes a message from javascript */
-  flash : function(text) {
+  flash : function(text, type /* = info */) {
     var container = Solace.getFlashContainer();
-    $('<p>').text(text).appendTo(container);
+    var item = $('<p>').text(text).addClass((type || 'info') + '_message')
+      .appendTo(container);
+    Solace.attachFlashTimeouts(item, container);
     Solace.fadeInFlashMessages();
   },
 
     }
   });
 
-  /* flash messages get a close button and are nicely faded in */
+  /* flash messages are nicely faded in and out */
   Solace.fadeInFlashMessages();
 
   /* process the body HTML */

solace/templates/layout.html

   {%- set messages = request.pull_flash_messages() %}
   {%- if messages %}
     <div id="flash_message">
-      {# inject some javascript to directly hide the flash message
-         if JavaScript is available. #}
-      <script type="text/javascript">
-        document.getElementById('flash_message').style.display = 'none';
-      </script>
-    {%- for message in messages %}
-      <p>{{ message }}
+    {%- for type, message in messages %}
+      <p class="{{ type }}_message">{{ message }}
     {%- endfor %}
     </div>
   {%- endif %}

solace/themes/teal/static/style.css

 div.footer a                { color: #555; font-weight: bold; }
 
 /* flash message */
-#flash_message              { background: #2b6a7a; border-color: #0E3640; color: white; }
-#flash_message div.close a  { border-color: #42899b;
-                              background: #297385 url(flashtick.png) no-repeat; }
-#flash_message div.close a:hover
-                            { background-color: #54a4b8; }
+#flash_message              { border-color: #0E3640; color: white; }
+#flash_message p            { background: #2b6a7a; border-color: #0E3640; }
 
 /* contents */
 div.contents                { border-color: #ccc; }

solace/views/core.py

                 break
         else:
             request.flash(_(u'The password-reset key expired or the link '
-                            u'was invalid.'))
+                            u'was invalid.'), error=True)
             return redirect(url_for('core.reset_password'))
         new_password = user.set_random_password()
         session.commit()
                         u'log in now.'))
         return redirect(url_for('core.login'))
     request.flash(_(u'User activation failed.  The user is either already '
-                    u'activated or you followed a wrong link.'))
+                    u'activated or you followed a wrong link.'), error=True)
     return redirect(url_for('kb.overview'))
 
 

solace/views/kb.py

         message = _(u'You cannot vote on deleted posts.')
         if request.is_xhr:
             return json_response(message=message, error=True)
-        request.flash(message)
+        request.flash(message, error=True)
         return redirect(url_for(post))
 
     # otherwise
             message = _(u'You cannot upvote your own post.')
             if request.is_xhr:
                 return json_response(message=message, error=True)
-            request.flash(message)
+            request.flash(message, error=True)
             return redirect(url_for(post))
         # also some reputation is needed
         if not request.user.is_admin and \
                 settings.REPUTATION_MAP['UPVOTE']
             if request.is_xhr:
                 return json_response(message=message, error=True)
-            request.flash(message)
+            request.flash(message, error=True)
             return redirect(url_for(post))
         request.user.upvote(post)
     elif val == -1:
                 settings.REPUTATION_MAP['DOWNVOTE']
             if request.is_xhr:
                 return json_response(message=message, error=True)
-            request.flash(message)
+            request.flash(message, error=True)
             return redirect(url_for(post))
         request.user.downvote(post)
     else:
         message = _(u'You cannot accept deleted posts as answers')
         if request.is_xhr:
             return json_response(message=message, error=True)
-        request.flash(message)
+        request.flash(message, error=True)
         return redirect(url_for(post))
 
     topic = post.topic
             message = _(u'You cannot unaccept this reply as an answer.')
             if request.is_xhr:
                 return json_response(message=message, error=True)
-            request.flash(message)
+            request.flash(message, error=True)
             return redirect(url_for(post))
         topic.accept_answer(None, request.user)
         session.commit()
         message = _(u'You cannot accept this reply as answer.')
         if request.is_xhr:
             return json_response(message=message, error=True)
-        request.flash(message)
+        request.flash(message, error=True)
         return redirect(url_for(post))
     topic.accept_answer(post, request.user)
     session.commit()