Ghost/ghost/admin/app/services/notifications.js

178 lines
6.0 KiB
JavaScript
Raw Normal View History

2015-02-13 07:22:32 +03:00
import Ember from 'ember';
// Notification keys take the form of "noun.verb.message", eg:
//
// "invite.resend.api-error"
// "user.invite.already-invited"
//
// The "noun.verb" part will be used as the "key base" in duplicate checks
// to avoid stacking of multiple error messages whilst leaving enough
// specificity to re-use keys for i18n lookups
export default Ember.Service.extend({
delayedNotifications: Ember.A(),
content: Ember.A(),
alerts: Ember.computed.filter('content', function (notification) {
var status = Ember.get(notification, 'status');
return status === 'alert';
}),
notifications: Ember.computed.filter('content', function (notification) {
var status = Ember.get(notification, 'status');
return status === 'notification';
}),
handleNotification: function (message, delayed) {
// If this is an alert message from the server, treat it as html safe
if (typeof message.toJSON === 'function' && message.get('status') === 'alert') {
message.set('message', message.get('message').htmlSafe());
}
if (!Ember.get(message, 'status')) {
Ember.set(message, 'status', 'notification');
}
// close existing duplicate alerts/notifications to avoid stacking
if (Ember.get(message, 'key')) {
this._removeItems(Ember.get(message, 'status'), Ember.get(message, 'key'));
}
if (!delayed) {
this.get('content').pushObject(message);
} else {
this.get('delayedNotifications').pushObject(message);
}
},
showAlert: function (message, options) {
options = options || {};
this.handleNotification({
message: message,
status: 'alert',
type: options.type,
key: options.key
}, options.delayed);
},
showNotification: function (message, options) {
options = options || {};
if (!options.doNotCloseNotifications) {
this.closeNotifications();
} else {
// TODO: this should be removed along with showErrors
options.key = undefined;
}
this.handleNotification({
message: message,
status: 'notification',
type: options.type,
key: options.key
}, options.delayed);
},
// TODO: review whether this can be removed once no longer used by validations
showErrors: function (errors, options) {
options = options || {};
options.type = options.type || 'error';
// TODO: getting keys from the server would be useful here (necessary for i18n)
options.key = (options.key && `${options.key}.api-error`) || 'api-error';
if (!options.doNotCloseNotifications) {
this.closeNotifications();
}
// ensure all errors that are passed in get shown
options.doNotCloseNotifications = true;
for (var i = 0; i < errors.length; i += 1) {
this.showNotification(errors[i].message || errors[i], options);
}
},
showAPIError: function (resp, options) {
options = options || {};
options.type = options.type || 'error';
// TODO: getting keys from the server would be useful here (necessary for i18n)
options.key = (options.key && `${options.key}.api-error`) || 'api-error';
if (!options.doNotCloseNotifications) {
this.closeNotifications();
}
options.defaultErrorText = options.defaultErrorText || 'There was a problem on the server, please try again.';
if (resp && resp.jqXHR && resp.jqXHR.responseJSON && resp.jqXHR.responseJSON.error) {
this.showAlert(resp.jqXHR.responseJSON.error, options);
} else if (resp && resp.jqXHR && resp.jqXHR.responseJSON && resp.jqXHR.responseJSON.errors) {
this.showErrors(resp.jqXHR.responseJSON.errors, options);
} else if (resp && resp.jqXHR && resp.jqXHR.responseJSON && resp.jqXHR.responseJSON.message) {
this.showAlert(resp.jqXHR.responseJSON.message, options);
} else {
this.showAlert(options.defaultErrorText, options);
}
},
displayDelayed: function () {
var self = this;
self.delayedNotifications.forEach(function (message) {
self.get('content').pushObject(message);
});
self.delayedNotifications = [];
},
closeNotification: function (notification) {
var content = this.get('content');
if (typeof notification.toJSON === 'function') {
notification.deleteRecord();
notification.save().finally(function () {
content.removeObject(notification);
});
} else {
content.removeObject(notification);
}
},
closeNotifications: function (key) {
this._removeItems('notification', key);
},
closeAlerts: function (key) {
this._removeItems('alert', key);
},
clearAll: function () {
this.get('content').clear();
},
_removeItems: function (status, key) {
if (key) {
let keyBase = this._getKeyBase(key),
// TODO: keys should only have . special char but we should
// probably use a better regexp escaping function/polyfill
escapedKeyBase = keyBase.replace('.', '\\.'),
keyRegex = new RegExp(`^${escapedKeyBase}`);
this.set('content', this.get('content').reject(function (item) {
let itemKey = Ember.get(item, 'key'),
itemStatus = Ember.get(item, 'status');
return itemStatus === status && (itemKey && itemKey.match(keyRegex));
}));
} else {
this.set('content', this.get('content').rejectBy('status', status));
}
},
// take a key and return the first two elements, eg:
// "invite.revoke.failed" => "invite.revoke"
_getKeyBase: function (key) {
return key.split('.').slice(0, 2).join('.');
}
});