Skip to content

Commit

Permalink
FEATURE: Actually show more notifications
Browse files Browse the repository at this point in the history
The "Show more notifications..." link in the notifications dropdown now
links to /my/notifications, which is a historical view of all
notifications you have recieved.

Notification history is loaded in blocks of 60 at a time.

Admins can see others' notification history. (This was requested for
'debugging purposes', though that's what impersonation is for, IMO.)
  • Loading branch information
riking committed Sep 9, 2014
1 parent a5e98c9 commit 69bc552
Show file tree
Hide file tree
Showing 20 changed files with 246 additions and 12 deletions.
8 changes: 6 additions & 2 deletions app/assets/javascripts/discourse/controllers/header.js.es6
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,16 @@ export default DiscourseController.extend({
if (self.get("loadingNotifications")) { return; }

self.set("loadingNotifications", true);
Discourse.ajax("/notifications").then(function(result) {
Discourse.NotificationContainer.loadRecent().then(function(result) {
self.setProperties({
'currentUser.unread_notifications': 0,
notifications: result
});
}).finally(function(){
}).catch(function() {
self.setProperties({
notifications: null
});
}).finally(function() {
self.set("loadingNotifications", false);
});
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

export default Ember.ArrayController.extend({
canLoadMore: true,
loading: false,

actions: {
loadMore: function() {
if (this.get('canLoadMore') && !this.get('loading')) {
this.set('loading', true);
var self = this;
Discourse.NotificationContainer.loadHistory(
self.get('model.lastObject.created_at'),
self.get('user.username')).then(function(result) {
self.set('loading', false);
self.pushObjects(result);
// Stop trying if it's the end
if (result.length === 0) {
self.set('canLoadMore', false);
}
}).catch(function(error) {
self.set('loading', false);
Em.Logger.error(error);
});
}
}
}
});
2 changes: 2 additions & 0 deletions app/assets/javascripts/discourse/controllers/user.js.es6
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export default ObjectController.extend({
return this.get('viewingSelf') || Discourse.User.currentProp('admin');
}.property('viewingSelf'),

canSeeNotificationHistory: Em.computed.alias('canSeePrivateMessages'),

showBadges: function() {
return Discourse.SiteSettings.enable_badges && (this.get('content.badge_count') > 0);
}.property('content.badge_count'),
Expand Down
48 changes: 48 additions & 0 deletions app/assets/javascripts/discourse/models/notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
Discourse.NotificationContainer = Ember.ArrayProxy.extend({

});

Discourse.NotificationContainer.reopenClass({

createFromJson: function(json_array) {
return Discourse.NotificationContainer.create({content: json_array});
},

createFromError: function(error) {
return Discourse.NotificationContainer.create({
content: [],
error: true,
forbidden: error.status === 403
});
},

loadRecent: function() {
return Discourse.ajax('/notifications').then(function(result) {
return Discourse.NotificationContainer.createFromJson(result);
}).catch(function(error) {
// HeaderController can't handle it properly
throw error;
});
},

loadHistory: function(beforeDate, username) {
var url = '/notifications/history.json',
params = [
beforeDate ? ('before=' + beforeDate) : null,
username ? ('user=' + username) : null
];

// Remove nulls
params = params.filter(function(param) { return !!param; });
// Build URL
params.forEach(function(param, idx) {
url = url + (idx === 0 ? '?' : '&') + param;
});

return Discourse.ajax(url).then(function(result) {
return Discourse.NotificationContainer.createFromJson(result);
}).catch(function(error) {
return Discourse.NotificationContainer.createFromError(error);
});
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ Discourse.Route.buildRoutes(function() {
});

this.route('badges');
this.route('notifications');
this.route('flaggedPosts', { path: '/flagged-posts' });
this.route('deletedPosts', { path: '/deleted-posts' });

Expand Down
19 changes: 19 additions & 0 deletions app/assets/javascripts/discourse/routes/user-notifications.js.es6
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export default Discourse.Route.extend({
model: function() {
var user = this.modelFor('user');
return Discourse.NotificationContainer.loadHistory(undefined, user.get('username'));
},

setupController: function(controller, model) {
this.controllerFor('user').set('indexStream', false);
if (this.controllerFor('user_activity').get('content')) {
this.controllerFor('user_activity').set('userActionType', -1);
}
controller.set('model', model);
controller.set('user', this.modelFor('user'));
},

renderTemplate: function() {
this.render('user-notification-history', {into: 'user', outlet: 'userOutlet'});
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@
{{notification-item notification=this scope=scope}}
{{/each}}
<li class="read last">
<a {{bind-attr href="currentUser.path"}}>{{i18n notifications.more}} &hellip;</a>
<a href="/my/notifications">{{i18n notifications.more}}&hellip;</a>
</li>
</ul>
{{else}}
<div class="none">{{i18n notifications.none}}</div>
{{#if error}}
<div class="none error">{{i18n notifications.none}}</div>
{{else}}
<div class="none">{{i18n notifications.none}}</div>
{{/if}}
{{/if}}
{{else}}
<div class='loading'><i class='fa fa-spinner fa-spin'></i></div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{{#if model.error}}
<div class="item error">
{{#if model.forbidden}}
{{i18n errors.reasons.forbidden}}
{{else}}
{{i18n errors.desc.unknown}}
{{/if}}
</div>
{{/if}}

{{#each itemController="notification"}}
<div {{bind-attr class=":item :notification read::unread"}}>
{{notification-item notification=this scope=scope}}
<span class="time">
{{date path="created_at" leaveAgo="true"}}
</span>
</div>
{{/each}}
{{#if loading}}
<div class='spinner'>{{i18n loading}}</div>
{{/if}}
{{#unless canLoadMore}}
<div class='end-of-stream'></div>
{{/unless}}
10 changes: 10 additions & 0 deletions app/assets/javascripts/discourse/templates/user/user.js.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@
{{/link-to}}
{{/link-to}}
{{/if}}
{{#if canSeeNotificationHistory}}
{{#link-to 'user.notifications' tagName="li"}}
{{#link-to 'user.notifications'}}
<i class='glyph fa fa-comment'></i>
{{i18n user.notification_history}}
<span class='count'>({{unread_notification_count}})</span>
<span class='fa fa-chevron-right'></span>
{{/link-to}}
{{/link-to}}
{{/if}}
</ul>

{{#if canSeePrivateMessages}}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default Ember.View.extend(Discourse.LoadMore, {
eyelineSelector: '.user-stream .notification',
classNames: ['user-stream', 'notification-history'],
templateName: 'user/notifications'
});
5 changes: 5 additions & 0 deletions app/assets/stylesheets/common/base/user.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
}
}

.end-of-stream {
border: 3px solid $primary;
width: 100%;
}

.user-navigation {

.map {
Expand Down
24 changes: 24 additions & 0 deletions app/assets/stylesheets/desktop/user.scss
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,30 @@
float: right;
margin-top: -4px;
}
.notification {
&.unread {
background-color: dark-light-diff($tertiary, $secondary, 90%, -60%);
}

li { display: inline-block; }
p {
display: inline-block;
margin-left: 10px;
span {
color: $primary;
}
}
.time {
display: inline-block;
margin-left: 10px;
float: none;
}
// common/base/header.scss
.fa, .icon {
color: scale-color($primary, $lightness: 50%);
font-size: 24px;
}
}
}

.staff-counters {
Expand Down
20 changes: 20 additions & 0 deletions app/assets/stylesheets/mobile/user.scss
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,26 @@
float: right !important;
margin-top: -8px;
}
.notification {
padding: 0 8px;
li { display: inline-block; }
p {
display: inline-block;
margin: 7px;
span {
color: $primary;
}
}
.time {
display: inline-block;
margin: 0;
float: none;
}
// common/base/header.scss
.fa, .icon {
color: scale-color($primary, $lightness: 50%);
}
}
}

.staff-counters {
Expand Down
23 changes: 22 additions & 1 deletion app/controllers/notifications_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ class NotificationsController < ApplicationController

before_filter :ensure_logged_in

def index
def recent
notifications = Notification.recent_report(current_user, 10)

if notifications.present?
Expand All @@ -16,4 +16,25 @@ def index
render_serialized(notifications, NotificationSerializer)
end

def history
params.permit(:before, :user)
params[:before] ||= 1.day.from_now

user = current_user
if params[:user]
user = User.find_by_username(params[:user].to_s)
end

unless guardian.can_see_notifications?(user)
return render json: {errors: [I18n.t('js.errors.reasons.forbidden')]}, status: 403
end

notifications = Notification.where(user_id: user.id)
.includes(:topic)
.limit(60)
.where('created_at < ?', params[:before])
.order(created_at: :desc)

render_serialized(notifications, NotificationSerializer)
end
end
6 changes: 6 additions & 0 deletions app/serializers/user_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def self.private_attributes(*attrs)
:suspended_till,
:uploaded_avatar_id,
:badge_count,
:unread_notification_count,
:has_title_badges,
:edit_history_public,
:custom_fields
Expand Down Expand Up @@ -76,6 +77,7 @@ def self.private_attributes(*attrs)
:tracked_category_ids,
:watched_category_ids,
:private_messages_stats,
:unread_notification_count,
:disable_jump_reply,
:gravatar_avatar_upload_id,
:custom_avatar_upload_id,
Expand Down Expand Up @@ -242,6 +244,10 @@ def has_title_badges
object.badges.where(allow_title: true).count > 0
end

def unread_notification_count
Notification.where(user_id: object.id, read: false).count
end

def include_edit_history_public?
can_edit && !SiteSetting.edit_history_visible_to_public
end
Expand Down
2 changes: 2 additions & 0 deletions config/locales/client.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ en:
invited_by: "Invited By"
trust_level: "Trust Level"
notifications: "Notifications"
notification_history: "Notification History"
disable_jump_reply: "Don't jump to your new post after replying"
dynamic_favicon: "Show incoming message notifications on favicon (experimental)"
edit_history_public: "Let other users view my post revisions"
Expand Down Expand Up @@ -520,6 +521,7 @@ en:
network: "Please check your connection."
network_fixed: "Looks like it's back."
server: "Error code: {{status}}"
forbidden: "You're not allowed to view that."
unknown: "Something went wrong."
buttons:
back: "Go Back"
Expand Down
4 changes: 3 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@
get "users/:username/activity" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}
get "users/:username/activity/:filter" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}
get "users/:username/badges" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}
get "users/:username/notifications" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}
delete "users/:username" => "users#destroy", constraints: {username: USERNAME_ROUTE_FORMAT}
get "users/by-external/:external_id" => "users#show"
get "users/:username/flagged-posts" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}
Expand Down Expand Up @@ -270,7 +271,8 @@
end
end

resources :notifications
get "notifications" => "notifications#recent"
get "notifications/history" => "notifications#history"

match "/auth/:provider/callback", to: "users/omniauth_callbacks#complete", via: [:get, :post]
match "/auth/failure", to: "users/omniauth_callbacks#failure", via: [:get, :post]
Expand Down
4 changes: 4 additions & 0 deletions lib/guardian/user_guardian.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ def can_edit_name?(user)
can_edit?(user)
end

def can_see_notifications?(user)
is_me?(user) || is_admin?
end

def can_block_user?(user)
user && is_staff? && not(user.staff?)
end
Expand Down

0 comments on commit 69bc552

Please sign in to comment.