Commit 23a91eb6 authored by Jonne Haß's avatar Jonne Haß

Merge pull request #4814 from svbergerem/notifications-bootstrap-port

[WIP] Port notifications to Bootstrap and add filters, port hovercards
parents 835608f0 4fc9c641
......@@ -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)
......@@ -32,6 +33,7 @@
* 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)
* 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
......
......@@ -99,7 +99,7 @@ var app = {
setupGlobalViews: function() {
app.hovercard = new app.views.Hovercard();
app.aspectMemberships = new app.views.AspectMembership();
app.aspectMembershipsBlueprint = new app.views.AspectMembershipBlueprint();
app.sidebar = new app.views.Sidebar();
},
......
/**
* 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() });
}
});
//= require ./aspects_dropdown_view
/**
* this view lets the user (de-)select aspect memberships in the context
* 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
* the results are dislpayed as flash messages.
*/
app.views.AspectMembership = Backbone.View.extend({
app.views.AspectMembership = app.views.AspectsDropdown.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));
events: {
"click ul.aspect_membership.dropdown-menu > li.aspect_selector": "_clickHandler"
},
initialize: function() {
this.list_item = null;
this.dropdown = null;
},
......@@ -22,7 +22,7 @@ app.views.AspectMembership = Backbone.View.extend({
// -> addMembership
// -> removeMembership
_clickHandler: function(evt) {
this.list_item = $(evt.target);
this.list_item = $(evt.target).closest('li.aspect_selector');
this.dropdown = this.list_item.parent();
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...
.data('membership_id', membership_id)
.addClass('selected');
.data('membership_id', membership_id);
this.updateSummary();
this.updateSummary(li);
this._done();
},
// show an error flash msg
_displayError: function(msg_id) {
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() });
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+'"]');
li.removeAttr('data-membership_id')
.removeData('membership_id')
.removeClass('selected');
.removeData('membership_id');
this.updateSummary(li);
// we just removed the last aspect, inform the user with a flash message
// 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 });
}
this.updateSummary();
this._done();
},
......@@ -129,33 +127,8 @@ app.views.AspectMembership = Backbone.View.extend({
},
// 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 + '');
updateSummary: function(target) {
this._toggleCheckbox(target);
this._updateButton('green');
},
_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
var href = this.href();
href += "/aspect_membership_button"
href += "/aspect_membership_button";
if(gon.bootstrap == true){
href += "?bootstrap=true";
}
$.get(href, function(response) {
self.dropdown_container.html(response);
});
var aspect_membership = new app.views.AspectMembership({el: self.dropdown_container});
},
_positionHovercard: function() {
......
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');
}
}
});
// require ../aspects_dropdown_view
//= require ../aspects_dropdown_view
/*
* Aspects view for the publisher.
......
......@@ -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
......
......@@ -18,7 +18,10 @@
.icon-refresh { display: inline-block;}
.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%);
&:hover {
@include button-gradient-hover($creation-blue);
background: $creation-blue;
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 {
margin-top: 0px;
margin-bottom: 0px;
padding-bottom: 0px;
}
a {
color: $blue;
font-weight: bold !important;
font-size: 16px;
a {
color: $blue;
font-weight: bold !important;
}