Port notifications to Bootstrap
This commit is contained in:
parent
eabdc7390c
commit
4fc9c6416e
12 changed files with 351 additions and 97 deletions
|
|
@ -14,6 +14,7 @@
|
|||
* Update to jQuery 10
|
||||
* Port publisher and bookmarklet to Bootstrap [#4678](https://github.com/diaspora/diaspora/pull/4678)
|
||||
* Improve search page, add better indications [#4794](https://github.com/diaspora/diaspora/pull/4794)
|
||||
* Port notifications and hovercards to Bootstrap [#4814](https://github.com/diaspora/diaspora/pull/4814)
|
||||
|
||||
## Bug fixes
|
||||
* Improve time agos by updating the plugin [#4280](https://github.com/diaspora/diaspora/issues/4280)
|
||||
|
|
|
|||
86
app/assets/javascripts/app/views/notifications_view.js
Normal file
86
app/assets/javascripts/app/views/notifications_view.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
app.views.Notifications = Backbone.View.extend({
|
||||
|
||||
events: {
|
||||
"click .unread-toggle" : "toggleUnread"
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
Diaspora.page.header.notifications.setUpNotificationPage(this);
|
||||
$('.aspect_membership_dropdown').each(function(){
|
||||
new app.views.AspectMembership({el: this});
|
||||
});
|
||||
},
|
||||
|
||||
toggleUnread: function(evt) {
|
||||
note = $(evt.target).closest(".stream_element");
|
||||
unread = note.hasClass("unread");
|
||||
|
||||
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
|
||||
});
|
||||
},
|
||||
|
||||
setUnread: function(guid) {
|
||||
$.ajax({
|
||||
url: "/notifications/" + guid,
|
||||
data: { set_unread: true },
|
||||
type: "PUT",
|
||||
context: this,
|
||||
success: this.clickSuccess
|
||||
});
|
||||
},
|
||||
|
||||
clickSuccess: function(data) {
|
||||
type = $('.stream_element[data-guid=' + data["guid"] + ']').data('type');
|
||||
this.updateView(data["guid"], type, data["unread"]);
|
||||
},
|
||||
|
||||
updateView: function(guid, type, unread) {
|
||||
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 + ']');
|
||||
if(unread) {
|
||||
note.removeClass("read").addClass("unread");
|
||||
$(".unread-toggle", note).text(Diaspora.I18n.t('notifications.mark_read'));
|
||||
}
|
||||
else {
|
||||
note.removeClass("unread").addClass("read");
|
||||
$(".unread-toggle", note).text(Diaspora.I18n.t('notifications.mark_unread'));
|
||||
}
|
||||
|
||||
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');
|
||||
} else {
|
||||
all_notes.removeClass('badge-important');
|
||||
}
|
||||
if(type_notes.text()>0){
|
||||
type_notes.addClass('badge-important');
|
||||
} else {
|
||||
type_notes.removeClass('badge-important');
|
||||
}
|
||||
if(header_badge.text()>0){
|
||||
header_badge.removeClass('hidden');
|
||||
} else {
|
||||
header_badge.addClass('hidden');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -12,8 +12,8 @@
|
|||
$.extend(self, {
|
||||
badge: badge,
|
||||
count: parseInt(badge.html()) || 0,
|
||||
notificationArea: null,
|
||||
notificationMenu: notificationMenu
|
||||
notificationMenu: notificationMenu,
|
||||
notificationPage: null
|
||||
});
|
||||
|
||||
$("a.more").click( function(evt) {
|
||||
|
|
@ -31,11 +31,6 @@
|
|||
self.notificationMenu.find('.unread').each(function(index) {
|
||||
self.setUpRead( $(this) );
|
||||
});
|
||||
if ( self.notificationArea ) {
|
||||
self.notificationArea.find('.unread').each(function(index) {
|
||||
self.setUpRead( $(this) );
|
||||
});
|
||||
}
|
||||
self.resetCount();
|
||||
}
|
||||
});
|
||||
|
|
@ -43,15 +38,8 @@
|
|||
return false;
|
||||
});
|
||||
});
|
||||
this.setUpNotificationPage = function( contentArea ) {
|
||||
self.notificationArea = contentArea;
|
||||
contentArea.find(".unread,.read").each(function(index) {
|
||||
if ( $(this).hasClass("unread") ) {
|
||||
self.setUpUnread( $(this) );
|
||||
} else {
|
||||
self.setUpRead( $(this) );
|
||||
}
|
||||
});
|
||||
this.setUpNotificationPage = function( page ) {
|
||||
self.notificationPage = page;
|
||||
}
|
||||
this.unreadClick = function() {
|
||||
$.ajax({
|
||||
|
|
@ -106,16 +94,9 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
if ( self.notificationArea ) {
|
||||
self.notificationArea.find('.read,.unread').each(function(index) {
|
||||
if ( $(this).data("guid") == itemID ) {
|
||||
if ( isUnread ) {
|
||||
self.setUpUnread( $(this) )
|
||||
} else {
|
||||
self.setUpRead( $(this) )
|
||||
}
|
||||
}
|
||||
});
|
||||
if ( self.notificationPage != null ) {
|
||||
var type = $('.notification_element[data-guid=' + data["guid"] + ']').data('type');
|
||||
self.notificationPage.updateView(data["guid"], type, isUnread);
|
||||
}
|
||||
};
|
||||
this.showNotification = function(notification) {
|
||||
|
|
@ -134,18 +115,12 @@
|
|||
this.changeNotificationCount = function(change) {
|
||||
self.count = Math.max( self.count + change, 0 )
|
||||
self.badge.text(self.count);
|
||||
if ( self.notificationArea )
|
||||
self.notificationArea.find( ".notification_count" ).text(self.count);
|
||||
|
||||
if(self.count === 0) {
|
||||
self.badge.addClass("hidden");
|
||||
if ( self.notificationArea )
|
||||
self.notificationArea.find( ".notification_count" ).removeClass("unread");
|
||||
}
|
||||
else if(self.count === 1) {
|
||||
self.badge.removeClass("hidden");
|
||||
if ( self.notificationArea )
|
||||
self.notificationArea.find( ".notification_count" ).addClass("unread");
|
||||
}
|
||||
};
|
||||
this.resetCount = function(change) {
|
||||
|
|
|
|||
|
|
@ -776,25 +776,6 @@ ul#press_logos
|
|||
#aspects_list
|
||||
:height auto
|
||||
|
||||
.notifications_for_day
|
||||
.stream_element
|
||||
:padding 0.2em 0.5em
|
||||
:width 500px
|
||||
|
||||
.day_group
|
||||
:min-height 100px
|
||||
:margin
|
||||
:bottom 10px
|
||||
.stream_element
|
||||
&:last-child
|
||||
:border none
|
||||
|
||||
.stream.notifications
|
||||
> li:hover
|
||||
:background none
|
||||
:border
|
||||
:bottom 1px solid #eee
|
||||
|
||||
.show_comments
|
||||
:border
|
||||
:top 1px solid $border-grey
|
||||
|
|
|
|||
|
|
@ -38,3 +38,6 @@
|
|||
|
||||
/* bookmarklet */
|
||||
@import 'bookmarklet';
|
||||
|
||||
/* notifications */
|
||||
@import 'notifications';
|
||||
|
|
|
|||
83
app/assets/stylesheets/notifications.css.scss
Normal file
83
app/assets/stylesheets/notifications.css.scss
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
#notifications_container {
|
||||
padding-top: 50px;
|
||||
|
||||
.nav.nav-tabs{
|
||||
li > a {
|
||||
color: $text-dark-grey;
|
||||
.entypo {
|
||||
color: $text-dark-grey;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
li.active > a {
|
||||
background-color: $background-grey;
|
||||
color: $black;
|
||||
.entypo { color: $black; }
|
||||
}
|
||||
}
|
||||
|
||||
.stream {
|
||||
.header {
|
||||
border-bottom: 1px solid $border-grey;
|
||||
.btn-toolbar, h4 {
|
||||
margin-bottom: 10px;
|
||||
line-height: 40px;
|
||||
}
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.day_group {
|
||||
margin-bottom: 20px;
|
||||
.date {
|
||||
text-align: center;
|
||||
color: $light-grey;
|
||||
margin-bottom: 5px;
|
||||
.day {
|
||||
font-size: 40px;
|
||||
line-height: 40px;
|
||||
}
|
||||
.month {
|
||||
font-size: 16px;
|
||||
line-height: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media, .media-body {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.stream_element.media {
|
||||
padding: 10px;
|
||||
margin: 0px;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
border-bottom: 1px solid $border-grey;
|
||||
&:last-child { border: none !important; }
|
||||
|
||||
&.unread {
|
||||
background-color: $background-grey;
|
||||
.unread-toggle { opacity: 1 !important; }
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.unread-toggle { opacity: 1 !important; }
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.unread-toggle {
|
||||
opacity: 0;
|
||||
margin-top: 4px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.btn-group.aspect_membership_dropdown { margin: 5px 0; }
|
||||
}
|
||||
|
||||
.pagination { text-align: center; }
|
||||
}
|
||||
}
|
||||
13
app/views/notifications/_notification.html.haml
Normal file
13
app/views/notifications/_notification.html.haml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
.media.stream_element{:data=>{:guid => note.id, :type => (Notification.types.key(note.type) || '') }, :class => (note.unread ? 'unread' : 'read')}
|
||||
%button.btn.btn-link.btn-small.unread-toggle
|
||||
= note.unread ? t('notifications.index.mark_read') : t('notifications.index.mark_unread')
|
||||
- if note.type == "Notifications::StartedSharing" && contact = current_user.contact_for(note.effective_target)
|
||||
.pull-right
|
||||
= aspect_membership_dropdown(contact, note.effective_target, 'left')
|
||||
|
||||
.media-object.pull-left
|
||||
= person_image_link note.actors.first, :size => :thumb_small, :class => 'hovercardable'
|
||||
.media-body
|
||||
= notification_message_for(note)
|
||||
%div
|
||||
= timeago(note.created_at)
|
||||
|
|
@ -1,38 +1,56 @@
|
|||
#notifications_content
|
||||
.span-13
|
||||
%h2
|
||||
%span.notification_count{:class => ('unread' if @unread_notification_count >0 )}
|
||||
= @unread_notification_count
|
||||
= t('.notifications')
|
||||
.span-8.last
|
||||
= link_to t('.mark_all_as_read'), notifications_read_all_path, :class => "button #{'disabled' unless @unread_notification_count > 0}"
|
||||
.span-24.last
|
||||
.stream.notifications
|
||||
.container-fluid#notifications_container
|
||||
.row-fluid
|
||||
.span3
|
||||
%h3
|
||||
= t('.notifications')
|
||||
%ul.nav.nav-tabs.nav-stacked
|
||||
%li{ :class => ('active' unless params[:type] && @grouped_unread_notification_counts.has_key?(params[:type])) }
|
||||
%a{ :href => '/notifications' + (params[:show] == 'unread' ? '?show=unread' : '') }
|
||||
%span.pull-right.badge{:class => ('badge-important' if @unread_notification_count > 0)}
|
||||
= @unread_notification_count
|
||||
= t('.all_notifications')
|
||||
- @grouped_unread_notification_counts.each do |key, count|
|
||||
%li{ :class => ('active' if params[:type] == key), :data => { :type => key } }
|
||||
%a{ :href => '/notifications?type=' + key + (params[:show] == 'unread' ? '&show=unread' : '') }
|
||||
%span.pull-right.badge{ :class => ('badge-important' if count > 0) }
|
||||
= count
|
||||
- case key
|
||||
- when 'also_commented', 'comment_on_post'
|
||||
%i.entypo.comment
|
||||
- when 'liked'
|
||||
%i.entypo.heart
|
||||
- when 'mentioned'
|
||||
%i.entypo.pencil
|
||||
- when 'reshared'
|
||||
%i.entypo.retweet
|
||||
- when 'started_sharing'
|
||||
%i.entypo.users
|
||||
= t('.'+key)
|
||||
|
||||
.span9.stream.notifications
|
||||
.row-fluid.header
|
||||
.span12
|
||||
.btn-toolbar.pull-right
|
||||
.btn-group
|
||||
%a.btn.btn-default{ :class => ('active' unless params[:show] == 'unread'), :href => '/notifications' + (params[:type] ? '?type=' + params[:type] : '') }
|
||||
= t('.show_all')
|
||||
%a.btn.btn-default{ :class => ('active' if params[:show] == 'unread'), :href => '/notifications?show=unread' + (params[:type] ? '&type=' + params[:type] : '') }
|
||||
= t('.show_unread')
|
||||
%a.btn.btn-default{:href => notifications_read_all_path, :class => ('disabled' unless @unread_notification_count > 0)}
|
||||
= t('.mark_all_as_read')
|
||||
- @group_days.each do |day, notes|
|
||||
.day_group.span-24.last
|
||||
.span-3
|
||||
.date
|
||||
.day= the_day(day.split(' '))
|
||||
.month= the_month(day.split(' '))
|
||||
.day_group.row-fluid
|
||||
.date.span2
|
||||
.day= the_day(day.split(' '))
|
||||
.month= the_month(day.split(' '))
|
||||
|
||||
.span-8.notifications_for_day
|
||||
.notifications_for_day.span10
|
||||
- notes.each do |note|
|
||||
.stream_element{:data=>{:guid => note.id}, :class => "#{note.unread ? 'unread' : 'read'}"}
|
||||
- if note.type == "Notifications::StartedSharing" && contact = current_user.contact_for(note.effective_target)
|
||||
.float-right
|
||||
= aspect_membership_dropdown(contact, note.effective_target, 'left')
|
||||
= render :partial => 'notifications/notification', :locals => { :note => note }
|
||||
|
||||
.media
|
||||
.bd
|
||||
= person_image_tag note.actors.first, :thumb_medium
|
||||
= notification_message_for(note)
|
||||
%div
|
||||
= timeago(note.created_at)
|
||||
= link_to t('.mark_unread'), "#", :class => "unread-setter"
|
||||
|
||||
= will_paginate @notifications
|
||||
= will_paginate @notifications, :renderer => WillPaginate::ActionView::BootstrapLinkRenderer
|
||||
|
||||
:javascript
|
||||
$(document).ready(function(){
|
||||
Diaspora.page.header.notifications.setUpNotificationPage( $("#notifications_content" ) );
|
||||
new app.views.Notifications({ el: '#notifications_container' });
|
||||
});
|
||||
|
|
|
|||
|
|
@ -637,25 +637,25 @@ en:
|
|||
one: "%{actors} sent you a message."
|
||||
other: "%{actors} sent you a message."
|
||||
comment_on_post:
|
||||
zero: "%{actors} commented on your post »%{post_link}«."
|
||||
one: "%{actors} commented on your post »%{post_link}«."
|
||||
other: "%{actors} commented on your post »%{post_link}«."
|
||||
zero: "%{actors} commented on your post %{post_link}."
|
||||
one: "%{actors} commented on your post %{post_link}."
|
||||
other: "%{actors} commented on your post %{post_link}."
|
||||
also_commented:
|
||||
zero: "%{actors} also commented on %{post_author}'s post »%{post_link}«."
|
||||
one: "%{actors} also commented on %{post_author}'s post »%{post_link}«."
|
||||
other: "%{actors} also commented on %{post_author}'s post »%{post_link}«."
|
||||
zero: "%{actors} also commented on %{post_author}'s post %{post_link}."
|
||||
one: "%{actors} also commented on %{post_author}'s post %{post_link}."
|
||||
other: "%{actors} also commented on %{post_author}'s post %{post_link}."
|
||||
mentioned:
|
||||
zero: "%{actors} have mentioned you in the post »%{post_link}«."
|
||||
one: "%{actors} has mentioned you in the post »%{post_link}«."
|
||||
other: "%{actors} have mentioned you in the »%{post_link}«."
|
||||
zero: "%{actors} have mentioned you in the post %{post_link}."
|
||||
one: "%{actors} has mentioned you in the post %{post_link}."
|
||||
other: "%{actors} have mentioned you in the %{post_link}."
|
||||
liked:
|
||||
zero: "%{actors} have liked your post »%{post_link}«."
|
||||
one: "%{actors} has liked your post »%{post_link}«."
|
||||
other: "%{actors} have liked your post »%{post_link}«."
|
||||
zero: "%{actors} have liked your post %{post_link}."
|
||||
one: "%{actors} has liked your post %{post_link}."
|
||||
other: "%{actors} have liked your post %{post_link}."
|
||||
reshared:
|
||||
zero: "%{actors} have reshared your post »%{post_link}«."
|
||||
one: "%{actors} has reshared your post »%{post_link}«."
|
||||
other: "%{actors} have reshared your post »%{post_link}«."
|
||||
zero: "%{actors} have reshared your post %{post_link}."
|
||||
one: "%{actors} has reshared your post %{post_link}."
|
||||
other: "%{actors} have reshared your post %{post_link}."
|
||||
post: "post"
|
||||
also_commented_deleted:
|
||||
zero: "%{actors} commented on a deleted post."
|
||||
|
|
|
|||
|
|
@ -73,5 +73,16 @@ Feature: Notifications
|
|||
When I sign in as "bob@bob.bob"
|
||||
And I follow "Notifications" in the header
|
||||
Then the notification dropdown should be visible
|
||||
Then I should see "mentioned you in a post"
|
||||
Then I should see "mentioned you in the post"
|
||||
And I should have 1 email delivery
|
||||
|
||||
Scenario: filter notifications
|
||||
Given a user with email "bob@bob.bob" is connected with "alice@alice.alice"
|
||||
And Alice has a post mentioning Bob
|
||||
When I sign in as "bob@bob.bob"
|
||||
And I am on the notifications page
|
||||
Then I should see "mentioned you in the post"
|
||||
When I filter notifications by likes
|
||||
Then I should not see "mentioned you in the post"
|
||||
When I filter notifications by mentions
|
||||
Then I should see "mentioned you in the post"
|
||||
|
|
|
|||
7
features/step_definitions/notifications_steps.rb
Normal file
7
features/step_definitions/notifications_steps.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
When /^I filter notifications by likes$/ do
|
||||
step %(I follow "Liked" within "#notifications_container ul.nav.nav-tabs")
|
||||
end
|
||||
|
||||
When /^I filter notifications by mentions$/ do
|
||||
step %(I follow "Mentioned" within "#notifications_container ul.nav.nav-tabs")
|
||||
end
|
||||
76
spec/javascripts/app/views/notifications_view_spec.js
Normal file
76
spec/javascripts/app/views/notifications_view_spec.js
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
describe("app.views.Notifications", function(){
|
||||
beforeEach(function() {
|
||||
spec.loadFixture("notifications");
|
||||
this.view = new app.views.Notifications({el: '#notifications_container'});
|
||||
});
|
||||
|
||||
context('mark read', function() {
|
||||
beforeEach(function() {
|
||||
this.unreadN = $('.stream_element.unread').first();
|
||||
this.guid = this.unreadN.data("guid");
|
||||
});
|
||||
|
||||
it('calls "setRead"', function() {
|
||||
spyOn(this.view, "setRead");
|
||||
this.unreadN.find('.unread-toggle').trigger('click');
|
||||
|
||||
expect(this.view.setRead).toHaveBeenCalledWith(this.guid);
|
||||
});
|
||||
});
|
||||
|
||||
context('mark unread', function() {
|
||||
beforeEach(function() {
|
||||
this.readN = $('.stream_element.read').first();
|
||||
this.guid = this.readN.data("guid");
|
||||
});
|
||||
|
||||
it('calls "setUnread"', function() {
|
||||
spyOn(this.view, "setUnread");
|
||||
this.readN.find('.unread-toggle').trigger('click');
|
||||
|
||||
expect(this.view.setUnread).toHaveBeenCalledWith(this.guid);
|
||||
});
|
||||
});
|
||||
|
||||
context('updateView', function() {
|
||||
beforeEach(function() {
|
||||
this.readN = $('.stream_element.read').first();
|
||||
this.guid = this.readN.data('guid');
|
||||
this.type = this.readN.data('type');
|
||||
});
|
||||
|
||||
it('changes the "all notifications" count', function() {
|
||||
badge = $('ul.nav > li:eq(0) .badge');
|
||||
count = parseInt(badge.text());
|
||||
|
||||
this.view.updateView(this.guid, this.type, true);
|
||||
expect(parseInt(badge.text())).toBe(count + 1);
|
||||
|
||||
this.view.updateView(this.guid, this.type, false);
|
||||
expect(parseInt(badge.text())).toBe(count);
|
||||
});
|
||||
|
||||
it('changes the notification type count', function() {
|
||||
badge = $('ul.nav > li[data-type=' + this.type + '] .badge');
|
||||
count = parseInt(badge.text());
|
||||
|
||||
this.view.updateView(this.guid, this.type, true);
|
||||
expect(parseInt(badge.text())).toBe(count + 1);
|
||||
|
||||
this.view.updateView(this.guid, this.type, false);
|
||||
expect(parseInt(badge.text())).toBe(count);
|
||||
});
|
||||
|
||||
it('toggles the unread class and changes the link text', function() {
|
||||
this.view.updateView(this.readN.data('guid'), this.readN.data('type'), true);
|
||||
expect(this.readN.hasClass('unread')).toBeTruethy;
|
||||
expect(this.readN.hasClass('read')).toBeFalsy;
|
||||
expect(this.readN.find('.unread-toggle').text()).toContain(Diaspora.I18n.t('notifications.mark_read'));
|
||||
|
||||
this.view.updateView(this.readN.data('guid'), this.readN.data('type'), false);
|
||||
expect(this.readN.hasClass('read')).toBeTruethy;
|
||||
expect(this.readN.hasClass('unread')).toBeFalsy;
|
||||
expect(this.readN.find('.unread-toggle').text()).toContain(Diaspora.I18n.t('notifications.mark_unread'));
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue