FEATURE: live refresh notifications as they happen

This commit is contained in:
Sam 2015-09-04 13:20:33 +10:00
parent 8bc7423045
commit a54e8f3c5e
6 changed files with 75 additions and 23 deletions

View file

@ -50,10 +50,20 @@ export default Ember.Component.extend({
// TODO: It's a bit odd to use the store in a component, but this one really
// wants to reach out and grab notifications
const store = this.container.lookup('store:main');
const stale = store.findStale('notification', {recent: true, limit });
const stale = store.findStale('notification', {recent: true, limit }, {storageKey: 'recent-notifications'});
if (stale.hasResults) {
this.set('notifications', stale.results);
const results = stale.results;
var content = results.get('content');
// we have to truncate to limit, otherwise we will render too much
if (content && (content.length > limit)) {
content = content.splice(0, limit);
results.set('content', content);
results.set('totalRows', limit);
}
this.set('notifications', results);
} else {
this.set('loadingNotifications', true);
}

View file

@ -8,7 +8,12 @@ export default {
const user = container.lookup('current-user:main'),
site = container.lookup('site:main'),
siteSettings = container.lookup('site-settings:main'),
bus = container.lookup('message-bus:main');
bus = container.lookup('message-bus:main'),
keyValueStore = container.lookup('key-value-store:main');
// clear old cached notifications
// they could be a week old for all we know
keyValueStore.remove('recent-notifications');
if (user) {
@ -38,6 +43,32 @@ export default {
if (oldUnread !== data.unread_notifications || oldPM !== data.unread_private_messages) {
user.set('lastNotificationChange', new Date());
}
var stale = keyValueStore.getObject('recent-notifications');
const lastNotification = data.last_notification && data.last_notification.notification;
if (stale && stale.notifications && lastNotification) {
const oldNotifications = stale.notifications;
const staleIndex = _.findIndex(oldNotifications, {id: lastNotification.id});
if (staleIndex > -1) {
oldNotifications.splice(staleIndex, 1);
}
// this gets a bit tricky, uread pms are bumped to front
var insertPosition = 0;
if (lastNotification.notification_type !== 6) {
insertPosition = _.findIndex(oldNotifications, function(n){
return n.notification_type !== 6 || n.read;
});
insertPosition = insertPosition === -1 ? oldNotifications.length - 1 : insertPosition;
}
oldNotifications.splice(insertPosition, 0, lastNotification);
keyValueStore.setItem('recent-notifications', JSON.stringify(stale));
}
}, user.notification_channel_position);
bus.subscribe("/categories", function(data) {

View file

@ -43,6 +43,13 @@ KeyValueStore.prototype = {
get(key) {
if (!safeLocalStorage) { return null; }
return safeLocalStorage[this.context + key];
},
getObject(key) {
if (!safeLocalStorage) { return null; }
try {
return JSON.parse(safeLocalStorage[this.context + key]);
} catch(e) {}
}
};

View file

@ -1,8 +1,6 @@
import StaleResult from 'discourse/lib/stale-result';
import { hashString } from 'discourse/lib/hash';
var skipFirst = true;
// Mix this in to an adapter to provide stale caching in our key value store
export default {
storageKey(type, findArgs) {
@ -10,17 +8,14 @@ export default {
return `${type}_${hashedArgs}`;
},
findStale(store, type, findArgs) {
findStale(store, type, findArgs, opts) {
const staleResult = new StaleResult();
const key = (opts && opts.storageKey) || this.storageKey(type, findArgs)
try {
if (!skipFirst) {
const stored = this.keyValueStore.getItem(this.storageKey(type, findArgs));
if (stored) {
const parsed = JSON.parse(stored);
staleResult.setResults(parsed);
}
} else {
skipFirst = false;
const stored = this.keyValueStore.getItem(key);
if (stored) {
const parsed = JSON.parse(stored);
staleResult.setResults(parsed);
}
} catch(e) {
// JSON parsing error
@ -28,9 +23,11 @@ export default {
return staleResult;
},
find(store, type, findArgs) {
find(store, type, findArgs, opts) {
const key = (opts && opts.storageKey) || this.storageKey(type, findArgs)
return this._super(store, type, findArgs).then((results) => {
this.keyValueStore.setItem(this.storageKey(type, findArgs), JSON.stringify(results));
this.keyValueStore.setItem(key, JSON.stringify(results));
return results;
});
}

View file

@ -71,18 +71,18 @@ export default Ember.Object.extend({
// See if the store can find stale data. We sometimes prefer to show stale data and
// refresh it in the background.
findStale(type, findArgs) {
const stale = this.adapterFor(type).findStale(this, type, findArgs);
findStale(type, findArgs, opts) {
const stale = this.adapterFor(type).findStale(this, type, findArgs, opts);
if (stale.hasResults) {
stale.results = this._hydrateFindResults(stale.results, type, findArgs);
}
stale.refresh = () => this.find(type, findArgs);
stale.refresh = () => this.find(type, findArgs, opts);
return stale;
},
find(type, findArgs) {
return this.adapterFor(type).find(this, type, findArgs).then((result) => {
return this._hydrateFindResults(result, type, findArgs);
find(type, findArgs, opts) {
return this.adapterFor(type).find(this, type, findArgs, opts).then((result) => {
return this._hydrateFindResults(result, type, findArgs, opts);
});
},

View file

@ -306,10 +306,17 @@ class User < ActiveRecord::Base
end
def publish_notifications_state
# publish last notification json with the message so we
# can apply an update
notification = notifications.visible.order('notifications.id desc').first
json = NotificationSerializer.new(notification).as_json if notification
MessageBus.publish("/notification/#{id}",
{unread_notifications: unread_notifications,
unread_private_messages: unread_private_messages,
total_unread_notifications: total_unread_notifications},
total_unread_notifications: total_unread_notifications,
last_notification: json
},
user_ids: [id] # only publish the notification to this user
)
end