cb59388c5b
no issue - adds `eslint-plugin-sort-imports-es6-autofix` dependency - implements ESLint's base `sort-imports` rule but has a distinction in that `import {foo} from 'bar';` is considered `multiple` rather than `single` - fixes ESLint's autofix behaviour so `eslint --fix` will actually fix the sort order - updates all unordered import rules by using `eslint --fix` With the increased number of `import` statements since Ember+ecosystem started moving towards es6 modules I've found it frustrating at times trying to search through randomly ordered import statements. Recently I've been sorting imports manually when I've added new code or touched old code so I thought I'd add an ESLint rule to codify it.
190 lines
5.8 KiB
JavaScript
190 lines
5.8 KiB
JavaScript
import Service from 'ember-service';
|
|
import get from 'ember-metal/get';
|
|
import injectService from 'ember-service/inject';
|
|
import set from 'ember-metal/set';
|
|
import {dasherize} from 'ember-string';
|
|
import {A as emberA, isEmberArray} from 'ember-array/utils';
|
|
import {filter} from 'ember-computed';
|
|
import {isBlank} from 'ember-utils';
|
|
import {
|
|
isMaintenanceError,
|
|
isVersionMismatchError
|
|
} from 'ghost-admin/services/ajax';
|
|
|
|
// 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 Service.extend({
|
|
delayedNotifications: emberA(),
|
|
content: emberA(),
|
|
|
|
upgradeStatus: injectService(),
|
|
|
|
alerts: filter('content', function (notification) {
|
|
let status = get(notification, 'status');
|
|
return status === 'alert';
|
|
}),
|
|
|
|
notifications: filter('content', function (notification) {
|
|
let status = get(notification, 'status');
|
|
return status === 'notification';
|
|
}),
|
|
|
|
handleNotification(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 (!get(message, 'status')) {
|
|
set(message, 'status', 'notification');
|
|
}
|
|
|
|
// close existing duplicate alerts/notifications to avoid stacking
|
|
if (get(message, 'key')) {
|
|
this._removeItems(get(message, 'status'), get(message, 'key'));
|
|
}
|
|
|
|
if (!delayed) {
|
|
this.get('content').pushObject(message);
|
|
} else {
|
|
this.get('delayedNotifications').pushObject(message);
|
|
}
|
|
},
|
|
|
|
showAlert(message, options) {
|
|
options = options || {};
|
|
|
|
this.handleNotification({
|
|
message,
|
|
status: 'alert',
|
|
type: options.type,
|
|
key: options.key
|
|
}, options.delayed);
|
|
},
|
|
|
|
showNotification(message, options) {
|
|
options = options || {};
|
|
|
|
if (!options.doNotCloseNotifications) {
|
|
this.closeNotifications();
|
|
} else {
|
|
// TODO: this should be removed along with showErrors
|
|
options.key = undefined;
|
|
}
|
|
|
|
this.handleNotification({
|
|
message,
|
|
status: 'notification',
|
|
type: options.type,
|
|
key: options.key
|
|
}, options.delayed);
|
|
},
|
|
|
|
showAPIError(resp, options) {
|
|
// handle "global" errors
|
|
if (isVersionMismatchError(resp)) {
|
|
return this.get('upgradeStatus').requireUpgrade();
|
|
} else if (isMaintenanceError(resp)) {
|
|
return this.get('upgradeStatus').maintenanceAlert();
|
|
}
|
|
|
|
// loop over ember-ajax errors object
|
|
if (resp && isEmberArray(resp.errors)) {
|
|
return resp.errors.forEach((error) => {
|
|
this._showAPIError(error, options);
|
|
});
|
|
}
|
|
|
|
this._showAPIError(resp, options);
|
|
},
|
|
|
|
_showAPIError(resp, options) {
|
|
options = options || {};
|
|
options.type = options.type || 'error';
|
|
|
|
// if possible use the title to get a unique key
|
|
// - we only show one alert for each key so if we get multiple errors
|
|
// only the last one will be shown
|
|
if (!options.key && !isBlank(get(resp, 'title'))) {
|
|
options.key = dasherize(get(resp, 'title'));
|
|
}
|
|
options.key = ['api-error', options.key].compact().join('.');
|
|
|
|
let msg = options.defaultErrorText || 'There was a problem on the server, please try again.';
|
|
|
|
if (resp instanceof String) {
|
|
msg = resp;
|
|
} else if (!isBlank(get(resp, 'detail'))) {
|
|
msg = resp.detail;
|
|
} else if (!isBlank(get(resp, 'message'))) {
|
|
msg = resp.message;
|
|
}
|
|
|
|
this.showAlert(msg, options);
|
|
},
|
|
|
|
displayDelayed() {
|
|
this.delayedNotifications.forEach((message) => {
|
|
this.get('content').pushObject(message);
|
|
});
|
|
this.delayedNotifications = [];
|
|
},
|
|
|
|
closeNotification(notification) {
|
|
let content = this.get('content');
|
|
|
|
if (typeof notification.toJSON === 'function') {
|
|
notification.deleteRecord();
|
|
notification.save().finally(() => {
|
|
content.removeObject(notification);
|
|
});
|
|
} else {
|
|
content.removeObject(notification);
|
|
}
|
|
},
|
|
|
|
closeNotifications(key) {
|
|
this._removeItems('notification', key);
|
|
},
|
|
|
|
closeAlerts(key) {
|
|
this._removeItems('alert', key);
|
|
},
|
|
|
|
clearAll() {
|
|
this.get('content').clear();
|
|
},
|
|
|
|
_removeItems(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
|
|
let escapedKeyBase = keyBase.replace('.', '\\.');
|
|
let keyRegex = new RegExp(`^${escapedKeyBase}`);
|
|
|
|
this.set('content', this.get('content').reject((item) => {
|
|
let itemKey = get(item, 'key');
|
|
let itemStatus = 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(key) {
|
|
return key.split('.').slice(0, 2).join('.');
|
|
}
|
|
});
|