diff --git a/Changelog.md b/Changelog.md index 65e9f4d8b..f737205f3 100644 --- a/Changelog.md +++ b/Changelog.md @@ -98,6 +98,7 @@ diaspora.yml file**. The existing settings from 0.4.x and before will not work a * Display photos on the profile page as thumbnails [#5521](https://github.com/diaspora/diaspora/pull/5521) * Unify not connected pages (sign in, sign up, forgot password) [#5391](https://github.com/diaspora/diaspora/pull/5391) * Port remaining stream pages to Bootstrap [#5715](https://github.com/diaspora/diaspora/pull/5715) +* Partial Backbone port of the notification dropdown [#5707](https://github.com/diaspora/diaspora/pull/5707) ## Bug fixes * orca cannot see 'Add Contact' button [#5158](https://github.com/diaspora/diaspora/pull/5158) diff --git a/app/assets/javascripts/app/views/header_view.js b/app/assets/javascripts/app/views/header_view.js index eaf6e9275..a309942e7 100644 --- a/app/assets/javascripts/app/views/header_view.js +++ b/app/assets/javascripts/app/views/header_view.js @@ -17,10 +17,12 @@ app.views.Header = app.views.Base.extend({ return this; }, - menuElement : function() { - return this.$("ul.dropdown"); + postRenderTemplate: function(){ + new app.views.Notifications({ el: '#notification_dropdown' }); }, + menuElement : function() { return this.$("ul.dropdown"); }, + toggleDropdown : function(evt) { if(evt){ evt.preventDefault(); } diff --git a/app/assets/javascripts/app/views/notifications_view.js b/app/assets/javascripts/app/views/notifications_view.js index 330022198..a6be69efe 100644 --- a/app/assets/javascripts/app/views/notifications_view.js +++ b/app/assets/javascripts/app/views/notifications_view.js @@ -3,11 +3,11 @@ app.views.Notifications = Backbone.View.extend({ events: { - "click .unread-toggle" : "toggleUnread" + "click .unread-toggle" : "toggleUnread", + "click #mark_all_read_link": "markAllRead" }, initialize: function() { - Diaspora.page.header.notifications.setUpNotificationPage(this); $(".unread-toggle .entypo").tooltip(); app.helpers.timeago($(document)); }, @@ -16,28 +16,20 @@ app.views.Notifications = Backbone.View.extend({ var note = $(evt.target).closest(".stream_element"); var unread = note.hasClass("unread"); - if (unread) { - this.setRead(note.data("guid")); - } - else { - this.setUnread(note.data("guid")); - } + if (unread){ this.setRead(note.data("guid")); } + else { this.setUnread(note.data("guid")); } }, - setRead: function(guid) { - $.ajax({ - url: "/notifications/" + guid, - data: { set_unread: false }, - type: "PUT", - context: this, - success: this.clickSuccess - }); - }, + getAllUnread: function(){ return $('.media.stream_element.unread'); }, - setUnread: function(guid) { + setRead: function(guid) { this.setUnreadStatus(guid, false); }, + + setUnread: function(guid){ this.setUnreadStatus(guid, true); }, + + setUnreadStatus: function(guid, state){ $.ajax({ url: "/notifications/" + guid, - data: { set_unread: true }, + data: { set_unread: state }, type: "PUT", context: this, success: this.clickSuccess @@ -49,47 +41,52 @@ app.views.Notifications = Backbone.View.extend({ this.updateView(data["guid"], type, data["unread"]); }, + markAllRead: function(){ + var self = this; + this.getAllUnread().each(function(i, el){ + self.setRead($(el).data("guid")); + }); + }, + updateView: function(guid, type, unread) { var change = unread ? 1 : -1, all_notes = $('ul.nav > li:eq(0) .badge'), type_notes = $('ul.nav > li[data-type=' + type + '] .badge'), header_badge = $('#notification_badge .badge_count'), - note = $('.stream_element[data-guid=' + guid + ']'); + note = $('.stream_element[data-guid=' + guid + ']'), + markAllReadLink = $('a#mark_all_read_link'), + translationKey = unread ? 'notifications.mark_read' : 'notifications.mark_unread'; - if(unread) { - note.removeClass("read").addClass("unread"); - $(".unread-toggle .entypo", note) + if(unread){ note.removeClass("read").addClass("unread"); } + else { note.removeClass("unread").addClass("read"); } + + $(".unread-toggle .entypo", note) .tooltip('destroy') .removeAttr("data-original-title") - .attr('title',Diaspora.I18n.t('notifications.mark_read')) + .attr('title',Diaspora.I18n.t(translationKey)) .tooltip(); - } - else { - note.removeClass("unread").addClass("read"); - $(".unread-toggle .entypo", note) - .tooltip('destroy') - .removeAttr("data-original-title") - .attr('title',Diaspora.I18n.t('notifications.mark_unread')) - .tooltip(); - } - all_notes.text( function(i,text) { return parseInt(text) + change }); - type_notes.text( function(i,text) { return parseInt(text) + change }); - header_badge.text( function(i,text) { return parseInt(text) + change }); - if(all_notes.text()>0){ - all_notes.addClass('badge-important').removeClass('badge-default'); - } else { - all_notes.removeClass('badge-important').addClass('badge-default'); - } - if(type_notes.text()>0){ - type_notes.addClass('badge-important').removeClass('badge-default'); - } else { - type_notes.removeClass('badge-important').addClass('badge-default'); - } - if(header_badge.text()>0){ + [all_notes, type_notes, header_badge].forEach(function(element){ + element.text(function(i, text){ + return parseInt(text) + change }); + }); + + [all_notes, type_notes].forEach(function(badge) { + if(badge.text() > 0) { + badge.addClass('badge-important').removeClass('badge-default'); + } + else { + badge.removeClass('badge-important').addClass('badge-default'); + } + }); + + if(header_badge.text() > 0){ header_badge.removeClass('hidden'); - } else { + markAllReadLink.removeClass('disabled'); + } + else{ header_badge.addClass('hidden'); + markAllReadLink.addClass('disabled'); } } }); diff --git a/app/assets/javascripts/widgets/notifications-badge.js b/app/assets/javascripts/widgets/notifications-badge.js index 06ea7bf48..fadb8e185 100644 --- a/app/assets/javascripts/widgets/notifications-badge.js +++ b/app/assets/javascripts/widgets/notifications-badge.js @@ -5,7 +5,6 @@ var self = this; var currentPage = 2; var notificationsLoaded = 10; - var isLoading = false; this.subscribe("widget/ready",function(evt, badge, dropdown) { $.extend(self, { @@ -48,19 +47,19 @@ self.ajaxLoader.show(); self.badge.addClass("active"); self.dropdown.css("display", "block"); - $('.notifications').addClass("loading"); + self.dropdownNotifications.addClass("loading"); self.getNotifications(); - + }; this.hideDropdown = function() { self.badge.removeClass("active"); self.dropdown.css("display", "none"); - $('.notifications').perfectScrollbar('destroy'); + self.dropdownNotifications.perfectScrollbar('destroy'); }; - this.getMoreNotifications = function() { - $.getJSON("/notifications?per_page=5&page="+currentPage, function(notifications) { + this.getMoreNotifications = function(page) { + $.getJSON("/notifications?per_page=5&page=" + page, function(notifications) { for(var i = 0; i < notifications.length; ++i) self.notifications.push(notifications[i]); notificationsLoaded += 5; @@ -68,6 +67,14 @@ }); }; + this.hideAjaxLoader = function(){ + self.ajaxLoader.find('img').fadeTo(200, 0, function(){ + self.ajaxLoader.hide(300, function(){ + self.ajaxLoader.find('img').css('opacity', 1); + }); + }); + }; + this.getNotifications = function() { $.getJSON("/notifications?per_page="+notificationsLoaded, function(notifications) { self.notifications = notifications; @@ -76,36 +83,31 @@ }; this.renderNotifications = function() { - self.dropdownNotifications.empty(); + this.dropdownNotifications.find('.media.stream_element').remove(); $.each(self.notifications, function(index, notifications) { $.each(notifications, function(index, notification) { - if($.inArray(notification, notifications) === -1) - self.dropdownNotifications.append(notification.note_html); + if($.inArray(notification, notifications) === -1){ + var node = self.dropdownNotifications.append(notification.note_html); + $(node).find(".unread-toggle .entypo").tooltip('destroy').tooltip(); + } }); }); + self.hideAjaxLoader(); + app.helpers.timeago(self.dropdownNotifications); - self.dropdownNotifications.find('.unread').each(function() { - Diaspora.page.header.notifications.setUpUnread( $(this) ); - }); - self.dropdownNotifications.find('.read').each(function() { - Diaspora.page.header.notifications.setUpRead( $(this) ); - }); - $('.notifications').perfectScrollbar('destroy'); - $('.notifications').perfectScrollbar(); - self.ajaxLoader.hide(); - isLoading = false; - $('.notifications').removeClass("loading"); + self.dropdownNotifications.perfectScrollbar('destroy').perfectScrollbar(); + var isLoading = false; + self.dropdownNotifications.removeClass("loading"); //Infinite Scrolling - $('.notifications').scroll(function() { - var bottom = $('.notifications').prop('scrollHeight') - $('.notifications').height(); - var currentPosition = $('.notifications').scrollTop(); + self.dropdownNotifications.scroll(function() { + var bottom = self.dropdownNotifications.prop('scrollHeight') - self.dropdownNotifications.height(); + var currentPosition = self.dropdownNotifications.scrollTop(); isLoading = ($('.loading').length === 1); if (currentPosition + 50 >= bottom && notificationsLoaded <= self.notifications.length && !isLoading) { - $('.notifications').addClass("loading"); - ++currentPage; - self.getMoreNotifications(); + self.dropdownNotifications.addClass("loading"); + self.getMoreNotifications(++currentPage); } }); }; diff --git a/app/assets/javascripts/widgets/notifications.js b/app/assets/javascripts/widgets/notifications.js deleted file mode 100644 index 3a0b5a099..000000000 --- a/app/assets/javascripts/widgets/notifications.js +++ /dev/null @@ -1,147 +0,0 @@ -// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later - -/* Copyright (c) 2010-2011, Diaspora Inc. This file is -* licensed under the Affero General Public License version 3 or later. See -* the COPYRIGHT file. -*/ - -(function() { - var Notifications = function() { - var self = this; - - this.subscribe("widget/ready", function(evt, badge, notificationMenu) { - $.extend(self, { - badge: badge, - count: parseInt(badge.html()) || 0, - notificationMenu: notificationMenu, - notificationPage: null - }); - - $("a.more").click( function(evt) { - evt.preventDefault(); - $(this).hide() - .next(".hidden") - .removeClass("hidden"); - }); - self.notificationMenu.find('a#mark_all_read_link').click(function(event) { - $.ajax({ - url: "/notifications/read_all", - type: "GET", - dataType:'json', - success: function(){ - self.notificationMenu.find('.unread').each(function() { - self.setUpRead( $(this) ); - }); - self.resetCount(); - } - }); - $(event.target).addClass("disabled"); - return false; - }); - }); - this.setUpNotificationPage = function( page ) { - self.notificationPage = page; - }; - this.unreadClick = function() { - $.ajax({ - url: "/notifications/" + $(this).closest(".stream_element,.notification_element").data("guid"), - data: { set_unread: true }, - type: "PUT", - success: self.clickSuccess - }); - }; - this.readClick = function() { - $.ajax({ - url: "/notifications/" + $(this).closest(".stream_element,.notification_element").data("guid"), - data: { set_unread: false }, - type: "PUT", - success: self.clickSuccess - }); - }; - this.setUpUnread = function( an_obj ) { - an_obj.removeClass("read").addClass( "unread" ); - an_obj.find('.unread-toggle') - .unbind("click") - .click(self.readClick) - .find('.entypo') - .tooltip('destroy') - .removeAttr('data-original-title') - .attr('title', Diaspora.I18n.t('notifications.mark_read')) - .tooltip(); - }; - this.setUpRead = function( an_obj ) { - an_obj.removeClass("unread").addClass( "read" ); - an_obj.find('.unread-toggle') - .unbind("click") - .click(self.unreadClick) - .find('.entypo') - .tooltip('destroy') - .removeAttr('data-original-title') - .attr('title', Diaspora.I18n.t('notifications.mark_unread')) - .tooltip(); - }; - this.clickSuccess = function( data ) { - var itemID = data["guid"]; - var isUnread = data["unread"]; - self.notificationMenu.find('.read,.unread').each(function() { - if ( $(this).data("guid") === itemID ) { - if ( isUnread ) { - self.notificationMenu.find('a#mark_all_read_link').removeClass('disabled'); - self.setUpUnread( $(this) ); - } else { - self.setUpRead( $(this) ); - } - } - }); - if ( self.notificationPage == null ) { - if ( isUnread ) { - self.incrementCount(); - }else{ - self.decrementCount(); - } - } else { - var type = $('.notification_element[data-guid=' + data["guid"] + ']').data('type'); - self.notificationPage.updateView(data["guid"], type, isUnread); - } - }; - this.showNotification = function(notification) { - $(notification.html).prependTo(this.notificationMenu) - .fadeIn(200) - .delay(8000) - .fadeOut(200, function() { - $(this).detach(); - }); - - if(typeof notification.incrementCount === "undefined" || notification.incrementCount) { - this.incrementCount(); - } - }; - - this.changeNotificationCount = function(change) { - self.count = Math.max( self.count + change, 0 ); - self.badge.text(self.count); - - if(self.count === 0) { - self.badge.addClass("hidden"); - } - else if(self.count === 1) { - self.badge.removeClass("hidden"); - } - }; - this.resetCount = function() { - self.count = 0; - this.changeNotificationCount(0); - }; - - this.decrementCount = function() { - self.changeNotificationCount(-1); - }; - - this.incrementCount = function() { - self.changeNotificationCount(1); - }; - }; - - Diaspora.Widgets.Notifications = Notifications; -})(); -// @license-end diff --git a/app/assets/stylesheets/header.scss b/app/assets/stylesheets/header.scss index 044580554..7e4757ae8 100644 --- a/app/assets/stylesheets/header.scss +++ b/app/assets/stylesheets/header.scss @@ -155,6 +155,7 @@ body > header { } .ajax_loader { + border-bottom: 1px solid $border-grey; text-align: center; padding: 15px; } @@ -162,28 +163,22 @@ body > header { overflow: hidden; position: relative; max-height: 325px; - } - .notification_element { - border-bottom: 1px solid $border-grey; - padding: 5px; - min-height: 35px; - line-height: 18px; + .media.stream_element { + border-bottom: 1px solid $border-grey; + padding: 5px; + min-height: 35px; + line-height: 18px; + margin: 0; - > .avatar { - height: 35px; - width: 35px; - float: left; - } + img.avatar { + width: 35px; + height: 35px; + } - .notification_message { - margin-left: 40px; - .tooltip { position: fixed; } - } + .pull-right > .aspect_membership_dropdown { display: none; } + .unread-toggle { margin: 10px; } - .unread-toggle { - padding: 9px 9px; - position: relative; .entypo { cursor: pointer; color: lighten($black,75%); @@ -193,14 +188,15 @@ body > header { .tooltip { position: fixed; } - } - &.unread { - background-color: $background-grey; - color: $black; - .unread-toggle { - .entypo { color: $black; } + &.unread { + background-color: $background-grey; + color: $black; + .unread-toggle { + .entypo { color: $black; } + } } + } } @@ -328,7 +324,7 @@ body > header { text-decoration: none; } - &:focus { :outline: none; } + &:focus { outline: none; } } .user-menu-more-indicator { diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index 3f50bbd88..5c3c7766e 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -40,8 +40,8 @@ class NotificationsController < ApplicationController pager.replace(result) end - @notifications.each do |n| - n.note_html = render_to_string( :partial => 'notify_popup_item', :locals => { :n => n } ) + @notifications.each do |note| + note.note_html = render_to_string( :partial => 'notification.html', :locals => { :note => note } ) end @group_days = @notifications.group_by{|note| note.created_at.strftime('%Y-%m-%d')} diff --git a/app/views/notifications/_notify_popup_item.haml b/app/views/notifications/_notify_popup_item.haml deleted file mode 100644 index ab7a43868..000000000 --- a/app/views/notifications/_notify_popup_item.haml +++ /dev/null @@ -1,7 +0,0 @@ -.notification_element{:data=>{:guid => n.id, :type => (Notification.types.key(n.type) || '')}, :class => (n.unread ? "unread" : "read")} - .pull-right.unread-toggle - %i.entypo.eye{ :title => (n.unread ? t('notifications.index.mark_read') : t('notifications.index.mark_unread')) } - = person_image_tag n.actors.first, :thumb_small - .notification_message - = notification_message_for(n) - %div= timeago(n.created_at) diff --git a/features/step_definitions/custom_web_steps.rb b/features/step_definitions/custom_web_steps.rb index 047a26f0b..ab42db0ba 100644 --- a/features/step_definitions/custom_web_steps.rb +++ b/features/step_definitions/custom_web_steps.rb @@ -216,7 +216,7 @@ Then /^the notification dropdown scrollbar should be visible$/ do end Then /^there should be (\d+) notifications loaded$/ do |n| - result = page.evaluate_script("$('.notification_element').length") + result = page.evaluate_script("$('.media.stream_element').length") result.should == n.to_i end diff --git a/spec/javascripts/app/views/notifications_view_spec.js b/spec/javascripts/app/views/notifications_view_spec.js index 34312118a..ab422515a 100644 --- a/spec/javascripts/app/views/notifications_view_spec.js +++ b/spec/javascripts/app/views/notifications_view_spec.js @@ -72,5 +72,35 @@ describe("app.views.Notifications", function(){ expect(this.readN.hasClass('unread')).toBeFalsy(); expect(this.readN.find('.unread-toggle .entypo').data('original-title')).toBe(Diaspora.I18n.t('notifications.mark_unread')); }); + + context("with a header", function() { + beforeEach(function() { + loginAs({name: "alice", avatar : {small : "http://avatar.com/photo.jpg"}, notifications_count : 2}); + this.header = new app.views.Header(); + $("header").prepend(this.header.el); + this.header.render(); + }); + + it("changes the header notifications count", function() { + var badge = $("#notification_badge .badge_count"); + var count = parseInt(badge.text(), 10); + + this.view.updateView(this.guid, this.type, true); + expect(parseInt(badge.text(), 10)).toBe(count + 1); + + this.view.updateView(this.guid, this.type, false); + expect(parseInt(badge.text(), 10)).toBe(count); + }); + + context("markAllRead", function() { + it("calls setRead for each unread notification", function(){ + spyOn(this.view, "setRead"); + this.view.markAllRead(); + expect(this.view.setRead).toHaveBeenCalledWith(this.view.$('.stream_element.unread').eq(0).data('guid')); + this.view.markAllRead(); + expect(this.view.setRead).toHaveBeenCalledWith(this.view.$('.stream_element.unread').eq(1).data('guid')); + }); + }); + }); }); }); diff --git a/spec/javascripts/widgets/notifications-spec.js b/spec/javascripts/widgets/notifications-spec.js deleted file mode 100644 index 9fd08458b..000000000 --- a/spec/javascripts/widgets/notifications-spec.js +++ /dev/null @@ -1,122 +0,0 @@ -/* Copyright (c) 2010-2011, Diaspora Inc. This file is - * licensed under the Affero General Public License version 3 or later. See - * the COPYRIGHT file. - */ -describe("Diaspora.Widgets.Notifications", function() { - var changeNotificationCountSpy, notifications, incrementCountSpy, decrementCountSpy; - - beforeEach(function() { - spec.loadFixture("aspects_index"); - this.view = new app.views.Header().render(); - - notifications = Diaspora.BaseWidget.instantiate("Notifications", this.view.$("#notification_badge .badge_count"), this.view.$(".notifications")); - - changeNotificationCountSpy = spyOn(notifications, "changeNotificationCount").and.callThrough(); - incrementCountSpy = spyOn(notifications, "incrementCount").and.callThrough(); - decrementCountSpy = spyOn(notifications, "decrementCount").and.callThrough(); - }); - - describe("clickSuccess", function(){ - it("changes the css to a read cell at stream element", function() { - this.view.$(".notifications").html( - '
' + - '
' - ); - notifications.clickSuccess({guid:2,unread:false}); - expect( this.view.$('.stream_element#2')).toHaveClass("read"); - }); - it("changes the css to a read cell at notications element", function() { - this.view.$(".notifications").html( - '
' + - '
' - ); - notifications.clickSuccess({guid:2,unread:false}); - expect( this.view.$('.notification_element#2')).toHaveClass("read"); - }); - it("changes the css to an unread cell at stream element", function() { - this.view.$(".notifications").html( - '
' + - '
' - ); - notifications.clickSuccess({guid:1,unread:true}); - expect( this.view.$('.stream_element#1')).toHaveClass("unread"); - }); - it("changes the css to an unread cell at notications element", function() { - this.view.$(".notifications").html( - '
' + - '
' - ); - notifications.clickSuccess({guid:1,unread:true}); - expect( this.view.$('.notification_element#1')).toHaveClass("unread"); - }); - - - it("calls Notifications.decrementCount on a read cell at stream/notification element", function() { - notifications.clickSuccess(JSON.stringify({guid:1,unread:false})); - expect(notifications.decrementCount).toHaveBeenCalled(); - }); - it("calls Notifications.incrementCount on a unread cell at stream/notification element", function() { - notifications.clickSuccess({guid:1,unread:true}); - expect(notifications.incrementCount).toHaveBeenCalled(); - }); - }); - - describe("decrementCount", function() { - it("wont decrement Notifications.count below zero", function() { - var originalCount = notifications.count; - notifications.decrementCount(); - expect(originalCount).toEqual(0); - expect(notifications.count).toEqual(0); - }); - - it("decrements Notifications.count", function() { - notifications.incrementCount(); - notifications.incrementCount(); - var originalCount = notifications.count; - notifications.decrementCount(); - expect(notifications.count).toBeLessThan(originalCount); - }); - - it("calls Notifications.changeNotificationCount", function() { - notifications.decrementCount(); - expect(notifications.changeNotificationCount).toHaveBeenCalled(); - }); - }); - - describe("incrementCount", function() { - it("increments Notifications.count", function() { - var originalCount = notifications.count; - notifications.incrementCount(); - expect(notifications.count).toBeGreaterThan(originalCount); - }); - - it("calls Notifications.changeNotificationCount", function() { - notifications.incrementCount(); - expect(notifications.changeNotificationCount).toHaveBeenCalled(); - }); - }); - - describe("showNotification", function() { - it("prepends a div to div#notifications", function() { - expect(this.view.$(".notifications div").length).toEqual(1); - - notifications.showNotification({ - html: '
' - }); - - expect(this.view.$(".notifications div").length).toEqual(2); - }); - - it("only increments the notification count if specified to do so", function() { - var originalCount = notifications.count; - - notifications.showNotification({ - html: '
', - incrementCount: false - }); - - expect(notifications.count).toEqual(originalCount); - - }); - }); -});