diff --git a/core/client/app/components/gh-alert.js b/core/client/app/components/gh-alert.js
index 1ac91e4b39..e5fbad5c28 100644
--- a/core/client/app/components/gh-alert.js
+++ b/core/client/app/components/gh-alert.js
@@ -2,7 +2,7 @@ import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'article',
- classNames: ['gh-alert', 'gh-alert-blue'],
+ classNames: ['gh-alert'],
classNameBindings: ['typeClass'],
notifications: Ember.inject.service(),
@@ -10,22 +10,18 @@ export default Ember.Component.extend({
typeClass: Ember.computed(function () {
var classes = '',
message = this.get('message'),
- type,
- dismissible;
+ type = Ember.get(message, 'type'),
+ typeMapping;
- // Check to see if we're working with a DS.Model or a plain JS object
- if (typeof message.toJSON === 'function') {
- type = message.get('type');
- dismissible = message.get('dismissible');
- } else {
- type = message.type;
- dismissible = message.dismissible;
- }
+ typeMapping = {
+ success: 'green',
+ error: 'red',
+ warn: 'yellow',
+ info: 'blue'
+ };
- classes += 'notification-' + type;
-
- if (type === 'success' && dismissible !== false) {
- classes += ' notification-passive';
+ if (typeMapping[type] !== undefined) {
+ classes += 'gh-alert-' + typeMapping[type];
}
return classes;
diff --git a/core/client/app/components/gh-alerts.js b/core/client/app/components/gh-alerts.js
index 5fd6459163..6e04c4dfb4 100644
--- a/core/client/app/components/gh-alerts.js
+++ b/core/client/app/components/gh-alerts.js
@@ -1,18 +1,14 @@
import Ember from 'ember';
-var AlertsComponent = Ember.Component.extend({
+
+export default Ember.Component.extend({
tagName: 'aside',
classNames: 'gh-alerts',
- messages: Ember.computed.filter('notifications', function (notification) {
- var displayStatus = (typeof notification.toJSON === 'function') ?
- notification.get('status') : notification.status;
+ notifications: Ember.inject.service(),
- return displayStatus === 'persistent';
- }),
+ messages: Ember.computed.alias('notifications.alerts'),
messageCountObserver: Ember.observer('messages.[]', function () {
this.sendAction('notify', this.get('messages').length);
})
});
-
-export default AlertsComponent;
diff --git a/core/client/app/components/gh-notification.js b/core/client/app/components/gh-notification.js
index 60f67744e9..55cb48b731 100644
--- a/core/client/app/components/gh-notification.js
+++ b/core/client/app/components/gh-notification.js
@@ -2,7 +2,7 @@ import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'article',
- classNames: ['gh-notification', 'gh-notification-green'],
+ classNames: ['gh-notification', 'gh-notification-passive'],
classNameBindings: ['typeClass'],
message: null,
@@ -12,22 +12,17 @@ export default Ember.Component.extend({
typeClass: Ember.computed(function () {
var classes = '',
message = this.get('message'),
- type,
- dismissible;
+ type = Ember.get(message, 'type'),
+ typeMapping;
- // Check to see if we're working with a DS.Model or a plain JS object
- if (typeof message.toJSON === 'function') {
- type = message.get('type');
- dismissible = message.get('dismissible');
- } else {
- type = message.type;
- dismissible = message.dismissible;
- }
+ typeMapping = {
+ success: 'green',
+ error: 'red',
+ warn: 'yellow'
+ };
- classes += 'notification-' + type;
-
- if (type === 'success' && dismissible !== false) {
- classes += ' notification-passive';
+ if (typeMapping[type] !== undefined) {
+ classes += 'gh-notification-' + typeMapping[type];
}
return classes;
@@ -38,11 +33,15 @@ export default Ember.Component.extend({
self.$().on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', function (event) {
if (event.originalEvent.animationName === 'fade-out') {
- self.get('notifications').removeObject(self.get('message'));
+ self.get('notifications').closeNotification(self.get('message'));
}
});
},
+ willDestroyElement: function () {
+ this.$().off('animationend webkitAnimationEnd oanimationend MSAnimationEnd');
+ },
+
actions: {
closeNotification: function () {
this.get('notifications').closeNotification(this.get('message'));
diff --git a/core/client/app/components/gh-notifications.js b/core/client/app/components/gh-notifications.js
index 03ce6e859f..a8c4c81288 100644
--- a/core/client/app/components/gh-notifications.js
+++ b/core/client/app/components/gh-notifications.js
@@ -6,10 +6,5 @@ export default Ember.Component.extend({
notifications: Ember.inject.service(),
- messages: Ember.computed.filter('notifications.content', function (notification) {
- var displayStatus = (typeof notification.toJSON === 'function') ?
- notification.get('status') : notification.status;
-
- return displayStatus === 'passive';
- })
+ messages: Ember.computed.alias('notifications.notifications')
});
diff --git a/core/client/app/components/gh-trim-focus-input.js b/core/client/app/components/gh-trim-focus-input.js
index b75f4f9844..f1627bf7f6 100644
--- a/core/client/app/components/gh-trim-focus-input.js
+++ b/core/client/app/components/gh-trim-focus-input.js
@@ -13,19 +13,18 @@ var TrimFocusInput = Ember.TextField.extend({
return false;
}),
- didInsertElement: function () {
+ focusField: Ember.on('didInsertElement', function () {
// This fix is required until Mobile Safari has reliable
// autofocus, select() or focus() support
if (this.get('focus') && !device.ios()) {
this.$().val(this.$().val()).focus();
}
- },
+ }),
- focusOut: function () {
+ trimValue: Ember.on('focusOut', function () {
var text = this.$().val();
-
this.$().val(text.trim());
- }
+ })
});
export default TrimFocusInput;
diff --git a/core/client/app/components/gh-user-invited.js b/core/client/app/components/gh-user-invited.js
index 14fdab8059..8aaf83563c 100644
--- a/core/client/app/components/gh-user-invited.js
+++ b/core/client/app/components/gh-user-invited.js
@@ -24,10 +24,10 @@ export default Ember.Component.extend({
// If sending the invitation email fails, the API will still return a status of 201
// but the user's status in the response object will be 'invited-pending'.
if (result.users[0].status === 'invited-pending') {
- notifications.showWarn('Invitation email was not sent. Please try resending.');
+ notifications.showAlert('Invitation email was not sent. Please try resending.', {type: 'error'});
} else {
user.set('status', result.users[0].status);
- notifications.showSuccess(notificationText);
+ notifications.showNotification(notificationText);
}
}).catch(function (error) {
notifications.showAPIError(error);
@@ -46,14 +46,14 @@ export default Ember.Component.extend({
user.destroyRecord().then(function () {
var notificationText = 'Invitation revoked. (' + email + ')';
- notifications.showSuccess(notificationText, false);
+ notifications.showNotification(notificationText);
}).catch(function (error) {
notifications.showAPIError(error);
});
} else {
// if the user is no longer marked as "invited", then show a warning and reload the route
self.sendAction('reload');
- notifications.showError('This user has already accepted the invitation.', {delayed: 500});
+ notifications.showAlert('This user has already accepted the invitation.', {type: 'error', delayed: true});
}
});
}
diff --git a/core/client/app/controllers/modals/delete-all.js b/core/client/app/controllers/modals/delete-all.js
index 30c10f3179..dc19efda5c 100644
--- a/core/client/app/controllers/modals/delete-all.js
+++ b/core/client/app/controllers/modals/delete-all.js
@@ -12,11 +12,11 @@ export default Ember.Controller.extend({
ajax(this.get('ghostPaths.url').api('db'), {
type: 'DELETE'
}).then(function () {
- self.get('notifications').showSuccess('All content deleted from database.');
+ self.get('notifications').showAlert('All content deleted from database.', {type: 'success'});
self.store.unloadAll('post');
self.store.unloadAll('tag');
}).catch(function (response) {
- self.get('notifications').showErrors(response);
+ self.get('notifications').showAPIError(response);
});
},
diff --git a/core/client/app/controllers/modals/delete-post.js b/core/client/app/controllers/modals/delete-post.js
index 1f334c4437..8b87628122 100644
--- a/core/client/app/controllers/modals/delete-post.js
+++ b/core/client/app/controllers/modals/delete-post.js
@@ -15,9 +15,8 @@ export default Ember.Controller.extend({
model.destroyRecord().then(function () {
self.get('dropdown').closeDropdowns();
self.transitionToRoute('posts.index');
- self.get('notifications').showSuccess('Your post has been deleted.', {delayed: true});
}, function () {
- self.get('notifications').showError('Your post could not be deleted. Please try again.');
+ self.get('notifications').showAlert('Your post could not be deleted. Please try again.', {type: 'error'});
});
},
diff --git a/core/client/app/controllers/modals/delete-tag.js b/core/client/app/controllers/modals/delete-tag.js
index 507eacd077..f94908c4b3 100644
--- a/core/client/app/controllers/modals/delete-tag.js
+++ b/core/client/app/controllers/modals/delete-tag.js
@@ -10,14 +10,11 @@ export default Ember.Controller.extend({
actions: {
confirmAccept: function () {
var tag = this.get('model'),
- name = tag.get('name'),
self = this;
this.send('closeMenus');
- tag.destroyRecord().then(function () {
- self.get('notifications').showSuccess('Deleted ' + name);
- }).catch(function (error) {
+ tag.destroyRecord().catch(function (error) {
self.get('notifications').showAPIError(error);
});
},
diff --git a/core/client/app/controllers/modals/delete-user.js b/core/client/app/controllers/modals/delete-user.js
index e446b62954..c3ea0ff24c 100644
--- a/core/client/app/controllers/modals/delete-user.js
+++ b/core/client/app/controllers/modals/delete-user.js
@@ -31,9 +31,8 @@ export default Ember.Controller.extend({
user.destroyRecord().then(function () {
self.store.unloadAll('post');
self.transitionToRoute('team');
- self.get('notifications').showSuccess('The user has been deleted.', {delayed: true});
}, function () {
- self.get('notifications').showError('The user could not be deleted. Please try again.');
+ self.get('notifications').showAlert('The user could not be deleted. Please try again.', {type: 'error'});
});
},
diff --git a/core/client/app/controllers/modals/invite-new-user.js b/core/client/app/controllers/modals/invite-new-user.js
index 4af00cc725..5f8e66f511 100644
--- a/core/client/app/controllers/modals/invite-new-user.js
+++ b/core/client/app/controllers/modals/invite-new-user.js
@@ -55,9 +55,9 @@ export default Ember.Controller.extend({
if (invitedUser) {
if (invitedUser.get('status') === 'invited' || invitedUser.get('status') === 'invited-pending') {
- self.get('notifications').showWarn('A user with that email address was already invited.');
+ self.get('notifications').showAlert('A user with that email address was already invited.', {type: 'warn'});
} else {
- self.get('notifications').showWarn('A user with that email address already exists.');
+ self.get('notifications').showAlert('A user with that email address already exists.', {type: 'warn'});
}
} else {
newUser = self.store.createRecord('user', {
@@ -72,12 +72,16 @@ export default Ember.Controller.extend({
// If sending the invitation email fails, the API will still return a status of 201
// but the user's status in the response object will be 'invited-pending'.
if (newUser.get('status') === 'invited-pending') {
- self.get('notifications').showWarn('Invitation email was not sent. Please try resending.');
+ self.get('notifications').showAlert('Invitation email was not sent. Please try resending.', {type: 'error'});
} else {
- self.get('notifications').showSuccess(notificationText);
+ self.get('notifications').showAlert(notificationText, {type: 'success'});
}
}).catch(function (errors) {
newUser.deleteRecord();
+ // TODO: user model includes ValidationEngine mixin so
+ // save is overridden in order to validate, we probably
+ // want to use inline-validations here and only show an
+ // alert if we have an actual error
self.get('notifications').showErrors(errors);
});
}
diff --git a/core/client/app/controllers/modals/leave-editor.js b/core/client/app/controllers/modals/leave-editor.js
index d455ead29d..2b188f5287 100644
--- a/core/client/app/controllers/modals/leave-editor.js
+++ b/core/client/app/controllers/modals/leave-editor.js
@@ -19,7 +19,7 @@ export default Ember.Controller.extend({
}
if (!transition || !editorController) {
- this.get('notifications').showError('Sorry, there was an error in the application. Please let the Ghost team know what happened.');
+ this.get('notifications').showNotification('Sorry, there was an error in the application. Please let the Ghost team know what happened.', {type: 'error'});
return true;
}
diff --git a/core/client/app/controllers/modals/signin.js b/core/client/app/controllers/modals/signin.js
index 89a438ef7f..ada9b10280 100644
--- a/core/client/app/controllers/modals/signin.js
+++ b/core/client/app/controllers/modals/signin.js
@@ -22,7 +22,6 @@ export default Ember.Controller.extend(ValidationEngine, {
this.get('session').authenticate(authStrategy, data).then(function () {
self.send('closeModal');
- self.get('notifications').showSuccess('Login successful.');
self.set('password', '');
}).catch(function () {
// if authentication fails a rejected promise will be returned.
@@ -41,7 +40,7 @@ export default Ember.Controller.extend(ValidationEngine, {
$('#login').find('input').trigger('change');
this.validate({format: false}).then(function () {
- self.get('notifications').closePassive();
+ self.get('notifications').closeNotifications();
self.send('authenticate');
}).catch(function (errors) {
self.get('notifications').showErrors(errors);
diff --git a/core/client/app/controllers/modals/transfer-owner.js b/core/client/app/controllers/modals/transfer-owner.js
index f721713962..40372adaee 100644
--- a/core/client/app/controllers/modals/transfer-owner.js
+++ b/core/client/app/controllers/modals/transfer-owner.js
@@ -33,7 +33,7 @@ export default Ember.Controller.extend({
});
}
- self.get('notifications').showSuccess('Ownership successfully transferred to ' + user.get('name'));
+ self.get('notifications').showAlert('Ownership successfully transferred to ' + user.get('name'), {type: 'success'});
}).catch(function (error) {
self.get('notifications').showAPIError(error);
});
diff --git a/core/client/app/controllers/modals/upload.js b/core/client/app/controllers/modals/upload.js
index f186205826..f11abae874 100644
--- a/core/client/app/controllers/modals/upload.js
+++ b/core/client/app/controllers/modals/upload.js
@@ -10,11 +10,9 @@ export default Ember.Controller.extend({
var notifications = this.get('notifications');
this.get('model').save().then(function (model) {
- notifications.showSuccess('Saved');
-
return model;
}).catch(function (err) {
- notifications.showErrors(err);
+ notifications.showAPIError(err);
});
},
diff --git a/core/client/app/controllers/post-settings-menu.js b/core/client/app/controllers/post-settings-menu.js
index d40f05ee69..58051e9210 100644
--- a/core/client/app/controllers/post-settings-menu.js
+++ b/core/client/app/controllers/post-settings-menu.js
@@ -193,10 +193,6 @@ export default Ember.Controller.extend(SettingsMenuMixin, {
this.get('notifications').showErrors(errors);
},
- showSuccess: function (message) {
- this.get('notifications').showSuccess(message);
- },
-
actions: {
togglePage: function () {
var self = this;
diff --git a/core/client/app/controllers/reset.js b/core/client/app/controllers/reset.js
index a428b30b5c..4f0a2d6307 100644
--- a/core/client/app/controllers/reset.js
+++ b/core/client/app/controllers/reset.js
@@ -33,8 +33,8 @@ export default Ember.Controller.extend(ValidationEngine, {
var credentials = this.getProperties('newPassword', 'ne2Password', 'token'),
self = this;
- this.toggleProperty('submitting');
- this.validate({format: false}).then(function () {
+ this.validate().then(function () {
+ self.toggleProperty('submitting');
ajax({
url: self.get('ghostPaths.url').api('authentication', 'passwordreset'),
type: 'PUT',
@@ -43,7 +43,7 @@ export default Ember.Controller.extend(ValidationEngine, {
}
}).then(function (resp) {
self.toggleProperty('submitting');
- self.get('notifications').showSuccess(resp.passwordreset[0].message, true);
+ self.get('notifications').showAlert(resp.passwordreset[0].message, {type: 'warn', delayed: true});
self.get('session').authenticate('simple-auth-authenticator:oauth2-password-grant', {
identification: self.get('email'),
password: credentials.newPassword
@@ -52,9 +52,6 @@ export default Ember.Controller.extend(ValidationEngine, {
self.get('notifications').showAPIError(response);
self.toggleProperty('submitting');
});
- }).catch(function (error) {
- self.toggleProperty('submitting');
- self.get('notifications').showErrors(error);
});
}
}
diff --git a/core/client/app/controllers/settings/code-injection.js b/core/client/app/controllers/settings/code-injection.js
index 9cfa4307d0..c68c63ee95 100644
--- a/core/client/app/controllers/settings/code-injection.js
+++ b/core/client/app/controllers/settings/code-injection.js
@@ -8,13 +8,9 @@ export default Ember.Controller.extend({
var notifications = this.get('notifications');
return this.get('model').save().then(function (model) {
- notifications.closePassive();
- notifications.showSuccess('Settings successfully saved.');
-
return model;
- }).catch(function (errors) {
- notifications.closePassive();
- notifications.showErrors(errors);
+ }).catch(function (error) {
+ notifications.showAPIError(error);
});
}
}
diff --git a/core/client/app/controllers/settings/general.js b/core/client/app/controllers/settings/general.js
index 8a829c2185..68a67779a9 100644
--- a/core/client/app/controllers/settings/general.js
+++ b/core/client/app/controllers/settings/general.js
@@ -63,17 +63,22 @@ export default Ember.Controller.extend({
}),
actions: {
+ validate: function () {
+ this.get('model').validate(arguments);
+ },
+
save: function () {
var notifications = this.get('notifications'),
config = this.get('config');
return this.get('model').save().then(function (model) {
config.set('blogTitle', model.get('title'));
- notifications.showSuccess('Settings successfully saved.');
return model;
- }).catch(function (errors) {
- notifications.showErrors(errors);
+ }).catch(function (error) {
+ if (error) {
+ notifications.showAPIError(error);
+ }
});
},
diff --git a/core/client/app/controllers/settings/labs.js b/core/client/app/controllers/settings/labs.js
index 58c48dc9d7..c1e3629572 100644
--- a/core/client/app/controllers/settings/labs.js
+++ b/core/client/app/controllers/settings/labs.js
@@ -36,7 +36,7 @@ export default Ember.Controller.extend({
this.set('uploadButtonText', 'Importing');
this.set('importErrors', '');
- notifications.closePassive();
+ notifications.closeNotifications();
formData.append('importfile', file);
@@ -52,13 +52,14 @@ export default Ember.Controller.extend({
self.store.unloadAll();
// Reload currentUser and set session
self.set('session.user', self.store.find('user', currentUserId));
- notifications.showSuccess('Import successful.');
+ // TODO: keep as notification, add link to view content
+ notifications.showNotification('Import successful.');
}).catch(function (response) {
if (response && response.jqXHR && response.jqXHR.responseJSON && response.jqXHR.responseJSON.errors) {
self.set('importErrors', response.jqXHR.responseJSON.errors);
}
- notifications.showError('Import Failed');
+ notifications.showAlert('Import Failed', {type: 'error'});
}).finally(function () {
self.set('uploadButtonText', 'Import');
});
@@ -82,7 +83,7 @@ export default Ember.Controller.extend({
ajax(this.get('ghostPaths.url').api('mail', 'test'), {
type: 'POST'
}).then(function () {
- notifications.showSuccess('Check your email for the test message.');
+ notifications.showAlert('Check your email for the test message.', {type: 'info'});
}).catch(function (error) {
if (typeof error.jqXHR !== 'undefined') {
notifications.showAPIError(error);
diff --git a/core/client/app/controllers/settings/navigation.js b/core/client/app/controllers/settings/navigation.js
index bcb7e91ae7..1807d0da6a 100644
--- a/core/client/app/controllers/settings/navigation.js
+++ b/core/client/app/controllers/settings/navigation.js
@@ -108,7 +108,7 @@ export default Ember.Controller.extend({
// Don't save if there's a blank label.
if (navItems.find(function (item) {return !item.get('isComplete') && !item.get('last');})) {
- notifications.showErrors([message.htmlSafe()]);
+ notifications.showAlert(message.htmlSafe(), {type: 'error'});
return;
}
@@ -148,11 +148,9 @@ export default Ember.Controller.extend({
// we need to have navigationItems recomputed.
this.get('model').notifyPropertyChange('navigation');
- notifications.closePassive();
+ notifications.closeNotifications();
- this.get('model').save().then(function () {
- notifications.showSuccess('Navigation items saved.');
- }).catch(function (err) {
+ this.get('model').save().catch(function (err) {
notifications.showErrors(err);
});
}
diff --git a/core/client/app/controllers/settings/tags.js b/core/client/app/controllers/settings/tags.js
index fefe3ea4ad..e1b9e01ac5 100644
--- a/core/client/app/controllers/settings/tags.js
+++ b/core/client/app/controllers/settings/tags.js
@@ -59,7 +59,7 @@ export default Ember.Controller.extend(PaginationMixin, SettingsMenuMixin, {
activeTag.set(propKey, newValue);
- this.get('notifications').closePassive();
+ this.get('notifications').closeNotifications();
activeTag.save().catch(function (errors) {
self.showErrors(errors);
diff --git a/core/client/app/controllers/setup/three.js b/core/client/app/controllers/setup/three.js
index 29dd0df2a2..4c56a58d96 100644
--- a/core/client/app/controllers/setup/three.js
+++ b/core/client/app/controllers/setup/three.js
@@ -1,7 +1,9 @@
import Ember from 'ember';
+import DS from 'ember-data';
export default Ember.Controller.extend({
notifications: Ember.inject.service(),
+ errors: DS.Errors.create(),
users: '',
usersArray: Ember.computed('users', function () {
var users = this.get('users').split('\n').filter(function (email) {
@@ -62,10 +64,11 @@ export default Ember.Controller.extend({
var self = this,
validationErrors = this.get('validateUsers'),
users = this.get('usersArray'),
- errorMessages,
notifications = this.get('notifications'),
invitationsString;
+ this.get('errors').clear();
+
if (validationErrors === true && users.length > 0) {
this.get('authorRole').then(function (authorRole) {
Ember.RSVP.Promise.all(
@@ -104,30 +107,28 @@ export default Ember.Controller.extend({
if (erroredEmails.length > 0) {
message = 'Failed to send ' + erroredEmails.length + ' invitations: ';
message += erroredEmails.join(', ');
- notifications.showError(message, {delayed: successCount > 0});
+ notifications.showAlert(message, {type: 'error', delayed: successCount > 0});
}
if (successCount > 0) {
// pluralize
invitationsString = successCount > 1 ? 'invitations' : 'invitation';
- notifications.showSuccess(successCount + ' ' + invitationsString + ' sent!', {delayed: true});
+ notifications.showAlert(successCount + ' ' + invitationsString + ' sent!', {type: 'success', delayed: true});
self.transitionTo('posts.index');
}
});
});
} else if (users.length === 0) {
- notifications.showError('No users to invite.');
+ this.get('errors').add('users', 'No users to invite.');
} else {
- errorMessages = validationErrors.map(function (error) {
+ validationErrors.forEach(function (error) {
// Only one error type here so far, but one day the errors might be more detailed
switch (error.error) {
case 'email':
- return {message: error.user + ' is not a valid email.'};
+ self.get('errors').add('users', error.user + ' is not a valid email.');
}
});
-
- notifications.showErrors(errorMessages);
}
}
}
diff --git a/core/client/app/controllers/signin.js b/core/client/app/controllers/signin.js
index b578d975d6..76c352f915 100644
--- a/core/client/app/controllers/signin.js
+++ b/core/client/app/controllers/signin.js
@@ -3,13 +3,14 @@ import ValidationEngine from 'ghost/mixins/validation-engine';
import {request as ajax} from 'ic-ajax';
export default Ember.Controller.extend(ValidationEngine, {
- validationType: 'signin',
-
submitting: false,
ghostPaths: Ember.inject.service('ghost-paths'),
notifications: Ember.inject.service(),
+ // ValidationEngine settings
+ validationType: 'signin',
+
actions: {
authenticate: function () {
var model = this.get('model'),
@@ -30,12 +31,12 @@ export default Ember.Controller.extend(ValidationEngine, {
// browsers and password managers that don't send proper events on autofill
$('#login').find('input').trigger('change');
- this.validate({format: false}).then(function () {
- self.get('notifications').closePassive();
+ this.validate().then(function () {
+ self.get('notifications').closeNotifications();
self.send('authenticate');
- }).catch(function (errors) {
- if (errors) {
- self.get('notifications').showErrors(errors);
+ }).catch(function (error) {
+ if (error) {
+ self.get('notifications').showAPIError(error);
}
});
},
@@ -45,26 +46,24 @@ export default Ember.Controller.extend(ValidationEngine, {
notifications = this.get('notifications'),
self = this;
- if (!email) {
- return notifications.showError('Enter email address to reset password.');
- }
+ this.validate({property: 'identification'}).then(function () {
+ self.set('submitting', true);
- self.set('submitting', true);
-
- ajax({
- url: self.get('ghostPaths.url').api('authentication', 'passwordreset'),
- type: 'POST',
- data: {
- passwordreset: [{
- email: email
- }]
- }
- }).then(function () {
- self.set('submitting', false);
- notifications.showSuccess('Please check your email for instructions.');
- }).catch(function (resp) {
- self.set('submitting', false);
- notifications.showAPIError(resp, {defaultErrorText: 'There was a problem with the reset, please try again.'});
+ ajax({
+ url: self.get('ghostPaths.url').api('authentication', 'passwordreset'),
+ type: 'POST',
+ data: {
+ passwordreset: [{
+ email: email
+ }]
+ }
+ }).then(function () {
+ self.set('submitting', false);
+ notifications.showAlert('Please check your email for instructions.', {type: 'info'});
+ }).catch(function (resp) {
+ self.set('submitting', false);
+ notifications.showAPIError(resp, {defaultErrorText: 'There was a problem with the reset, please try again.'});
+ });
});
}
}
diff --git a/core/client/app/controllers/signup.js b/core/client/app/controllers/signup.js
index 77ea44a6be..b348109dda 100644
--- a/core/client/app/controllers/signup.js
+++ b/core/client/app/controllers/signup.js
@@ -18,10 +18,10 @@ export default Ember.Controller.extend(ValidationEngine, {
data = model.getProperties('name', 'email', 'password', 'token'),
notifications = this.get('notifications');
- notifications.closePassive();
+ notifications.closeNotifications();
- this.toggleProperty('submitting');
- this.validate({format: false}).then(function () {
+ this.validate().then(function () {
+ this.toggleProperty('submitting');
ajax({
url: self.get('ghostPaths.url').api('authentication', 'invitation'),
type: 'POST',
@@ -43,10 +43,9 @@ export default Ember.Controller.extend(ValidationEngine, {
self.toggleProperty('submitting');
notifications.showAPIError(resp);
});
- }).catch(function (errors) {
- self.toggleProperty('submitting');
- if (errors) {
- notifications.showErrors(errors);
+ }).catch(function (error) {
+ if (error) {
+ notifications.showAPIError(error);
}
});
}
diff --git a/core/client/app/controllers/team/user.js b/core/client/app/controllers/team/user.js
index 6ad63f3f0d..0598d2bcf4 100644
--- a/core/client/app/controllers/team/user.js
+++ b/core/client/app/controllers/team/user.js
@@ -2,8 +2,12 @@ import Ember from 'ember';
import SlugGenerator from 'ghost/models/slug-generator';
import isNumber from 'ghost/utils/isNumber';
import boundOneWay from 'ghost/utils/bound-one-way';
+import ValidationEngine from 'ghost/mixins/validation-engine';
+
+export default Ember.Controller.extend(ValidationEngine, {
+ // ValidationEngine settings
+ validationType: 'user',
-export default Ember.Controller.extend({
ghostPaths: Ember.inject.service('ghost-paths'),
notifications: Ember.inject.service(),
@@ -105,8 +109,6 @@ export default Ember.Controller.extend({
var currentPath,
newPath;
- self.get('notifications').showSuccess('Settings successfully saved.');
-
// If the user's slug has changed, change the URL and replace
// the history so refresh and back button still work
if (slugChanged) {
@@ -142,13 +144,14 @@ export default Ember.Controller.extend({
ne2Password: ''
});
- self.get('notifications').showSuccess('Password updated.');
+ self.get('notifications').showAlert('Password updated.', {type: 'success'});
return model;
}).catch(function (errors) {
self.get('notifications').showAPIError(errors);
});
} else {
+ // TODO: switch to in-line validation
self.get('notifications').showErrors(user.get('passwordValidationErrors'));
}
},
diff --git a/core/client/app/mixins/editor-base-controller.js b/core/client/app/mixins/editor-base-controller.js
index 718167d017..7fa44730f4 100644
--- a/core/client/app/mixins/editor-base-controller.js
+++ b/core/client/app/mixins/editor-base-controller.js
@@ -209,6 +209,7 @@ export default Ember.Mixin.create({
}
},
+ // TODO: Update for new notification click-action API
showSaveNotification: function (prevStatus, status, delay) {
var message = this.messageMap.success.post[prevStatus][status],
path = this.get('model.absoluteUrl'),
@@ -219,7 +220,7 @@ export default Ember.Mixin.create({
message += ` View ${type}`;
}
- notifications.showSuccess(message.htmlSafe(), {delayed: delay});
+ notifications.showNotification(message.htmlSafe(), {delayed: delay});
},
showErrorNotification: function (prevStatus, status, errors, delay) {
@@ -229,7 +230,7 @@ export default Ember.Mixin.create({
message += '
' + error;
- notifications.showError(message.htmlSafe(), {delayed: delay});
+ notifications.showAlert(message.htmlSafe(), {type: 'error', delayed: delay});
},
actions: {
@@ -263,7 +264,7 @@ export default Ember.Mixin.create({
this.set('timedSaveId', null);
}
- notifications.closePassive();
+ notifications.closeNotifications();
// Set the properties that are indirected
// set markdown equal to what's in the editor, minus the image markers.
diff --git a/core/client/app/mixins/pagination-controller.js b/core/client/app/mixins/pagination-controller.js
index 7957781561..4b417d9ee8 100644
--- a/core/client/app/mixins/pagination-controller.js
+++ b/core/client/app/mixins/pagination-controller.js
@@ -25,7 +25,7 @@ export default Ember.Mixin.create({
message += '.';
}
- this.get('notifications').showError(message);
+ this.get('notifications').showAlert(message, {type: 'error'});
},
actions: {
diff --git a/core/client/app/mixins/validation-engine.js b/core/client/app/mixins/validation-engine.js
index 3c4287588f..be1370cfc4 100644
--- a/core/client/app/mixins/validation-engine.js
+++ b/core/client/app/mixins/validation-engine.js
@@ -15,49 +15,6 @@ import TagSettingsValidator from 'ghost/validators/tag-settings';
// our extensions to the validator library
ValidatorExtensions.init();
-// This is here because it is used by some things that format errors from api responses
-// This function should be removed in the notifications refactor
-// format errors to be used in `notifications.showErrors`.
-// result is [{message: 'concatenated error messages'}]
-function formatErrors(errors, opts) {
- var message = 'There was an error';
-
- opts = opts || {};
-
- if (opts.wasSave && opts.validationType) {
- message += ' saving this ' + opts.validationType;
- }
-
- if (Ember.isArray(errors)) {
- // get the validator's error messages from the array.
- // normalize array members to map to strings.
- message = errors.map(function (error) {
- var errorMessage;
- if (typeof error === 'string') {
- errorMessage = error;
- } else {
- errorMessage = error.message;
- }
-
- return Ember.Handlebars.Utils.escapeExpression(errorMessage);
- }).join('
').htmlSafe();
- } else if (errors instanceof Error) {
- message += errors.message || '.';
- } else if (typeof errors === 'object') {
- // Get messages from server response
- message += ': ' + getRequestErrorMessage(errors, true);
- } else if (typeof errors === 'string') {
- message += ': ' + errors;
- } else {
- message += '.';
- }
-
- // set format for notifications.showErrors
- message = [{message: message}];
-
- return message;
-}
-
/**
* The class that gets this mixin will receive these properties and functions.
* It will be able to validate any properties on itself (or the model it passes to validate())
@@ -163,15 +120,10 @@ export default Ember.Mixin.create({
return this.validate(options).then(function () {
return _super.call(self, options);
}).catch(function (result) {
- // server save failed - validate() would have given back an array
- if (!Ember.isArray(result)) {
- if (options.format !== false) {
- // concatenate all errors into an array with a single object: [{message: 'concatted message'}]
- result = formatErrors(result, options);
- } else {
- // return the array of errors from the server
- result = getRequestErrorMessage(result);
- }
+ // server save failed or validator type doesn't exist
+ if (result && !Ember.isArray(result)) {
+ // return the array of errors from the server
+ result = getRequestErrorMessage(result);
}
return Ember.RSVP.reject(result);
diff --git a/core/client/app/models/notification.js b/core/client/app/models/notification.js
index 98cdc175e1..93e8bb506e 100644
--- a/core/client/app/models/notification.js
+++ b/core/client/app/models/notification.js
@@ -1,7 +1,6 @@
import DS from 'ember-data';
var Notification = DS.Model.extend({
dismissible: DS.attr('boolean'),
- location: DS.attr('string'),
status: DS.attr('string'),
type: DS.attr('string'),
message: DS.attr('string')
diff --git a/core/client/app/router.js b/core/client/app/router.js
index d5253a5771..de02a58899 100644
--- a/core/client/app/router.js
+++ b/core/client/app/router.js
@@ -11,7 +11,7 @@ var Router = Ember.Router.extend({
clearNotifications: Ember.on('didTransition', function () {
var notifications = this.get('notifications');
- notifications.closePassive();
+ notifications.closeNotifications();
notifications.displayDelayed();
})
});
diff --git a/core/client/app/routes/application.js b/core/client/app/routes/application.js
index db4e3f0ede..7a12f7075e 100644
--- a/core/client/app/routes/application.js
+++ b/core/client/app/routes/application.js
@@ -66,7 +66,7 @@ export default Ember.Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
this.get('notifications').showErrors(error.errors);
} else {
// connection errors don't return proper status message, only req.body
- this.get('notifications').showError('There was a problem on the server.');
+ this.get('notifications').showAlert('There was a problem on the server.', {type: 'error'});
}
},
@@ -91,7 +91,7 @@ export default Ember.Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
},
sessionInvalidationFailed: function (error) {
- this.get('notifications').showError(error.message);
+ this.get('notifications').showAlert(error.message, {type: 'error'});
},
openModal: function (modalName, model, type) {
@@ -152,19 +152,6 @@ export default Ember.Route.extend(ApplicationRouteMixin, ShortcutsRoute, {
}
},
- handleErrors: function (errors) {
- var notifications = this.get('notifications');
-
- notifications.clear();
- errors.forEach(function (errorObj) {
- notifications.showError(errorObj.message || errorObj);
-
- if (errorObj.hasOwnProperty('el')) {
- errorObj.el.addClass('input-error');
- }
- });
- },
-
// noop default for unhandled save (used from shortcuts)
save: Ember.K
}
diff --git a/core/client/app/routes/reset.js b/core/client/app/routes/reset.js
index 9497f6d121..e2e98a6d12 100644
--- a/core/client/app/routes/reset.js
+++ b/core/client/app/routes/reset.js
@@ -9,7 +9,7 @@ export default Ember.Route.extend(styleBody, {
beforeModel: function () {
if (this.get('session').isAuthenticated) {
- this.get('notifications').showWarn('You can\'t reset your password while you\'re signed in.', {delayed: true});
+ this.get('notifications').showAlert('You can\'t reset your password while you\'re signed in.', {type: 'warn', delayed: true});
this.transitionTo(Configuration.routeAfterAuthentication);
}
},
diff --git a/core/client/app/routes/signup.js b/core/client/app/routes/signup.js
index 7527de9916..b7a900c746 100644
--- a/core/client/app/routes/signup.js
+++ b/core/client/app/routes/signup.js
@@ -12,7 +12,7 @@ export default Ember.Route.extend(styleBody, {
beforeModel: function () {
if (this.get('session').isAuthenticated) {
- this.get('notifications').showWarn('You need to sign out to register as a new user.', {delayed: true});
+ this.get('notifications').showAlert('You need to sign out to register as a new user.', {type: 'warn', delayed: true});
this.transitionTo(Configuration.routeAfterAuthentication);
}
},
@@ -26,7 +26,7 @@ export default Ember.Route.extend(styleBody, {
return new Ember.RSVP.Promise(function (resolve) {
if (!re.test(params.token)) {
- self.get('notifications').showError('Invalid token.', {delayed: true});
+ self.get('notifications').showAlert('Invalid token.', {type: 'error', delayed: true});
return resolve(self.transitionTo('signin'));
}
@@ -47,7 +47,7 @@ export default Ember.Route.extend(styleBody, {
}
}).then(function (response) {
if (response && response.invitation && response.invitation[0].valid === false) {
- self.get('notifications').showError('The invitation does not exist or is no longer valid.', {delayed: true});
+ self.get('notifications').showAlert('The invitation does not exist or is no longer valid.', {type: 'warn', delayed: true});
return resolve(self.transitionTo('signin'));
}
diff --git a/core/client/app/services/notifications.js b/core/client/app/services/notifications.js
index 4e4c10d6b4..f178ee36ca 100644
--- a/core/client/app/services/notifications.js
+++ b/core/client/app/services/notifications.js
@@ -1,138 +1,94 @@
import Ember from 'ember';
-import Notification from 'ghost/models/notification';
export default Ember.Service.extend({
delayedNotifications: Ember.A(),
content: Ember.A(),
- timeout: 3000,
- pushObject: function (object) {
- // object can be either a DS.Model or a plain JS object, so when working with
- // it, we need to handle both cases.
+ alerts: Ember.computed.filter('content', function (notification) {
+ var status = Ember.get(notification, 'status');
+ return status === 'alert';
+ }),
- // make sure notifications have all the necessary properties set.
- if (typeof object.toJSON === 'function') {
- // working with a DS.Model
-
- if (object.get('location') === '') {
- object.set('location', 'bottom');
- }
- } else {
- if (!object.location) {
- object.location = 'bottom';
- }
- }
-
- this._super(object);
- },
+ notifications: Ember.computed.filter('content', function (notification) {
+ var status = Ember.get(notification, 'status');
+ return status === 'notification';
+ }),
handleNotification: function (message, delayed) {
- if (typeof message.toJSON === 'function') {
- // If this is a persistent message from the server, treat it as html safe
- if (message.get('status') === 'persistent') {
- message.set('message', message.get('message').htmlSafe());
- }
+ // 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 (!message.get('status')) {
- message.set('status', 'passive');
- }
- } else {
- if (!message.status) {
- message.status = 'passive';
- }
+ if (!Ember.get(message, 'status')) {
+ Ember.set(message, 'status', 'notification');
}
if (!delayed) {
this.get('content').pushObject(message);
} else {
- this.delayedNotifications.pushObject(message);
+ this.get('delayedNotifications').pushObject(message);
}
},
- showError: function (message, options) {
+ showAlert: function (message, options) {
options = options || {};
- if (!options.doNotClosePassive) {
- this.closePassive();
- }
-
this.handleNotification({
- type: 'error',
- message: message
+ message: message,
+ status: 'alert',
+ type: options.type
}, options.delayed);
},
+ showNotification: function (message, options) {
+ options = options || {};
+
+ if (!options.doNotCloseNotifications) {
+ this.closeNotifications();
+ }
+
+ this.handleNotification({
+ message: message,
+ status: 'notification',
+ type: options.type
+ }, options.delayed);
+ },
+
+ // TODO: review whether this can be removed once no longer used by validations
showErrors: function (errors, options) {
options = options || {};
- if (!options.doNotClosePassive) {
- this.closePassive();
+ if (!options.doNotCloseNotifications) {
+ this.closeNotifications();
}
for (var i = 0; i < errors.length; i += 1) {
- this.showError(errors[i].message || errors[i], {doNotClosePassive: true});
+ this.showNotification(errors[i].message || errors[i], {type: 'error', doNotCloseNotifications: true});
}
},
showAPIError: function (resp, options) {
options = options || {};
+ options.type = options.type || 'error';
- if (!options.doNotClosePassive) {
- this.closePassive();
+ 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.showError(resp.jqXHR.responseJSON.error, options);
+ 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.showError(resp.jqXHR.responseJSON.message, options);
+ this.showAlert(resp.jqXHR.responseJSON.message, options);
} else {
- this.showError(options.defaultErrorText, {doNotClosePassive: true});
+ this.showAlert(options.defaultErrorText, {type: options.type, doNotCloseNotifications: true});
}
},
- showInfo: function (message, options) {
- options = options || {};
-
- if (!options.doNotClosePassive) {
- this.closePassive();
- }
-
- this.handleNotification({
- type: 'info',
- message: message
- }, options.delayed);
- },
-
- showSuccess: function (message, options) {
- options = options || {};
-
- if (!options.doNotClosePassive) {
- this.closePassive();
- }
-
- this.handleNotification({
- type: 'success',
- message: message
- }, options.delayed);
- },
-
- showWarn: function (message, options) {
- options = options || {};
-
- if (!options.doNotClosePassive) {
- this.closePassive();
- }
-
- this.handleNotification({
- type: 'warn',
- message: message
- }, options.delayed);
- },
-
displayDelayed: function () {
var self = this;
@@ -145,7 +101,7 @@ export default Ember.Service.extend({
closeNotification: function (notification) {
var content = this.get('content');
- if (notification instanceof Notification) {
+ if (typeof notification.toJSON === 'function') {
notification.deleteRecord();
notification.save().finally(function () {
content.removeObject(notification);
@@ -155,12 +111,8 @@ export default Ember.Service.extend({
}
},
- closePassive: function () {
- this.set('content', this.get('content').rejectBy('status', 'passive'));
- },
-
- closePersistent: function () {
- this.set('content', this.get('content').rejectBy('status', 'persistent'));
+ closeNotifications: function () {
+ this.set('content', this.get('content').rejectBy('status', 'notification'));
},
closeAll: function () {
diff --git a/core/client/app/styles/components/notifications.css b/core/client/app/styles/components/notifications.css
index c06884a672..8895860afa 100644
--- a/core/client/app/styles/components/notifications.css
+++ b/core/client/app/styles/components/notifications.css
@@ -63,6 +63,16 @@
color: var(--red);
}
+.gh-notification-passive {
+ animation: fade-out;
+ animation-delay: 5s;
+ animation-iteration-count: 1;
+}
+
+.gh-notification-passive:hover {
+ animation: fade-in;
+}
+
/* Red notification
/* ---------------------------------------------------------- */
diff --git a/core/client/app/styles/layouts/flow.css b/core/client/app/styles/layouts/flow.css
index fd880b17ba..9e019741bb 100644
--- a/core/client/app/styles/layouts/flow.css
+++ b/core/client/app/styles/layouts/flow.css
@@ -377,6 +377,7 @@
}
.gh-flow-content .gh-flow-invite {
+ position: relative;
margin: 0 auto;
max-width: 400px;
width: 100%;
diff --git a/core/client/app/templates/reset.hbs b/core/client/app/templates/reset.hbs
index 3e98a6ca13..b630b5e532 100644
--- a/core/client/app/templates/reset.hbs
+++ b/core/client/app/templates/reset.hbs
@@ -2,12 +2,21 @@