Merge pull request #4814 from svbergerem/notifications-bootstrap-port
[WIP] Port notifications to Bootstrap and add filters, port hovercards
This commit is contained in:
commit
23a91eb63f
41 changed files with 854 additions and 231 deletions
|
|
@ -14,6 +14,7 @@
|
||||||
* Update to jQuery 10
|
* Update to jQuery 10
|
||||||
* Port publisher and bookmarklet to Bootstrap [#4678](https://github.com/diaspora/diaspora/pull/4678)
|
* 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)
|
* 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
|
## Bug fixes
|
||||||
* Improve time agos by updating the plugin [#4280](https://github.com/diaspora/diaspora/issues/4280)
|
* Improve time agos by updating the plugin [#4280](https://github.com/diaspora/diaspora/issues/4280)
|
||||||
|
|
@ -32,6 +33,7 @@
|
||||||
* Add permalinks for comments [#4577](https://github.com/diaspora/diaspora/pull/4577)
|
* Add permalinks for comments [#4577](https://github.com/diaspora/diaspora/pull/4577)
|
||||||
* New menu for the mobile version [#4673](https://github.com/diaspora/diaspora/pull/4673)
|
* New menu for the mobile version [#4673](https://github.com/diaspora/diaspora/pull/4673)
|
||||||
* Added comment count to statistic to enable calculations of posts/comments ratios [#4799](https://github.com/diaspora/diaspora/pull/4799)
|
* Added comment count to statistic to enable calculations of posts/comments ratios [#4799](https://github.com/diaspora/diaspora/pull/4799)
|
||||||
|
* Add filters to notifications controller [#4814](https://github.com/diaspora/diaspora/pull/4814)
|
||||||
|
|
||||||
# 0.3.0.3
|
# 0.3.0.3
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ var app = {
|
||||||
|
|
||||||
setupGlobalViews: function() {
|
setupGlobalViews: function() {
|
||||||
app.hovercard = new app.views.Hovercard();
|
app.hovercard = new app.views.Hovercard();
|
||||||
app.aspectMemberships = new app.views.AspectMembership();
|
app.aspectMembershipsBlueprint = new app.views.AspectMembershipBlueprint();
|
||||||
app.sidebar = new app.views.Sidebar();
|
app.sidebar = new app.views.Sidebar();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,161 @@
|
||||||
|
/**
|
||||||
|
* this view lets the user (de-)select aspect memberships in the context
|
||||||
|
* of another users profile or the contact page.
|
||||||
|
*
|
||||||
|
* updates to the list of aspects are immediately propagated to the server, and
|
||||||
|
* the results are dislpayed as flash messages.
|
||||||
|
*/
|
||||||
|
app.views.AspectMembershipBlueprint = Backbone.View.extend({
|
||||||
|
|
||||||
|
initialize: function() {
|
||||||
|
// attach event handler, removing any previous instances
|
||||||
|
var selector = '.dropdown.aspect_membership .dropdown_list > li';
|
||||||
|
$('body')
|
||||||
|
.off('click', selector)
|
||||||
|
.on('click', selector, _.bind(this._clickHandler, this));
|
||||||
|
|
||||||
|
this.list_item = null;
|
||||||
|
this.dropdown = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
// decide what to do when clicked
|
||||||
|
// -> addMembership
|
||||||
|
// -> removeMembership
|
||||||
|
_clickHandler: function(evt) {
|
||||||
|
this.list_item = $(evt.target);
|
||||||
|
this.dropdown = this.list_item.parent();
|
||||||
|
|
||||||
|
this.list_item.addClass('loading');
|
||||||
|
|
||||||
|
if( this.list_item.is('.selected') ) {
|
||||||
|
var membership_id = this.list_item.data('membership_id');
|
||||||
|
this.removeMembership(membership_id);
|
||||||
|
} else {
|
||||||
|
var aspect_id = this.list_item.data('aspect_id');
|
||||||
|
var person_id = this.dropdown.data('person_id');
|
||||||
|
this.addMembership(person_id, aspect_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // stop the event
|
||||||
|
},
|
||||||
|
|
||||||
|
// return the (short) name of the person associated with the current dropdown
|
||||||
|
_name: function() {
|
||||||
|
return this.dropdown.data('person-short-name');
|
||||||
|
},
|
||||||
|
|
||||||
|
// create a membership for the given person in the given aspect
|
||||||
|
addMembership: function(person_id, aspect_id) {
|
||||||
|
var aspect_membership = new app.models.AspectMembership({
|
||||||
|
'person_id': person_id,
|
||||||
|
'aspect_id': aspect_id
|
||||||
|
});
|
||||||
|
|
||||||
|
aspect_membership.on('sync', this._successSaveCb, this);
|
||||||
|
aspect_membership.on('error', function() {
|
||||||
|
this._displayError('aspect_dropdown.error');
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
aspect_membership.save();
|
||||||
|
},
|
||||||
|
|
||||||
|
_successSaveCb: function(aspect_membership) {
|
||||||
|
var aspect_id = aspect_membership.get('aspect_id');
|
||||||
|
var membership_id = aspect_membership.get('id');
|
||||||
|
var li = this.dropdown.find('li[data-aspect_id="'+aspect_id+'"]');
|
||||||
|
|
||||||
|
// the user didn't have this person in any aspects before, congratulate them
|
||||||
|
// on their newly found friendship ;)
|
||||||
|
if( this.dropdown.find('li.selected').length == 0 ) {
|
||||||
|
var msg = Diaspora.I18n.t('aspect_dropdown.started_sharing_with', { 'name': this._name() });
|
||||||
|
Diaspora.page.flashMessages.render({ 'success':true, 'notice':msg });
|
||||||
|
}
|
||||||
|
|
||||||
|
li.attr('data-membership_id', membership_id) // just to be sure...
|
||||||
|
.data('membership_id', membership_id)
|
||||||
|
.addClass('selected');
|
||||||
|
|
||||||
|
this.updateSummary();
|
||||||
|
this._done();
|
||||||
|
},
|
||||||
|
|
||||||
|
// show an error flash msg
|
||||||
|
_displayError: function(msg_id) {
|
||||||
|
this._done();
|
||||||
|
this.dropdown.removeClass('active'); // close the dropdown
|
||||||
|
|
||||||
|
var msg = Diaspora.I18n.t(msg_id, { 'name': this._name() });
|
||||||
|
Diaspora.page.flashMessages.render({ 'success':false, 'notice':msg });
|
||||||
|
},
|
||||||
|
|
||||||
|
// remove the membership with the given id
|
||||||
|
removeMembership: function(membership_id) {
|
||||||
|
var aspect_membership = new app.models.AspectMembership({
|
||||||
|
'id': membership_id
|
||||||
|
});
|
||||||
|
|
||||||
|
aspect_membership.on('sync', this._successDestroyCb, this);
|
||||||
|
aspect_membership.on('error', function() {
|
||||||
|
this._displayError('aspect_dropdown.error_remove');
|
||||||
|
}, this);
|
||||||
|
|
||||||
|
aspect_membership.destroy();
|
||||||
|
},
|
||||||
|
|
||||||
|
_successDestroyCb: function(aspect_membership) {
|
||||||
|
var membership_id = aspect_membership.get('id');
|
||||||
|
var li = this.dropdown.find('li[data-membership_id="'+membership_id+'"]');
|
||||||
|
|
||||||
|
li.removeAttr('data-membership_id')
|
||||||
|
.removeData('membership_id')
|
||||||
|
.removeClass('selected');
|
||||||
|
|
||||||
|
// we just removed the last aspect, inform the user with a flash message
|
||||||
|
// that he is no longer sharing with that person
|
||||||
|
if( this.dropdown.find('li.selected').length == 0 ) {
|
||||||
|
var msg = Diaspora.I18n.t('aspect_dropdown.stopped_sharing_with', { 'name': this._name() });
|
||||||
|
Diaspora.page.flashMessages.render({ 'success':true, 'notice':msg });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateSummary();
|
||||||
|
this._done();
|
||||||
|
},
|
||||||
|
|
||||||
|
// cleanup tasks after aspect selection
|
||||||
|
_done: function() {
|
||||||
|
if( this.list_item ) {
|
||||||
|
this.list_item.removeClass('loading');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// refresh the button text to reflect the current aspect selection status
|
||||||
|
updateSummary: function() {
|
||||||
|
var btn = this.dropdown.parents('div.aspect_membership').find('.button.toggle');
|
||||||
|
var aspects_cnt = this.dropdown.find('li.selected').length;
|
||||||
|
var txt;
|
||||||
|
|
||||||
|
if( aspects_cnt == 0 ) {
|
||||||
|
btn.removeClass('in_aspects');
|
||||||
|
txt = Diaspora.I18n.t('aspect_dropdown.toggle.zero');
|
||||||
|
} else {
|
||||||
|
btn.addClass('in_aspects');
|
||||||
|
txt = this._pluralSummaryTxt(aspects_cnt);
|
||||||
|
}
|
||||||
|
|
||||||
|
btn.text(txt + ' ▼');
|
||||||
|
},
|
||||||
|
|
||||||
|
_pluralSummaryTxt: function(cnt) {
|
||||||
|
var all_aspects_cnt = this.dropdown.find('li').length;
|
||||||
|
|
||||||
|
if( cnt == 1 ) {
|
||||||
|
return this.dropdown.find('li.selected').first().text();
|
||||||
|
}
|
||||||
|
|
||||||
|
if( cnt == all_aspects_cnt ) {
|
||||||
|
return Diaspora.I18n.t('aspect_dropdown.all_aspects');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Diaspora.I18n.t('aspect_dropdown.toggle', { 'count':cnt.toString() });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
//= require ./aspects_dropdown_view
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this view lets the user (de-)select aspect memberships in the context
|
* this view lets the user (de-)select aspect memberships in the context
|
||||||
* of another users profile or the contact page.
|
* of another users profile or the contact page.
|
||||||
|
|
@ -5,15 +7,13 @@
|
||||||
* updates to the list of aspects are immediately propagated to the server, and
|
* updates to the list of aspects are immediately propagated to the server, and
|
||||||
* the results are dislpayed as flash messages.
|
* the results are dislpayed as flash messages.
|
||||||
*/
|
*/
|
||||||
app.views.AspectMembership = Backbone.View.extend({
|
app.views.AspectMembership = app.views.AspectsDropdown.extend({
|
||||||
|
|
||||||
|
events: {
|
||||||
|
"click ul.aspect_membership.dropdown-menu > li.aspect_selector": "_clickHandler"
|
||||||
|
},
|
||||||
|
|
||||||
initialize: function() {
|
initialize: function() {
|
||||||
// attach event handler, removing any previous instances
|
|
||||||
var selector = '.dropdown.aspect_membership .dropdown_list > li';
|
|
||||||
$('body')
|
|
||||||
.off('click', selector)
|
|
||||||
.on('click', selector, _.bind(this._clickHandler, this));
|
|
||||||
|
|
||||||
this.list_item = null;
|
this.list_item = null;
|
||||||
this.dropdown = null;
|
this.dropdown = null;
|
||||||
},
|
},
|
||||||
|
|
@ -22,7 +22,7 @@ app.views.AspectMembership = Backbone.View.extend({
|
||||||
// -> addMembership
|
// -> addMembership
|
||||||
// -> removeMembership
|
// -> removeMembership
|
||||||
_clickHandler: function(evt) {
|
_clickHandler: function(evt) {
|
||||||
this.list_item = $(evt.target);
|
this.list_item = $(evt.target).closest('li.aspect_selector');
|
||||||
this.dropdown = this.list_item.parent();
|
this.dropdown = this.list_item.parent();
|
||||||
|
|
||||||
this.list_item.addClass('loading');
|
this.list_item.addClass('loading');
|
||||||
|
|
@ -72,17 +72,16 @@ app.views.AspectMembership = Backbone.View.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
li.attr('data-membership_id', membership_id) // just to be sure...
|
li.attr('data-membership_id', membership_id) // just to be sure...
|
||||||
.data('membership_id', membership_id)
|
.data('membership_id', membership_id);
|
||||||
.addClass('selected');
|
|
||||||
|
|
||||||
this.updateSummary();
|
this.updateSummary(li);
|
||||||
this._done();
|
this._done();
|
||||||
},
|
},
|
||||||
|
|
||||||
// show an error flash msg
|
// show an error flash msg
|
||||||
_displayError: function(msg_id) {
|
_displayError: function(msg_id) {
|
||||||
this._done();
|
this._done();
|
||||||
this.dropdown.removeClass('active'); // close the dropdown
|
this.dropdown.closest('.aspect_membership_dropdown').removeClass('open'); // close the dropdown
|
||||||
|
|
||||||
var msg = Diaspora.I18n.t(msg_id, { 'name': this._name() });
|
var msg = Diaspora.I18n.t(msg_id, { 'name': this._name() });
|
||||||
Diaspora.page.flashMessages.render({ 'success':false, 'notice':msg });
|
Diaspora.page.flashMessages.render({ 'success':false, 'notice':msg });
|
||||||
|
|
@ -107,8 +106,8 @@ app.views.AspectMembership = Backbone.View.extend({
|
||||||
var li = this.dropdown.find('li[data-membership_id="'+membership_id+'"]');
|
var li = this.dropdown.find('li[data-membership_id="'+membership_id+'"]');
|
||||||
|
|
||||||
li.removeAttr('data-membership_id')
|
li.removeAttr('data-membership_id')
|
||||||
.removeData('membership_id')
|
.removeData('membership_id');
|
||||||
.removeClass('selected');
|
this.updateSummary(li);
|
||||||
|
|
||||||
// we just removed the last aspect, inform the user with a flash message
|
// we just removed the last aspect, inform the user with a flash message
|
||||||
// that he is no longer sharing with that person
|
// that he is no longer sharing with that person
|
||||||
|
|
@ -117,7 +116,6 @@ app.views.AspectMembership = Backbone.View.extend({
|
||||||
Diaspora.page.flashMessages.render({ 'success':true, 'notice':msg });
|
Diaspora.page.flashMessages.render({ 'success':true, 'notice':msg });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateSummary();
|
|
||||||
this._done();
|
this._done();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -129,33 +127,8 @@ app.views.AspectMembership = Backbone.View.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
// refresh the button text to reflect the current aspect selection status
|
// refresh the button text to reflect the current aspect selection status
|
||||||
updateSummary: function() {
|
updateSummary: function(target) {
|
||||||
var btn = this.dropdown.parents('div.aspect_membership').find('.button.toggle');
|
this._toggleCheckbox(target);
|
||||||
var aspects_cnt = this.dropdown.find('li.selected').length;
|
this._updateButton('green');
|
||||||
var txt;
|
|
||||||
|
|
||||||
if( aspects_cnt == 0 ) {
|
|
||||||
btn.removeClass('in_aspects');
|
|
||||||
txt = Diaspora.I18n.t('aspect_dropdown.toggle.zero');
|
|
||||||
} else {
|
|
||||||
btn.addClass('in_aspects');
|
|
||||||
txt = this._pluralSummaryTxt(aspects_cnt);
|
|
||||||
}
|
|
||||||
|
|
||||||
btn.text(txt + ' ▼');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_pluralSummaryTxt: function(cnt) {
|
|
||||||
var all_aspects_cnt = this.dropdown.find('li').length;
|
|
||||||
|
|
||||||
if( cnt == 1 ) {
|
|
||||||
return this.dropdown.find('li.selected').first().text();
|
|
||||||
}
|
|
||||||
|
|
||||||
if( cnt == all_aspects_cnt ) {
|
|
||||||
return Diaspora.I18n.t('aspect_dropdown.all_aspects');
|
|
||||||
}
|
|
||||||
|
|
||||||
return Diaspora.I18n.t('aspect_dropdown.toggle', { 'count':cnt.toString() });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -103,10 +103,14 @@ app.views.Hovercard = Backbone.View.extend({
|
||||||
|
|
||||||
// set aspect dropdown
|
// set aspect dropdown
|
||||||
var href = this.href();
|
var href = this.href();
|
||||||
href += "/aspect_membership_button"
|
href += "/aspect_membership_button";
|
||||||
|
if(gon.bootstrap == true){
|
||||||
|
href += "?bootstrap=true";
|
||||||
|
}
|
||||||
$.get(href, function(response) {
|
$.get(href, function(response) {
|
||||||
self.dropdown_container.html(response);
|
self.dropdown_container.html(response);
|
||||||
});
|
});
|
||||||
|
var aspect_membership = new app.views.AspectMembership({el: self.dropdown_container});
|
||||||
},
|
},
|
||||||
|
|
||||||
_positionHovercard: function() {
|
_positionHovercard: function() {
|
||||||
|
|
|
||||||
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// require ../aspects_dropdown_view
|
//= require ../aspects_dropdown_view
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Aspects view for the publisher.
|
* Aspects view for the publisher.
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@
|
||||||
$.extend(self, {
|
$.extend(self, {
|
||||||
badge: badge,
|
badge: badge,
|
||||||
count: parseInt(badge.html()) || 0,
|
count: parseInt(badge.html()) || 0,
|
||||||
notificationArea: null,
|
notificationMenu: notificationMenu,
|
||||||
notificationMenu: notificationMenu
|
notificationPage: null
|
||||||
});
|
});
|
||||||
|
|
||||||
$("a.more").click( function(evt) {
|
$("a.more").click( function(evt) {
|
||||||
|
|
@ -31,11 +31,6 @@
|
||||||
self.notificationMenu.find('.unread').each(function(index) {
|
self.notificationMenu.find('.unread').each(function(index) {
|
||||||
self.setUpRead( $(this) );
|
self.setUpRead( $(this) );
|
||||||
});
|
});
|
||||||
if ( self.notificationArea ) {
|
|
||||||
self.notificationArea.find('.unread').each(function(index) {
|
|
||||||
self.setUpRead( $(this) );
|
|
||||||
});
|
|
||||||
}
|
|
||||||
self.resetCount();
|
self.resetCount();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -43,15 +38,8 @@
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.setUpNotificationPage = function( contentArea ) {
|
this.setUpNotificationPage = function( page ) {
|
||||||
self.notificationArea = contentArea;
|
self.notificationPage = page;
|
||||||
contentArea.find(".unread,.read").each(function(index) {
|
|
||||||
if ( $(this).hasClass("unread") ) {
|
|
||||||
self.setUpUnread( $(this) );
|
|
||||||
} else {
|
|
||||||
self.setUpRead( $(this) );
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
this.unreadClick = function() {
|
this.unreadClick = function() {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
|
|
@ -106,16 +94,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if ( self.notificationArea ) {
|
if ( self.notificationPage != null ) {
|
||||||
self.notificationArea.find('.read,.unread').each(function(index) {
|
var type = $('.notification_element[data-guid=' + data["guid"] + ']').data('type');
|
||||||
if ( $(this).data("guid") == itemID ) {
|
self.notificationPage.updateView(data["guid"], type, isUnread);
|
||||||
if ( isUnread ) {
|
|
||||||
self.setUpUnread( $(this) )
|
|
||||||
} else {
|
|
||||||
self.setUpRead( $(this) )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.showNotification = function(notification) {
|
this.showNotification = function(notification) {
|
||||||
|
|
@ -134,18 +115,12 @@
|
||||||
this.changeNotificationCount = function(change) {
|
this.changeNotificationCount = function(change) {
|
||||||
self.count = Math.max( self.count + change, 0 )
|
self.count = Math.max( self.count + change, 0 )
|
||||||
self.badge.text(self.count);
|
self.badge.text(self.count);
|
||||||
if ( self.notificationArea )
|
|
||||||
self.notificationArea.find( ".notification_count" ).text(self.count);
|
|
||||||
|
|
||||||
if(self.count === 0) {
|
if(self.count === 0) {
|
||||||
self.badge.addClass("hidden");
|
self.badge.addClass("hidden");
|
||||||
if ( self.notificationArea )
|
|
||||||
self.notificationArea.find( ".notification_count" ).removeClass("unread");
|
|
||||||
}
|
}
|
||||||
else if(self.count === 1) {
|
else if(self.count === 1) {
|
||||||
self.badge.removeClass("hidden");
|
self.badge.removeClass("hidden");
|
||||||
if ( self.notificationArea )
|
|
||||||
self.notificationArea.find( ".notification_count" ).addClass("unread");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.resetCount = function(change) {
|
this.resetCount = function(change) {
|
||||||
|
|
|
||||||
|
|
@ -776,25 +776,6 @@ ul#press_logos
|
||||||
#aspects_list
|
#aspects_list
|
||||||
:height auto
|
: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
|
.show_comments
|
||||||
:border
|
:border
|
||||||
:top 1px solid $border-grey
|
:top 1px solid $border-grey
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,10 @@
|
||||||
.icon-refresh { display: inline-block;}
|
.icon-refresh { display: inline-block;}
|
||||||
.icon-ok { display: none;}
|
.icon-ok { display: none;}
|
||||||
}
|
}
|
||||||
a { cursor: pointer; }
|
a {
|
||||||
|
cursor: pointer;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,25 @@
|
||||||
border: 1px solid darken($button-border-color,20%);
|
border: 1px solid darken($button-border-color,20%);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@include button-gradient-hover($creation-blue);
|
|
||||||
background: $creation-blue;
|
background: $creation-blue;
|
||||||
border: 1px solid darken($button-border-color,35%);
|
border: 1px solid darken($button-border-color,35%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.btn-group.open > .btn.creation {
|
||||||
|
background: $creation-blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.green {
|
||||||
|
$button-border-color: #aaa;
|
||||||
|
@include button-gradient($green);
|
||||||
|
color: $grey;
|
||||||
|
border: 1px solid darken($button-border-color,20%);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $green;
|
||||||
|
border: 1px solid darken($button-border-color,35%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.btn-group.open > .btn.green {
|
||||||
|
background: $green;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,22 +49,25 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
|
margin-top: 0px;
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
padding-bottom: 0px;
|
padding-bottom: 0px;
|
||||||
}
|
font-size: 16px;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: $blue;
|
color: $blue;
|
||||||
font-weight: bold !important;
|
font-weight: bold !important;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
color: $text-grey;
|
color: $text-grey;
|
||||||
padding-top: 0px;
|
padding-top: 0px;
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-group.aspect_membership_dropdown { margin: 0 !important; }
|
||||||
|
|
||||||
.hovercard_footer {
|
.hovercard_footer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
|
|
||||||
|
|
@ -38,3 +38,6 @@
|
||||||
|
|
||||||
/* bookmarklet */
|
/* bookmarklet */
|
||||||
@import '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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -151,4 +151,10 @@ class ApplicationController < ActionController::Base
|
||||||
gon.preloads = {}
|
gon.preloads = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.use_bootstrap_for *routes
|
||||||
|
before_filter -> {
|
||||||
|
@css_framework = :bootstrap
|
||||||
|
gon.bootstrap = true
|
||||||
|
}, only: routes.flatten
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ class ConversationsController < ApplicationController
|
||||||
before_filter :authenticate_user!
|
before_filter :authenticate_user!
|
||||||
|
|
||||||
layout ->(c) { request.format == :mobile ? "application" : "with_header" }
|
layout ->(c) { request.format == :mobile ? "application" : "with_header" }
|
||||||
before_filter -> { @css_framework = :bootstrap }
|
use_bootstrap_for :index, :show, :new
|
||||||
|
|
||||||
respond_to :html, :mobile, :json, :js
|
respond_to :html, :mobile, :json, :js
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@
|
||||||
class NotificationsController < ApplicationController
|
class NotificationsController < ApplicationController
|
||||||
before_filter :authenticate_user!
|
before_filter :authenticate_user!
|
||||||
|
|
||||||
|
layout ->(c) { request.format == :mobile ? "application" : "with_header_with_footer" }
|
||||||
|
use_bootstrap_for :index
|
||||||
|
|
||||||
def update
|
def update
|
||||||
note = Notification.where(:recipient_id => current_user.id, :id => params[:id]).first
|
note = Notification.where(:recipient_id => current_user.id, :id => params[:id]).first
|
||||||
if note
|
if note
|
||||||
|
|
@ -23,6 +26,10 @@ class NotificationsController < ApplicationController
|
||||||
|
|
||||||
def index
|
def index
|
||||||
conditions = {:recipient_id => current_user.id}
|
conditions = {:recipient_id => current_user.id}
|
||||||
|
if params[:type] && Notification.types.has_key?(params[:type])
|
||||||
|
conditions[:type] = Notification.types[params[:type]]
|
||||||
|
end
|
||||||
|
if params[:show] == "unread" then conditions[:unread] = true end
|
||||||
page = params[:page] || 1
|
page = params[:page] || 1
|
||||||
per_page = params[:per_page] || 25
|
per_page = params[:per_page] || 25
|
||||||
@notifications = WillPaginate::Collection.create(page, per_page, Notification.where(conditions).count ) do |pager|
|
@notifications = WillPaginate::Collection.create(page, per_page, Notification.where(conditions).count ) do |pager|
|
||||||
|
|
@ -43,6 +50,12 @@ class NotificationsController < ApplicationController
|
||||||
|
|
||||||
@unread_notification_count = current_user.unread_notifications.count
|
@unread_notification_count = current_user.unread_notifications.count
|
||||||
|
|
||||||
|
@grouped_unread_notification_counts = {}
|
||||||
|
|
||||||
|
Notification.types.each_with_object(current_user.unread_notifications.group_by(&:type)) {|(name, type), notifications|
|
||||||
|
@grouped_unread_notification_counts[name] = notifications.has_key?(type) ? notifications[type].count : 0
|
||||||
|
}
|
||||||
|
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html
|
format.html
|
||||||
format.xml { render :xml => @notifications.to_xml }
|
format.xml { render :xml => @notifications.to_xml }
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,8 @@ class PeopleController < ApplicationController
|
||||||
return render :text => I18n.t('people.person.thats_you') if @person == current_user.person
|
return render :text => I18n.t('people.person.thats_you') if @person == current_user.person
|
||||||
|
|
||||||
@contact = current_user.contact_for(@person) || Contact.new
|
@contact = current_user.contact_for(@person) || Contact.new
|
||||||
render :partial => 'aspect_membership_dropdown', :locals => {:contact => @contact, :person => @person, :hang => 'left'}
|
bootstrap = params[:bootstrap] || false
|
||||||
|
render :partial => 'aspect_membership_dropdown', :locals => {:contact => @contact, :person => @person, :hang => 'left', :bootstrap => bootstrap}
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ class PostsController < ApplicationController
|
||||||
before_filter :set_format_if_malformed_from_status_net, :only => :show
|
before_filter :set_format_if_malformed_from_status_net, :only => :show
|
||||||
before_filter :find_post, :only => [:show, :interactions]
|
before_filter :find_post, :only => [:show, :interactions]
|
||||||
|
|
||||||
before_filter -> { @css_framework = :bootstrap }
|
use_bootstrap_for :show
|
||||||
|
|
||||||
respond_to :html,
|
respond_to :html,
|
||||||
:mobile,
|
:mobile,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ class StatusMessagesController < ApplicationController
|
||||||
|
|
||||||
before_filter :remove_getting_started, :only => [:create]
|
before_filter :remove_getting_started, :only => [:create]
|
||||||
|
|
||||||
before_filter -> { @css_framework = :bootstrap }, :only => [:bookmarklet]
|
use_bootstrap_for :bookmarklet
|
||||||
|
|
||||||
respond_to :html,
|
respond_to :html,
|
||||||
:mobile,
|
:mobile,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
# the COPYRIGHT file.
|
# the COPYRIGHT file.
|
||||||
|
|
||||||
module AspectGlobalHelper
|
module AspectGlobalHelper
|
||||||
def aspect_membership_dropdown(contact, person, hang, aspect=nil)
|
def aspect_membership_dropdown(contact, person, hang, aspect=nil, force_bootstrap=false)
|
||||||
aspect_membership_ids = {}
|
aspect_membership_ids = {}
|
||||||
|
|
||||||
selected_aspects = all_aspects.select{|aspect| contact.in_aspect?(aspect)}
|
selected_aspects = all_aspects.select{|aspect| contact.in_aspect?(aspect)}
|
||||||
|
|
@ -12,12 +12,21 @@ module AspectGlobalHelper
|
||||||
aspect_membership_ids[a.id] = record.id
|
aspect_membership_ids[a.id] = record.id
|
||||||
end
|
end
|
||||||
|
|
||||||
render "shared/aspect_dropdown",
|
if bootstrap? || force_bootstrap
|
||||||
|
render "aspect_memberships/aspect_membership_dropdown",
|
||||||
:selected_aspects => selected_aspects,
|
:selected_aspects => selected_aspects,
|
||||||
:aspect_membership_ids => aspect_membership_ids,
|
:aspect_membership_ids => aspect_membership_ids,
|
||||||
:person => person,
|
:person => person,
|
||||||
:hang => hang,
|
:hang => hang,
|
||||||
:dropdown_class => "aspect_membership"
|
:dropdown_class => "aspect_membership"
|
||||||
|
else
|
||||||
|
render "aspect_memberships/aspect_membership_dropdown_blueprint",
|
||||||
|
:selected_aspects => selected_aspects,
|
||||||
|
:aspect_membership_ids => aspect_membership_ids,
|
||||||
|
:person => person,
|
||||||
|
:hang => hang,
|
||||||
|
:dropdown_class => "aspect_membership"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def aspect_dropdown_list_item(aspect, am_id=nil)
|
def aspect_dropdown_list_item(aspect, am_id=nil)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
module NotificationsHelper
|
module NotificationsHelper
|
||||||
include PeopleHelper
|
include PeopleHelper
|
||||||
|
include PostsHelper
|
||||||
|
|
||||||
def object_link(note, actors)
|
def object_link(note, actors)
|
||||||
target_type = note.popup_translation_key
|
target_type = note.popup_translation_key
|
||||||
|
|
@ -7,13 +8,13 @@ module NotificationsHelper
|
||||||
|
|
||||||
if note.instance_of?(Notifications::Mentioned)
|
if note.instance_of?(Notifications::Mentioned)
|
||||||
if post = note.linked_object
|
if post = note.linked_object
|
||||||
translation(target_type, :actors => actors, :count => actors_count, :post_link => link_to(t('notifications.post'), post_path(post)).html_safe)
|
translation(target_type, :actors => actors, :count => actors_count, :post_link => link_to(post_page_title(post), post_path(post)).html_safe)
|
||||||
else
|
else
|
||||||
t(note.deleted_translation_key, :actors => actors, :count => actors_count).html_safe
|
t(note.deleted_translation_key, :actors => actors, :count => actors_count).html_safe
|
||||||
end
|
end
|
||||||
elsif note.instance_of?(Notifications::CommentOnPost) || note.instance_of?(Notifications::AlsoCommented) || note.instance_of?(Notifications::Reshared) || note.instance_of?(Notifications::Liked)
|
elsif note.instance_of?(Notifications::CommentOnPost) || note.instance_of?(Notifications::AlsoCommented) || note.instance_of?(Notifications::Reshared) || note.instance_of?(Notifications::Liked)
|
||||||
if post = note.linked_object
|
if post = note.linked_object
|
||||||
translation(target_type, :actors => actors, :count => actors_count, :post_author => h(post.author_name), :post_link => link_to(t('notifications.post'), post_path(post), 'data-ref' => post.id, :class => 'hard_object_link').html_safe)
|
translation(target_type, :actors => actors, :count => actors_count, :post_author => h(post.author_name), :post_link => link_to(post_page_title(post), post_path(post), 'data-ref' => post.id, :class => 'hard_object_link').html_safe)
|
||||||
else
|
else
|
||||||
t(note.deleted_translation_key, :actors => actors, :count => actors_count).html_safe
|
t(note.deleted_translation_key, :actors => actors, :count => actors_count).html_safe
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,8 @@ module PeopleHelper
|
||||||
if opts[:to] == :photos
|
if opts[:to] == :photos
|
||||||
link_to person_image_tag(person, opts[:size]), person_photos_path(person)
|
link_to person_image_tag(person, opts[:size]), person_photos_path(person)
|
||||||
else
|
else
|
||||||
"<a #{person_href(person)} class='#{opts[:class]}' #{ ("target=" + opts[:target]) if opts[:target]}>
|
remote_or_hovercard_link = Rails.application.routes.url_helpers.person_path(person).html_safe
|
||||||
|
"<a href='#{remote_or_hovercard_link}' class='#{opts[:class]}' #{ ("target=" + opts[:target]) if opts[:target]}>
|
||||||
#{person_image_tag(person, opts[:size])}
|
#{person_image_tag(person, opts[:size])}
|
||||||
</a>".html_safe
|
</a>".html_safe
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -91,4 +91,15 @@ private
|
||||||
def self.suppress_notification?(recipient, post)
|
def self.suppress_notification?(recipient, post)
|
||||||
post.is_a?(Post) && recipient.is_shareable_hidden?(post)
|
post.is_a?(Post) && recipient.is_shareable_hidden?(post)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.types
|
||||||
|
{
|
||||||
|
"also_commented" => "Notifications::AlsoCommented",
|
||||||
|
"comment_on_post" => "Notifications::CommentOnPost",
|
||||||
|
"liked" => "Notifications::Liked",
|
||||||
|
"mentioned" => "Notifications::Mentioned",
|
||||||
|
"reshared" => "Notifications::Reshared",
|
||||||
|
"started_sharing" => "Notifications::StartedSharing"
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
.btn-group.aspect_dropdown.aspect_membership_dropdown
|
||||||
|
%button.btn.btn-small.dropdown-toggle{:class => selected_aspects.size>0 ? "green" : "btn-default", "data-toggle" => "dropdown"}
|
||||||
|
%span.text
|
||||||
|
- if selected_aspects.size == all_aspects.size
|
||||||
|
= t('all_aspects')
|
||||||
|
- elsif selected_aspects.size == 1
|
||||||
|
= selected_aspects.first.name
|
||||||
|
- else
|
||||||
|
= t('shared.aspect_dropdown.toggle', :count => selected_aspects.size)
|
||||||
|
%span.caret
|
||||||
|
|
||||||
|
%ul.dropdown-menu{:class => ["pull-#{hang}", defined?(dropdown_class) && dropdown_class], :unSelectable => 'on', 'data-person_id' => (person.id if defined?(person) && person), 'data-service_uid' => (service_uid if defined?(service_uid)), 'data-person-short-name' => (person.first_name if defined?(person) && person)}
|
||||||
|
- for aspect in all_aspects
|
||||||
|
%li.aspect_selector{ :class => ('selected' if aspect_membership_ids[aspect.id].present?), 'data-aspect_id' => aspect.id, 'data-membership_id' => aspect_membership_ids[aspect.id] }
|
||||||
|
%a
|
||||||
|
%span.status_indicator
|
||||||
|
%i.icon-ok
|
||||||
|
%i.icon-refresh
|
||||||
|
%span.text
|
||||||
|
= aspect.name
|
||||||
|
|
||||||
|
- if (dropdown_may_create_new_aspect && defined?(person) && person)
|
||||||
|
%li.divider
|
||||||
|
%li.newItem
|
||||||
|
.add_aspect
|
||||||
|
= link_to t('contacts.index.add_a_new_aspect'), new_aspect_path(:person_id => person.id, :remote => true), :rel => 'facebox'
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
-# Copyright (c) 2010-2011, Diaspora Inc. This file is
|
|
||||||
-# licensed under the Affero General Public License version 3 or later. See
|
|
||||||
-# the COPYRIGHT file.
|
|
||||||
|
|
||||||
.dropdown{:class => ["hang_#{hang}", defined?(dropdown_class) && dropdown_class]}
|
.dropdown{:class => ["hang_#{hang}", defined?(dropdown_class) && dropdown_class]}
|
||||||
.button.toggle{:class => ("in_aspects" if selected_aspects.size > 0)}
|
.button.toggle{:class => ("in_aspects" if selected_aspects.size > 0)}
|
||||||
- if selected_aspects.size == all_aspects.size
|
- if selected_aspects.size == all_aspects.size
|
||||||
|
|
@ -9,7 +5,7 @@
|
||||||
- elsif selected_aspects.size == 1
|
- elsif selected_aspects.size == 1
|
||||||
= selected_aspects.first.name
|
= selected_aspects.first.name
|
||||||
- else
|
- else
|
||||||
= t('.toggle', :count => selected_aspects.size)
|
= t('shared.aspect_dropdown.toggle', :count => selected_aspects.size)
|
||||||
▼
|
▼
|
||||||
|
|
||||||
.wrapper
|
.wrapper
|
||||||
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,4 +1,4 @@
|
||||||
.notification_element{:data=>{:guid => n.id}, :class => (n.unread ? "unread" : "read")}
|
.notification_element{:data=>{:guid => n.id, :type => (Notification.types.key(n.type) || '')}, :class => (n.unread ? "unread" : "read")}
|
||||||
= person_image_tag n.actors.first, :thumb_small
|
= person_image_tag n.actors.first, :thumb_small
|
||||||
= notification_message_for(n)
|
= notification_message_for(n)
|
||||||
%div
|
%div
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,56 @@
|
||||||
#notifications_content
|
.container-fluid#notifications_container
|
||||||
.span-13
|
.row-fluid
|
||||||
%h2
|
.span3
|
||||||
%span.notification_count{:class => ('unread' if @unread_notification_count >0 )}
|
%h3
|
||||||
= @unread_notification_count
|
|
||||||
= t('.notifications')
|
= t('.notifications')
|
||||||
.span-8.last
|
%ul.nav.nav-tabs.nav-stacked
|
||||||
= link_to t('.mark_all_as_read'), notifications_read_all_path, :class => "button #{'disabled' unless @unread_notification_count > 0}"
|
%li{ :class => ('active' unless params[:type] && @grouped_unread_notification_counts.has_key?(params[:type])) }
|
||||||
.span-24.last
|
%a{ :href => '/notifications' + (params[:show] == 'unread' ? '?show=unread' : '') }
|
||||||
.stream.notifications
|
%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|
|
- @group_days.each do |day, notes|
|
||||||
.day_group.span-24.last
|
.day_group.row-fluid
|
||||||
.span-3
|
.date.span2
|
||||||
.date
|
|
||||||
.day= the_day(day.split(' '))
|
.day= the_day(day.split(' '))
|
||||||
.month= the_month(day.split(' '))
|
.month= the_month(day.split(' '))
|
||||||
|
|
||||||
.span-8.notifications_for_day
|
.notifications_for_day.span10
|
||||||
- notes.each do |note|
|
- notes.each do |note|
|
||||||
.stream_element{:data=>{:guid => note.id}, :class => "#{note.unread ? 'unread' : 'read'}"}
|
= render :partial => 'notifications/notification', :locals => { :note => note }
|
||||||
- if note.type == "Notifications::StartedSharing" && contact = current_user.contact_for(note.effective_target)
|
|
||||||
.float-right
|
|
||||||
= aspect_membership_dropdown(contact, note.effective_target, 'left')
|
|
||||||
|
|
||||||
.media
|
= will_paginate @notifications, :renderer => WillPaginate::ActionView::BootstrapLinkRenderer
|
||||||
.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
|
|
||||||
|
|
||||||
:javascript
|
:javascript
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
Diaspora.page.header.notifications.setUpNotificationPage( $("#notifications_content" ) );
|
new app.views.Notifications({ el: '#notifications_container' });
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
= aspect_membership_dropdown(@contact, @person, 'left')
|
= aspect_membership_dropdown(@contact, @person, 'left', nil, bootstrap)
|
||||||
|
|
|
||||||
|
|
@ -637,25 +637,25 @@ en:
|
||||||
one: "%{actors} sent you a message."
|
one: "%{actors} sent you a message."
|
||||||
other: "%{actors} sent you a message."
|
other: "%{actors} sent you a message."
|
||||||
comment_on_post:
|
comment_on_post:
|
||||||
zero: "%{actors} commented on your %{post_link}."
|
zero: "%{actors} commented on your post %{post_link}."
|
||||||
one: "%{actors} commented on your %{post_link}."
|
one: "%{actors} commented on your post %{post_link}."
|
||||||
other: "%{actors} commented on your %{post_link}."
|
other: "%{actors} commented on your post %{post_link}."
|
||||||
also_commented:
|
also_commented:
|
||||||
zero: "%{actors} also commented on %{post_author}'s %{post_link}."
|
zero: "%{actors} also commented on %{post_author}'s post %{post_link}."
|
||||||
one: "%{actors} also commented on %{post_author}'s %{post_link}."
|
one: "%{actors} also commented on %{post_author}'s post %{post_link}."
|
||||||
other: "%{actors} also commented on %{post_author}'s %{post_link}."
|
other: "%{actors} also commented on %{post_author}'s post %{post_link}."
|
||||||
mentioned:
|
mentioned:
|
||||||
zero: "%{actors} have mentioned you in a %{post_link}."
|
zero: "%{actors} have mentioned you in the post %{post_link}."
|
||||||
one: "%{actors} has mentioned you in a %{post_link}."
|
one: "%{actors} has mentioned you in the post %{post_link}."
|
||||||
other: "%{actors} have mentioned you in a %{post_link}."
|
other: "%{actors} have mentioned you in the %{post_link}."
|
||||||
liked:
|
liked:
|
||||||
zero: "%{actors} have liked your %{post_link}."
|
zero: "%{actors} have liked your post %{post_link}."
|
||||||
one: "%{actors} has liked your %{post_link}."
|
one: "%{actors} has liked your post %{post_link}."
|
||||||
other: "%{actors} have liked your %{post_link}."
|
other: "%{actors} have liked your post %{post_link}."
|
||||||
reshared:
|
reshared:
|
||||||
zero: "%{actors} have reshared your %{post_link}."
|
zero: "%{actors} have reshared your post %{post_link}."
|
||||||
one: "%{actors} has reshared your %{post_link}."
|
one: "%{actors} has reshared your post %{post_link}."
|
||||||
other: "%{actors} have reshared your %{post_link}."
|
other: "%{actors} have reshared your post %{post_link}."
|
||||||
post: "post"
|
post: "post"
|
||||||
also_commented_deleted:
|
also_commented_deleted:
|
||||||
zero: "%{actors} commented on a deleted post."
|
zero: "%{actors} commented on a deleted post."
|
||||||
|
|
@ -675,8 +675,18 @@ en:
|
||||||
other: "%{actors} mentioned you in a deleted post."
|
other: "%{actors} mentioned you in a deleted post."
|
||||||
index:
|
index:
|
||||||
notifications: "Notifications"
|
notifications: "Notifications"
|
||||||
mark_all_as_read: "Mark All as Read"
|
mark_all_as_read: "Mark all as read"
|
||||||
|
mark_read: "Mark read"
|
||||||
mark_unread: "Mark unread"
|
mark_unread: "Mark unread"
|
||||||
|
show_all: "show all"
|
||||||
|
show_unread: "show unread"
|
||||||
|
all_notifications: "All Notifications"
|
||||||
|
also_commented: "Also commented"
|
||||||
|
comment_on_post: "Comment on post"
|
||||||
|
liked: "Liked"
|
||||||
|
mentioned: "Mentioned"
|
||||||
|
reshared: "Reshared"
|
||||||
|
started_sharing: "Started sharing"
|
||||||
and_others:
|
and_others:
|
||||||
zero: "and nobody else"
|
zero: "and nobody else"
|
||||||
one: "and one more"
|
one: "and one more"
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,10 @@ en:
|
||||||
conversation:
|
conversation:
|
||||||
participants: "Participants"
|
participants: "Participants"
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
mark_read: "Mark read"
|
||||||
|
mark_unread: "Mark unread"
|
||||||
|
|
||||||
stream:
|
stream:
|
||||||
hide: "Hide"
|
hide: "Hide"
|
||||||
public: "Public"
|
public: "Public"
|
||||||
|
|
|
||||||
|
|
@ -73,5 +73,16 @@ Feature: Notifications
|
||||||
When I sign in as "bob@bob.bob"
|
When I sign in as "bob@bob.bob"
|
||||||
And I follow "Notifications" in the header
|
And I follow "Notifications" in the header
|
||||||
Then the notification dropdown should be visible
|
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
|
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
|
||||||
19
spec/controllers/jasmine_fixtures/notifications_spec.rb
Normal file
19
spec/controllers/jasmine_fixtures/notifications_spec.rb
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe NotificationsController do
|
||||||
|
describe '#index' do
|
||||||
|
before do
|
||||||
|
sign_in :user, alice
|
||||||
|
@post = FactoryGirl.create(:status_message)
|
||||||
|
FactoryGirl.create(:notification, :recipient => alice, :target => @post)
|
||||||
|
get :read_all
|
||||||
|
FactoryGirl.create(:notification, :recipient => alice, :target => @post)
|
||||||
|
eve.share_with(alice.person, eve.aspects.first)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "generates a jasmine fixture", :fixture => true do
|
||||||
|
get :index
|
||||||
|
save_fixture(html_for("body"), "notifications")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -20,4 +20,20 @@ describe PeopleController do
|
||||||
save_fixture(html_for("body"), "pending_external_people_search")
|
save_fixture(html_for("body"), "pending_external_people_search")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#aspect_membership_dropdown' do
|
||||||
|
before do
|
||||||
|
sign_in :user, bob
|
||||||
|
end
|
||||||
|
|
||||||
|
it "generates a jasmine fixture using Blueprint", :fixture => true do
|
||||||
|
get :aspect_membership_dropdown, :person_id => alice.person.guid
|
||||||
|
save_fixture(html_for("body"), "aspect_membership_dropdown_blueprint")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "generates a jasmine fixture using Bootstrap", :fixture => true do
|
||||||
|
get :aspect_membership_dropdown, :person_id => alice.person.guid, :bootstrap => true
|
||||||
|
save_fixture(html_for("body"), "aspect_membership_dropdown_bootstrap")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,21 @@ describe NotificationsController do
|
||||||
|
|
||||||
Nokogiri(response.body).css('.aspect_membership').should_not be_empty
|
Nokogiri(response.body).css('.aspect_membership').should_not be_empty
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "filter notifications" do
|
||||||
|
it "supports filtering by notification type" do
|
||||||
|
eve.share_with(alice.person, eve.aspects.first)
|
||||||
|
get :index, "type" => "started_sharing"
|
||||||
|
assigns[:notifications].count.should == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
it "supports filtering by read/unread" do
|
||||||
|
get :read_all
|
||||||
|
2.times { FactoryGirl.create(:notification, :recipient => alice, :target => @post) }
|
||||||
|
get :index, "show" => "unread"
|
||||||
|
assigns[:notifications].count.should == 2
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ describe NotificationsHelper do
|
||||||
output.should include I18n.t("#{@notification.popup_translation_key}",
|
output.should include I18n.t("#{@notification.popup_translation_key}",
|
||||||
:actors => notification_people_link(@notification),
|
:actors => notification_people_link(@notification),
|
||||||
:count => @notification.actors.count,
|
:count => @notification.actors.count,
|
||||||
:post_link => "<a href=\"#{post_path(@post)}\" class=\"hard_object_link\" data-ref=\"#{@post.id}\">#{t('notifications.post')}</a>")
|
:post_link => link_to(post_page_title(@post), post_path(@post), 'data-ref' => @post.id, :class => 'hard_object_link').html_safe)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when post is deleted' do
|
context 'when post is deleted' do
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
describe("app.views.AspectMembershipBlueprint", function(){
|
||||||
|
beforeEach(function() {
|
||||||
|
spec.loadFixture("aspect_membership_dropdown_blueprint");
|
||||||
|
this.view = new app.views.AspectMembershipBlueprint();
|
||||||
|
this.person_id = $('.dropdown_list').data('person_id');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('attaches to the aspect selector', function(){
|
||||||
|
spyOn($.fn, 'on');
|
||||||
|
view = new app.views.AspectMembership();
|
||||||
|
|
||||||
|
expect($.fn.on).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
context('adding to aspects', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
this.newAspect = $('li:not(.selected)');
|
||||||
|
this.newAspectId = this.newAspect.data('aspect_id');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls "addMembership"', function() {
|
||||||
|
spyOn(this.view, "addMembership");
|
||||||
|
this.newAspect.trigger('click');
|
||||||
|
|
||||||
|
expect(this.view.addMembership).toHaveBeenCalledWith(this.person_id, this.newAspectId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tries to create a new AspectMembership', function() {
|
||||||
|
spyOn(app.models.AspectMembership.prototype, "save");
|
||||||
|
this.view.addMembership(1, 2);
|
||||||
|
|
||||||
|
expect(app.models.AspectMembership.prototype.save).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays an error when it fails', function() {
|
||||||
|
spyOn(this.view, "_displayError");
|
||||||
|
spyOn(app.models.AspectMembership.prototype, "save").andCallFake(function() {
|
||||||
|
this.trigger('error');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.view.addMembership(1, 2);
|
||||||
|
|
||||||
|
expect(this.view._displayError).toHaveBeenCalledWith('aspect_dropdown.error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('removing from aspects', function(){
|
||||||
|
beforeEach(function() {
|
||||||
|
this.oldAspect = $('li.selected');
|
||||||
|
this.oldMembershipId = this.oldAspect.data('membership_id');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls "removeMembership"', function(){
|
||||||
|
spyOn(this.view, "removeMembership");
|
||||||
|
this.oldAspect.trigger('click');
|
||||||
|
|
||||||
|
expect(this.view.removeMembership).toHaveBeenCalledWith(this.oldMembershipId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('tries to destroy an AspectMembership', function() {
|
||||||
|
spyOn(app.models.AspectMembership.prototype, "destroy");
|
||||||
|
this.view.removeMembership(1);
|
||||||
|
|
||||||
|
expect(app.models.AspectMembership.prototype.destroy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays an error when it fails', function() {
|
||||||
|
spyOn(this.view, "_displayError");
|
||||||
|
spyOn(app.models.AspectMembership.prototype, "destroy").andCallFake(function() {
|
||||||
|
this.trigger('error');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.view.removeMembership(1);
|
||||||
|
|
||||||
|
expect(this.view._displayError).toHaveBeenCalledWith('aspect_dropdown.error_remove');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
context('summary text in the button', function() {
|
||||||
|
beforeEach(function() {
|
||||||
|
this.btn = $('div.button.toggle');
|
||||||
|
this.btn.text(""); // reset
|
||||||
|
this.view.dropdown = $('ul.dropdown_list');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows "no aspects" when nothing is selected', function() {
|
||||||
|
$('li[data-aspect_id]').removeClass('selected');
|
||||||
|
this.view.updateSummary();
|
||||||
|
|
||||||
|
expect(this.btn.text()).toContain(Diaspora.I18n.t('aspect_dropdown.toggle.zero'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows "all aspects" when everything is selected', function() {
|
||||||
|
$('li[data-aspect_id]').addClass('selected');
|
||||||
|
this.view.updateSummary();
|
||||||
|
|
||||||
|
expect(this.btn.text()).toContain(Diaspora.I18n.t('aspect_dropdown.all_aspects'));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the name of the selected aspect ( == 1 )', function() {
|
||||||
|
var list = $('li[data-aspect_id]');
|
||||||
|
list.removeClass('selected'); // reset
|
||||||
|
list.eq(1).addClass('selected');
|
||||||
|
this.view.updateSummary();
|
||||||
|
|
||||||
|
expect(this.btn.text()).toContain(list.eq(1).text());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows the number of selected aspects ( > 1)', function() {
|
||||||
|
var list = $('li[data-aspect_id]');
|
||||||
|
list.removeClass('selected'); // reset
|
||||||
|
$([list.eq(1), list.eq(2)]).addClass('selected');
|
||||||
|
this.view.updateSummary();
|
||||||
|
|
||||||
|
expect(this.btn.text()).toContain(Diaspora.I18n.t('aspect_dropdown.toggle', { 'count':2 }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,40 +1,22 @@
|
||||||
|
|
||||||
describe("app.views.AspectMembership", function(){
|
describe("app.views.AspectMembership", function(){
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
// mock a dummy aspect dropdown
|
// mock a dummy aspect dropdown
|
||||||
this.person = factory.author({name: "My Name"});
|
spec.loadFixture("aspect_membership_dropdown_bootstrap");
|
||||||
spec.content().html(
|
this.view = new app.views.AspectMembership({el: $('.aspect_membership_dropdown')});
|
||||||
'<div class="aspect_membership dropdown">'+
|
this.person_id = $('.dropdown-menu').data('person_id');
|
||||||
' <div class="button toggle">The Button</div>'+
|
|
||||||
' <ul class="dropdown_list" data-person-short-name="'+this.person.name+'" data-person_id="'+this.person.id+'">'+
|
|
||||||
' <li data-aspect_id="10">Aspect 10</li>'+
|
|
||||||
' <li data-membership_id="99" data-aspect_id="11" class="selected">Aspect 11</li>'+
|
|
||||||
' <li data-aspect_id="12">Aspect 12</li>'+
|
|
||||||
' </ul>'+
|
|
||||||
'</div>'
|
|
||||||
);
|
|
||||||
|
|
||||||
this.view = new app.views.AspectMembership();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('attaches to the aspect selector', function(){
|
|
||||||
spyOn($.fn, 'on');
|
|
||||||
view = new app.views.AspectMembership();
|
|
||||||
|
|
||||||
expect($.fn.on).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
context('adding to aspects', function() {
|
context('adding to aspects', function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
this.newAspect = spec.content().find('li:eq(0)');
|
this.newAspect = $('li:not(.selected)');
|
||||||
this.newAspectId = 10;
|
this.newAspectId = this.newAspect.data('aspect_id');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls "addMembership"', function() {
|
it('calls "addMembership"', function() {
|
||||||
spyOn(this.view, "addMembership");
|
spyOn(this.view, "addMembership");
|
||||||
this.newAspect.trigger('click');
|
this.newAspect.trigger('click');
|
||||||
|
|
||||||
expect(this.view.addMembership).toHaveBeenCalledWith(this.person.id, this.newAspectId);
|
expect(this.view.addMembership).toHaveBeenCalledWith(this.person_id, this.newAspectId);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('tries to create a new AspectMembership', function() {
|
it('tries to create a new AspectMembership', function() {
|
||||||
|
|
@ -58,8 +40,8 @@ describe("app.views.AspectMembership", function(){
|
||||||
|
|
||||||
context('removing from aspects', function(){
|
context('removing from aspects', function(){
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
this.oldAspect = spec.content().find('li:eq(1)');
|
this.oldAspect = $('li.selected');
|
||||||
this.oldMembershipId = 99;
|
this.oldMembershipId = this.oldAspect.data('membership_id');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('calls "removeMembership"', function(){
|
it('calls "removeMembership"', function(){
|
||||||
|
|
@ -88,43 +70,23 @@ describe("app.views.AspectMembership", function(){
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
context('summary text in the button', function() {
|
context('updateSummary', function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
this.btn = spec.content().find('div.button.toggle');
|
this.Aspect = $('li:eq(0)');
|
||||||
this.btn.text(""); // reset
|
|
||||||
this.view.dropdown = spec.content().find('ul.dropdown_list');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows "no aspects" when nothing is selected', function() {
|
it('calls "_toggleCheckbox"', function() {
|
||||||
spec.content().find('li[data-aspect_id]').removeClass('selected');
|
spyOn(this.view, "_toggleCheckbox");
|
||||||
this.view.updateSummary();
|
this.view.updateSummary(this.Aspect);
|
||||||
|
|
||||||
expect(this.btn.text()).toContain(Diaspora.I18n.t('aspect_dropdown.toggle.zero'));
|
expect(this.view._toggleCheckbox).toHaveBeenCalledWith(this.Aspect);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows "all aspects" when everything is selected', function() {
|
it('calls "_updateButton"', function() {
|
||||||
spec.content().find('li[data-aspect_id]').addClass('selected');
|
spyOn(this.view, "_updateButton");
|
||||||
this.view.updateSummary();
|
this.view.updateSummary(this.Aspect);
|
||||||
|
|
||||||
expect(this.btn.text()).toContain(Diaspora.I18n.t('aspect_dropdown.all_aspects'));
|
expect(this.view._updateButton).toHaveBeenCalledWith('green');
|
||||||
});
|
|
||||||
|
|
||||||
it('shows the name of the selected aspect ( == 1 )', function() {
|
|
||||||
var list = spec.content().find('li[data-aspect_id]');
|
|
||||||
list.removeClass('selected'); // reset
|
|
||||||
list.eq(1).addClass('selected');
|
|
||||||
this.view.updateSummary();
|
|
||||||
|
|
||||||
expect(this.btn.text()).toContain(list.eq(1).text());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shows the number of selected aspects ( > 1)', function() {
|
|
||||||
var list = spec.content().find('li[data-aspect_id]');
|
|
||||||
list.removeClass('selected'); // reset
|
|
||||||
$([list.eq(1), list.eq(2)]).addClass('selected');
|
|
||||||
this.view.updateSummary();
|
|
||||||
|
|
||||||
expect(this.btn.text()).toContain(Diaspora.I18n.t('aspect_dropdown.toggle', { 'count':2 }));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
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