diff --git a/.gitignore b/.gitignore index e07bc7a8ff..ba4e20a0b4 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,7 @@ CHANGELOG.md config.js /core/client/config.js +!/core/client/app/services/config.js # Built asset files /core/built diff --git a/core/client/app/components/gh-alert.js b/core/client/app/components/gh-alert.js index b8e35b7d7c..1ac91e4b39 100644 --- a/core/client/app/components/gh-alert.js +++ b/core/client/app/components/gh-alert.js @@ -1,10 +1,12 @@ import Ember from 'ember'; -var AlertComponent = Ember.Component.extend({ +export default Ember.Component.extend({ tagName: 'article', classNames: ['gh-alert', 'gh-alert-blue'], classNameBindings: ['typeClass'], + notifications: Ember.inject.service(), + typeClass: Ember.computed(function () { var classes = '', message = this.get('message'), @@ -31,10 +33,7 @@ var AlertComponent = Ember.Component.extend({ actions: { closeNotification: function () { - var self = this; - self.notifications.closeNotification(self.get('message')); + this.get('notifications').closeNotification(this.get('message')); } } }); - -export default AlertComponent; diff --git a/core/client/app/components/gh-blog-url.js b/core/client/app/components/gh-blog-url.js index 381911e341..21df2de71c 100644 --- a/core/client/app/components/gh-blog-url.js +++ b/core/client/app/components/gh-blog-url.js @@ -1,7 +1,8 @@ import Ember from 'ember'; var blogUrl = Ember.Component.extend({ - tagName: '' + tagName: '', + config: Ember.inject.service() }); export default blogUrl; diff --git a/core/client/app/components/gh-dropdown-button.js b/core/client/app/components/gh-dropdown-button.js index 8d47b17d4a..d0f982e099 100644 --- a/core/client/app/components/gh-dropdown-button.js +++ b/core/client/app/components/gh-dropdown-button.js @@ -1,7 +1,7 @@ import Ember from 'ember'; import DropdownMixin from 'ghost/mixins/dropdown-mixin'; -var DropdownButton = Ember.Component.extend(DropdownMixin, { +export default Ember.Component.extend(DropdownMixin, { tagName: 'button', attributeBindings: 'role', role: 'button', @@ -9,11 +9,11 @@ var DropdownButton = Ember.Component.extend(DropdownMixin, { // matches with the dropdown this button toggles dropdownName: null, + dropdown: Ember.inject.service(), + // Notify dropdown service this dropdown should be toggled click: function (event) { this._super(event); this.get('dropdown').toggleDropdown(this.get('dropdownName'), this); } }); - -export default DropdownButton; diff --git a/core/client/app/components/gh-dropdown.js b/core/client/app/components/gh-dropdown.js index 707bad0b9a..810eca5883 100644 --- a/core/client/app/components/gh-dropdown.js +++ b/core/client/app/components/gh-dropdown.js @@ -1,8 +1,10 @@ import Ember from 'ember'; import DropdownMixin from 'ghost/mixins/dropdown-mixin'; -var GhostDropdown = Ember.Component.extend(DropdownMixin, { +export default Ember.Component.extend(DropdownMixin, { classNames: 'ghost-dropdown', + classNameBindings: ['fadeIn:fade-in-scale:fade-out', 'isOpen:open:closed'], + name: null, closeOnClick: false, @@ -17,7 +19,7 @@ var GhostDropdown = Ember.Component.extend(DropdownMixin, { return this.get('isOpen') && !this.get('closing'); }), - classNameBindings: ['fadeIn:fade-in-scale:fade-out', 'isOpen:open:closed'], + dropdown: Ember.inject.service(), open: function () { this.set('isOpen', true); @@ -88,5 +90,3 @@ var GhostDropdown = Ember.Component.extend(DropdownMixin, { dropdownService.off('toggle', this, this.toggle); } }); - -export default GhostDropdown; diff --git a/core/client/app/components/gh-ed-preview.js b/core/client/app/components/gh-ed-preview.js index def2ab1f07..437806e18e 100644 --- a/core/client/app/components/gh-ed-preview.js +++ b/core/client/app/components/gh-ed-preview.js @@ -2,6 +2,8 @@ import Ember from 'ember'; import uploader from 'ghost/assets/lib/uploader'; var Preview = Ember.Component.extend({ + config: Ember.inject.service(), + didInsertElement: function () { this.set('scrollWrapper', this.$().closest('.entry-preview-content')); Ember.run.scheduleOnce('afterRender', this, this.dropzoneHandler); diff --git a/core/client/app/components/gh-nav-menu.js b/core/client/app/components/gh-nav-menu.js index b9dd569880..666ea88e08 100644 --- a/core/client/app/components/gh-nav-menu.js +++ b/core/client/app/components/gh-nav-menu.js @@ -5,6 +5,8 @@ export default Ember.Component.extend({ classNames: ['gh-nav'], classNameBindings: ['open'], + config: Ember.inject.service(), + open: false, autoNav: null, diff --git a/core/client/app/components/gh-notification.js b/core/client/app/components/gh-notification.js index 3e69ceca99..60f67744e9 100644 --- a/core/client/app/components/gh-notification.js +++ b/core/client/app/components/gh-notification.js @@ -1,10 +1,14 @@ import Ember from 'ember'; -var NotificationComponent = Ember.Component.extend({ +export default Ember.Component.extend({ tagName: 'article', classNames: ['gh-notification', 'gh-notification-green'], classNameBindings: ['typeClass'], + message: null, + + notifications: Ember.inject.service(), + typeClass: Ember.computed(function () { var classes = '', message = this.get('message'), @@ -34,17 +38,14 @@ var NotificationComponent = Ember.Component.extend({ self.$().on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', function (event) { if (event.originalEvent.animationName === 'fade-out') { - self.notifications.removeObject(self.get('message')); + self.get('notifications').removeObject(self.get('message')); } }); }, actions: { closeNotification: function () { - var self = this; - self.notifications.closeNotification(self.get('message')); + this.get('notifications').closeNotification(this.get('message')); } } }); - -export default NotificationComponent; diff --git a/core/client/app/components/gh-notifications.js b/core/client/app/components/gh-notifications.js index ec124fa8e3..03ce6e859f 100644 --- a/core/client/app/components/gh-notifications.js +++ b/core/client/app/components/gh-notifications.js @@ -1,14 +1,15 @@ import Ember from 'ember'; -var NotificationsComponent = Ember.Component.extend({ + +export default Ember.Component.extend({ tagName: 'aside', classNames: 'gh-notifications', - messages: Ember.computed.filter('notifications', function (notification) { + 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'; }) }); - -export default NotificationsComponent; diff --git a/core/client/app/components/gh-popover-button.js b/core/client/app/components/gh-popover-button.js index 7484379212..0b9fb4baf2 100644 --- a/core/client/app/components/gh-popover-button.js +++ b/core/client/app/components/gh-popover-button.js @@ -1,8 +1,10 @@ import Ember from 'ember'; import DropdownButton from 'ghost/components/gh-dropdown-button'; -var PopoverButton = DropdownButton.extend({ - click: Ember.K, // We don't want clicks on popovers, but dropdowns have them. So `K`ill them here. +export default DropdownButton.extend({ + dropdown: Ember.inject.service(), + + click: Ember.K, mouseEnter: function (event) { this._super(event); @@ -14,5 +16,3 @@ var PopoverButton = DropdownButton.extend({ this.get('dropdown').toggleDropdown(this.get('popoverName'), this); } }); - -export default PopoverButton; diff --git a/core/client/app/components/gh-popover.js b/core/client/app/components/gh-popover.js index 5d819d62ae..70c2e8f9e8 100644 --- a/core/client/app/components/gh-popover.js +++ b/core/client/app/components/gh-popover.js @@ -1,7 +1,7 @@ +import Ember from 'ember'; import GhostDropdown from 'ghost/components/gh-dropdown'; -var GhostPopover = GhostDropdown.extend({ - classNames: 'ghost-popover' +export default GhostDropdown.extend({ + classNames: 'ghost-popover', + dropdown: Ember.inject.service() }); - -export default GhostPopover; diff --git a/core/client/app/components/gh-upload-modal.js b/core/client/app/components/gh-upload-modal.js index e417a3a199..4b54a344d4 100644 --- a/core/client/app/components/gh-upload-modal.js +++ b/core/client/app/components/gh-upload-modal.js @@ -6,6 +6,8 @@ import cajaSanitizers from 'ghost/utils/caja-sanitizers'; var UploadModal = ModalDialog.extend({ layoutName: 'components/gh-modal-dialog', + config: Ember.inject.service(), + didInsertElement: function () { this._super(); upload.call(this.$('.js-drop-zone'), {fileStorage: this.get('config.fileStorage')}); diff --git a/core/client/app/components/gh-uploader.js b/core/client/app/components/gh-uploader.js index 75dc86cb68..e136a4efdf 100644 --- a/core/client/app/components/gh-uploader.js +++ b/core/client/app/components/gh-uploader.js @@ -4,6 +4,8 @@ import uploader from 'ghost/assets/lib/uploader'; var PostImageUploader = Ember.Component.extend({ classNames: ['image-uploader', 'js-post-image-upload'], + config: Ember.inject.service(), + imageSource: Ember.computed('image', function () { return this.get('image') || ''; }), diff --git a/core/client/app/components/gh-url-preview.js b/core/client/app/components/gh-url-preview.js index cc46105281..4af38ec85e 100644 --- a/core/client/app/components/gh-url-preview.js +++ b/core/client/app/components/gh-url-preview.js @@ -8,9 +8,11 @@ var urlPreview = Ember.Component.extend({ prefix: null, slug: null, + config: Ember.inject.service(), + url: Ember.computed('slug', function () { // Get the blog URL and strip the scheme - var blogUrl = this.get('config').blogUrl, + var blogUrl = this.get('config.blogUrl'), noSchemeBlogUrl = blogUrl.substr(blogUrl.indexOf('://') + 3), // Remove `http[s]://` // Get the prefix and slug values diff --git a/core/client/app/controllers/application.js b/core/client/app/controllers/application.js index 51cbaecdb9..8bfcd147cb 100644 --- a/core/client/app/controllers/application.js +++ b/core/client/app/controllers/application.js @@ -1,6 +1,8 @@ import Ember from 'ember'; export default Ember.Controller.extend({ + dropdown: Ember.inject.service(), + // jscs: disable signedOut: Ember.computed.match('currentPath', /(signin|signup|setup|reset)/), // jscs: enable diff --git a/core/client/app/controllers/modals/copy-html.js b/core/client/app/controllers/modals/copy-html.js index 161e65baf1..c4550ab9a4 100644 --- a/core/client/app/controllers/modals/copy-html.js +++ b/core/client/app/controllers/modals/copy-html.js @@ -1,8 +1,5 @@ import Ember from 'ember'; -var CopyHTMLController = Ember.Controller.extend({ +export default Ember.Controller.extend({ generatedHTML: Ember.computed.alias('model.generatedHTML') - }); - -export default CopyHTMLController; diff --git a/core/client/app/controllers/modals/delete-all.js b/core/client/app/controllers/modals/delete-all.js index b7f7307386..30c10f3179 100644 --- a/core/client/app/controllers/modals/delete-all.js +++ b/core/client/app/controllers/modals/delete-all.js @@ -2,6 +2,9 @@ import Ember from 'ember'; import {request as ajax} from 'ic-ajax'; export default Ember.Controller.extend({ + ghostPaths: Ember.inject.service('ghost-paths'), + notifications: Ember.inject.service(), + actions: { confirmAccept: function () { var self = this; @@ -9,11 +12,11 @@ export default Ember.Controller.extend({ ajax(this.get('ghostPaths.url').api('db'), { type: 'DELETE' }).then(function () { - self.notifications.showSuccess('All content deleted from database.'); + self.get('notifications').showSuccess('All content deleted from database.'); self.store.unloadAll('post'); self.store.unloadAll('tag'); }).catch(function (response) { - self.notifications.showErrors(response); + self.get('notifications').showErrors(response); }); }, diff --git a/core/client/app/controllers/modals/delete-post.js b/core/client/app/controllers/modals/delete-post.js index 56b08db5d4..1f334c4437 100644 --- a/core/client/app/controllers/modals/delete-post.js +++ b/core/client/app/controllers/modals/delete-post.js @@ -1,5 +1,9 @@ import Ember from 'ember'; -var DeletePostController = Ember.Controller.extend({ + +export default Ember.Controller.extend({ + dropdown: Ember.inject.service(), + notifications: Ember.inject.service(), + actions: { confirmAccept: function () { var self = this, @@ -11,9 +15,9 @@ var DeletePostController = Ember.Controller.extend({ model.destroyRecord().then(function () { self.get('dropdown').closeDropdowns(); self.transitionToRoute('posts.index'); - self.notifications.showSuccess('Your post has been deleted.', {delayed: true}); + self.get('notifications').showSuccess('Your post has been deleted.', {delayed: true}); }, function () { - self.notifications.showError('Your post could not be deleted. Please try again.'); + self.get('notifications').showError('Your post could not be deleted. Please try again.'); }); }, @@ -33,5 +37,3 @@ var DeletePostController = Ember.Controller.extend({ } } }); - -export default DeletePostController; diff --git a/core/client/app/controllers/modals/delete-tag.js b/core/client/app/controllers/modals/delete-tag.js index 0657743bf8..0cfabca999 100644 --- a/core/client/app/controllers/modals/delete-tag.js +++ b/core/client/app/controllers/modals/delete-tag.js @@ -1,5 +1,8 @@ import Ember from 'ember'; -var DeleteTagController = Ember.Controller.extend({ + +export default Ember.Controller.extend({ + notifications: Ember.inject.service(), + postInflection: Ember.computed('model.post_count', function () { return this.get('model.post_count') > 1 ? 'posts' : 'post'; }), @@ -13,9 +16,9 @@ var DeleteTagController = Ember.Controller.extend({ this.send('closeSettingsMenu'); tag.destroyRecord().then(function () { - self.notifications.showSuccess('Deleted ' + name); + self.get('notifications').showSuccess('Deleted ' + name); }).catch(function (error) { - self.notifications.showAPIError(error); + self.get('notifications').showAPIError(error); }); }, @@ -35,5 +38,3 @@ var DeleteTagController = Ember.Controller.extend({ } } }); - -export default DeleteTagController; diff --git a/core/client/app/controllers/modals/delete-user.js b/core/client/app/controllers/modals/delete-user.js index 2f8e39bda3..9dc2a1c6a4 100644 --- a/core/client/app/controllers/modals/delete-user.js +++ b/core/client/app/controllers/modals/delete-user.js @@ -1,5 +1,8 @@ import Ember from 'ember'; -var DeleteUserController = Ember.Controller.extend({ + +export default Ember.Controller.extend({ + notifications: Ember.inject.service(), + userPostCount: Ember.computed('model.id', function () { var promise, query = { @@ -28,9 +31,9 @@ var DeleteUserController = Ember.Controller.extend({ user.destroyRecord().then(function () { self.store.unloadAll('post'); self.transitionToRoute('settings.users'); - self.notifications.showSuccess('The user has been deleted.', {delayed: true}); + self.get('notifications').showSuccess('The user has been deleted.', {delayed: true}); }, function () { - self.notifications.showError('The user could not be deleted. Please try again.'); + self.get('notifications').showError('The user could not be deleted. Please try again.'); }); }, @@ -50,5 +53,3 @@ var DeleteUserController = Ember.Controller.extend({ } } }); - -export default DeleteUserController; diff --git a/core/client/app/controllers/modals/invite-new-user.js b/core/client/app/controllers/modals/invite-new-user.js index dd54b976eb..707da53f2c 100644 --- a/core/client/app/controllers/modals/invite-new-user.js +++ b/core/client/app/controllers/modals/invite-new-user.js @@ -1,5 +1,8 @@ import Ember from 'ember'; -var InviteNewUserController = Ember.Controller.extend({ + +export default Ember.Controller.extend({ + notifications: Ember.inject.service(), + // Used to set the initial value for the dropdown authorRole: Ember.computed(function () { var self = this; @@ -45,9 +48,9 @@ var InviteNewUserController = Ember.Controller.extend({ if (invitedUser) { if (invitedUser.get('status') === 'invited' || invitedUser.get('status') === 'invited-pending') { - self.notifications.showWarn('A user with that email address was already invited.'); + self.get('notifications').showWarn('A user with that email address was already invited.'); } else { - self.notifications.showWarn('A user with that email address already exists.'); + self.get('notifications').showWarn('A user with that email address already exists.'); } } else { newUser = self.store.createRecord('user', { @@ -62,13 +65,13 @@ var InviteNewUserController = 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.notifications.showWarn('Invitation email was not sent. Please try resending.'); + self.get('notifications').showWarn('Invitation email was not sent. Please try resending.'); } else { - self.notifications.showSuccess(notificationText); + self.get('notifications').showSuccess(notificationText); } }).catch(function (errors) { newUser.deleteRecord(); - self.notifications.showErrors(errors); + self.get('notifications').showErrors(errors); }); } }); @@ -79,5 +82,3 @@ var InviteNewUserController = Ember.Controller.extend({ } } }); - -export default InviteNewUserController; diff --git a/core/client/app/controllers/modals/leave-editor.js b/core/client/app/controllers/modals/leave-editor.js index 0f01f14d8c..d455ead29d 100644 --- a/core/client/app/controllers/modals/leave-editor.js +++ b/core/client/app/controllers/modals/leave-editor.js @@ -1,5 +1,8 @@ import Ember from 'ember'; -var LeaveEditorController = Ember.Controller.extend({ + +export default Ember.Controller.extend({ + notifications: Ember.inject.service(), + args: Ember.computed.alias('model'), actions: { @@ -16,7 +19,7 @@ var LeaveEditorController = Ember.Controller.extend({ } if (!transition || !editorController) { - this.notifications.showError('Sorry, there was an error in the application. Please let the Ghost team know what happened.'); + this.get('notifications').showError('Sorry, there was an error in the application. Please let the Ghost team know what happened.'); return true; } @@ -56,5 +59,3 @@ var LeaveEditorController = Ember.Controller.extend({ } } }); - -export default LeaveEditorController; diff --git a/core/client/app/controllers/modals/signin.js b/core/client/app/controllers/modals/signin.js index 90d705a691..89a438ef7f 100644 --- a/core/client/app/controllers/modals/signin.js +++ b/core/client/app/controllers/modals/signin.js @@ -2,17 +2,18 @@ import Ember from 'ember'; import ValidationEngine from 'ghost/mixins/validation-engine'; export default Ember.Controller.extend(ValidationEngine, { - needs: 'application', - validationType: 'signin', + application: Ember.inject.controller(), + notifications: Ember.inject.service(), + identification: Ember.computed('session.user.email', function () { return this.get('session.user.email'); }), actions: { authenticate: function () { - var appController = this.get('controllers.application'), + var appController = this.get('application'), authStrategy = 'simple-auth-authenticator:oauth2-password-grant', data = this.getProperties('identification', 'password'), self = this; @@ -21,7 +22,7 @@ export default Ember.Controller.extend(ValidationEngine, { this.get('session').authenticate(authStrategy, data).then(function () { self.send('closeModal'); - self.notifications.showSuccess('Login successful.'); + self.get('notifications').showSuccess('Login successful.'); self.set('password', ''); }).catch(function () { // if authentication fails a rejected promise will be returned. @@ -40,10 +41,10 @@ export default Ember.Controller.extend(ValidationEngine, { $('#login').find('input').trigger('change'); this.validate({format: false}).then(function () { - self.notifications.closePassive(); + self.get('notifications').closePassive(); self.send('authenticate'); }).catch(function (errors) { - self.notifications.showErrors(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 30075c91b2..f721713962 100644 --- a/core/client/app/controllers/modals/transfer-owner.js +++ b/core/client/app/controllers/modals/transfer-owner.js @@ -2,6 +2,10 @@ import Ember from 'ember'; import {request as ajax} from 'ic-ajax'; export default Ember.Controller.extend({ + dropdown: Ember.inject.service(), + ghostPaths: Ember.inject.service('ghost-paths'), + notifications: Ember.inject.service(), + actions: { confirmAccept: function () { var user = this.get('model'), @@ -29,9 +33,9 @@ export default Ember.Controller.extend({ }); } - self.notifications.showSuccess('Ownership successfully transferred to ' + user.get('name')); + self.get('notifications').showSuccess('Ownership successfully transferred to ' + user.get('name')); }).catch(function (error) { - self.notifications.showAPIError(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 c2cdf42b22..f186205826 100644 --- a/core/client/app/controllers/modals/upload.js +++ b/core/client/app/controllers/modals/upload.js @@ -1,16 +1,20 @@ import Ember from 'ember'; -var UploadController = Ember.Controller.extend({ +export default Ember.Controller.extend({ + notifications: Ember.inject.service(), + acceptEncoding: 'image/*', + actions: { confirmAccept: function () { - var self = this; + var notifications = this.get('notifications'); this.get('model').save().then(function (model) { - self.notifications.showSuccess('Saved'); + notifications.showSuccess('Saved'); + return model; }).catch(function (err) { - self.notifications.showErrors(err); + notifications.showErrors(err); }); }, @@ -19,5 +23,3 @@ var UploadController = Ember.Controller.extend({ } } }); - -export default UploadController; diff --git a/core/client/app/controllers/post-settings-menu.js b/core/client/app/controllers/post-settings-menu.js index b66a68faf9..b95ddfb4b6 100644 --- a/core/client/app/controllers/post-settings-menu.js +++ b/core/client/app/controllers/post-settings-menu.js @@ -1,18 +1,22 @@ -import Ember from 'ember'; /* global moment */ + +import Ember from 'ember'; import {parseDateString, formatDate} from 'ghost/utils/date-formatting'; import SettingsMenuMixin from 'ghost/mixins/settings-menu-controller'; import SlugGenerator from 'ghost/models/slug-generator'; import boundOneWay from 'ghost/utils/bound-one-way'; import isNumber from 'ghost/utils/isNumber'; -var PostSettingsMenuController = Ember.Controller.extend(SettingsMenuMixin, { +export default Ember.Controller.extend(SettingsMenuMixin, { debounceId: null, lastPromise: null, selectedAuthor: null, uploaderReference: null, application: Ember.inject.controller(), + config: Ember.inject.service(), + ghostPaths: Ember.inject.service('ghost-paths'), + notifications: Ember.inject.service(), initializeSelectedAuthor: function () { var self = this; @@ -212,11 +216,11 @@ var PostSettingsMenuController = Ember.Controller.extend(SettingsMenuMixin, { showErrors: function (errors) { errors = Ember.isArray(errors) ? errors : [errors]; - this.notifications.showErrors(errors); + this.get('notifications').showErrors(errors); }, showSuccess: function (message) { - this.notifications.showSuccess(message); + this.get('notifications').showSuccess(message); }, actions: { @@ -465,5 +469,3 @@ var PostSettingsMenuController = Ember.Controller.extend(SettingsMenuMixin, { } } }); - -export default PostSettingsMenuController; diff --git a/core/client/app/controllers/posts/post.js b/core/client/app/controllers/posts/post.js index 4ada4f78b2..da909b5318 100644 --- a/core/client/app/controllers/posts/post.js +++ b/core/client/app/controllers/posts/post.js @@ -1,8 +1,13 @@ import Ember from 'ember'; -var PostController = Ember.Controller.extend({ - isPublished: Ember.computed.equal('model.status', 'published'), + +export default Ember.Controller.extend({ classNameBindings: ['model.featured'], + ghostPaths: Ember.inject.service('ghost-paths'), + notifications: Ember.inject.service(), + + isPublished: Ember.computed.equal('model.status', 'published'), + authorName: Ember.computed('model.author.name', 'model.author.email', function () { return this.get('model.author.name') || this.get('model.author.email'); }), @@ -17,17 +22,16 @@ var PostController = Ember.Controller.extend({ actions: { toggleFeatured: function () { - var self = this; + var notifications = this.get('notifications'); this.toggleProperty('model.featured'); this.get('model').save().catch(function (errors) { - self.notifications.showErrors(errors); + notifications.showErrors(errors); }); }, + showPostContent: function () { this.transitionToRoute('posts.post', this.get('model')); } } }); - -export default PostController; diff --git a/core/client/app/controllers/reset.js b/core/client/app/controllers/reset.js index ca3ebfe555..a428b30b5c 100644 --- a/core/client/app/controllers/reset.js +++ b/core/client/app/controllers/reset.js @@ -10,6 +10,9 @@ export default Ember.Controller.extend(ValidationEngine, { validationType: 'reset', + ghostPaths: Ember.inject.service('ghost-paths'), + notifications: Ember.inject.service(), + email: Ember.computed('token', function () { // The token base64 encodes the email (and some other stuff), // each section is divided by a '|'. Email comes second. @@ -40,18 +43,18 @@ export default Ember.Controller.extend(ValidationEngine, { } }).then(function (resp) { self.toggleProperty('submitting'); - self.notifications.showSuccess(resp.passwordreset[0].message, true); + self.get('notifications').showSuccess(resp.passwordreset[0].message, true); self.get('session').authenticate('simple-auth-authenticator:oauth2-password-grant', { identification: self.get('email'), password: credentials.newPassword }); }).catch(function (response) { - self.notifications.showAPIError(response); + self.get('notifications').showAPIError(response); self.toggleProperty('submitting'); }); }).catch(function (error) { self.toggleProperty('submitting'); - self.notifications.showErrors(error); + 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 f396f8737b..550338cdff 100644 --- a/core/client/app/controllers/settings/code-injection.js +++ b/core/client/app/controllers/settings/code-injection.js @@ -1,20 +1,19 @@ import Ember from 'ember'; -var SettingsCodeInjectionController = Ember.Controller.extend({ + +export default Ember.Controller.extend({ actions: { save: function () { - var self = this; + var notifications = this.get('notifications'); return this.get('model').save().then(function (model) { - self.notifications.closePassive(); - self.notifications.showSuccess('Settings successfully saved.'); + notifications.closePassive(); + notifications.showSuccess('Settings successfully saved.'); return model; }).catch(function (errors) { - self.notifications.closePassive(); - self.notifications.showErrors(errors); + notifications.closePassive(); + notifications.showErrors(errors); }); } } }); - -export default SettingsCodeInjectionController; diff --git a/core/client/app/controllers/settings/general.js b/core/client/app/controllers/settings/general.js index 7efddc613f..b36ff95230 100644 --- a/core/client/app/controllers/settings/general.js +++ b/core/client/app/controllers/settings/general.js @@ -1,7 +1,9 @@ import Ember from 'ember'; import randomPassword from 'ghost/utils/random-password'; -var SettingsGeneralController = Ember.Controller.extend({ +export default Ember.Controller.extend({ + notifications: Ember.inject.service(), + selectedTheme: null, logoImageSource: Ember.computed('model.logo', function () { @@ -47,14 +49,14 @@ var SettingsGeneralController = Ember.Controller.extend({ actions: { save: function () { - var self = this; + var notifications = this.get('notifications'); return this.get('model').save().then(function (model) { - self.notifications.showSuccess('Settings successfully saved.'); + notifications.showSuccess('Settings successfully saved.'); return model; }).catch(function (errors) { - self.notifications.showErrors(errors); + notifications.showErrors(errors); }); }, @@ -67,5 +69,3 @@ var SettingsGeneralController = Ember.Controller.extend({ } } }); - -export default SettingsGeneralController; diff --git a/core/client/app/controllers/settings/labs.js b/core/client/app/controllers/settings/labs.js index 826cd5cd3b..ac5f9a71bf 100644 --- a/core/client/app/controllers/settings/labs.js +++ b/core/client/app/controllers/settings/labs.js @@ -2,10 +2,12 @@ import Ember from 'ember'; import {request as ajax} from 'ic-ajax'; export default Ember.Controller.extend(Ember.Evented, { - needs: ['feature'], - uploadButtonText: 'Import', importErrors: '', + + ghostPaths: Ember.inject.service('ghost-paths'), + notifications: Ember.inject.service(), + labsJSON: Ember.computed('model.labs', function () { return JSON.parse(this.get('model.labs') || {}); }), @@ -28,11 +30,12 @@ export default Ember.Controller.extend(Ember.Evented, { actions: { onUpload: function (file) { var self = this, - formData = new FormData(); + formData = new FormData(), + notifications = this.get('notifications'); this.set('uploadButtonText', 'Importing'); this.set('importErrors', ''); - this.notifications.closePassive(); + notifications.closePassive(); formData.append('importfile', file); @@ -51,13 +54,13 @@ export default Ember.Controller.extend(Ember.Evented, { self.store.unloadAll('role'); self.store.unloadAll('setting'); self.store.unloadAll('notification'); - self.notifications.showSuccess('Import successful.'); + notifications.showSuccess('Import successful.'); }).catch(function (response) { if (response && response.jqXHR && response.jqXHR.responseJSON && response.jqXHR.responseJSON.errors) { self.set('importErrors', response.jqXHR.responseJSON.errors); } - self.notifications.showError('Import Failed'); + notifications.showError('Import Failed'); }).finally(function () { self.set('uploadButtonText', 'Import'); self.trigger('reset'); @@ -77,17 +80,17 @@ export default Ember.Controller.extend(Ember.Evented, { }, sendTestEmail: function () { - var self = this; + var notifications = this.get('notifications'); ajax(this.get('ghostPaths.url').api('mail', 'test'), { type: 'POST' }).then(function () { - self.notifications.showSuccess('Check your email for the test message.'); + notifications.showSuccess('Check your email for the test message.'); }).catch(function (error) { if (typeof error.jqXHR !== 'undefined') { - self.notifications.showAPIError(error); + notifications.showAPIError(error); } else { - self.notifications.showErrors(error); + notifications.showErrors(error); } }); } diff --git a/core/client/app/controllers/settings/navigation.js b/core/client/app/controllers/settings/navigation.js index d1bebe4a80..bcb7e91ae7 100644 --- a/core/client/app/controllers/settings/navigation.js +++ b/core/client/app/controllers/settings/navigation.js @@ -1,8 +1,6 @@ import Ember from 'ember'; -var NavigationController, - NavItem; -NavItem = Ember.Object.extend({ +var NavItem = Ember.Object.extend({ label: '', url: '', last: false, @@ -12,7 +10,10 @@ NavItem = Ember.Object.extend({ }) }); -NavigationController = Ember.Controller.extend({ +export default Ember.Controller.extend({ + config: Ember.inject.service(), + notifications: Ember.inject.service(), + blogUrl: Ember.computed('config.blogUrl', function () { var url = this.get('config.blogUrl'); @@ -96,18 +97,18 @@ NavigationController = Ember.Controller.extend({ }, save: function () { - var self = this, - navSetting, + var navSetting, blogUrl = this.get('config').blogUrl, blogUrlRegex = new RegExp('^' + blogUrl + '(.*)', 'i'), navItems = this.get('navigationItems'), message = 'One of your navigation items has an empty label. ' + '
Please enter a new label or delete the item before saving.', - match; + match, + notifications = this.get('notifications'); // Don't save if there's a blank label. - if (navItems.find(function (item) { return !item.get('isComplete') && !item.get('last');})) { - self.notifications.showErrors([message.htmlSafe()]); + if (navItems.find(function (item) {return !item.get('isComplete') && !item.get('last');})) { + notifications.showErrors([message.htmlSafe()]); return; } @@ -147,15 +148,13 @@ NavigationController = Ember.Controller.extend({ // we need to have navigationItems recomputed. this.get('model').notifyPropertyChange('navigation'); - this.notifications.closePassive(); + notifications.closePassive(); this.get('model').save().then(function () { - self.notifications.showSuccess('Navigation items saved.'); + notifications.showSuccess('Navigation items saved.'); }).catch(function (err) { - self.notifications.showErrors(err); + notifications.showErrors(err); }); } } }); - -export default NavigationController; diff --git a/core/client/app/controllers/settings/tags.js b/core/client/app/controllers/settings/tags.js index 8877a4a331..e4312154f4 100644 --- a/core/client/app/controllers/settings/tags.js +++ b/core/client/app/controllers/settings/tags.js @@ -3,7 +3,7 @@ import PaginationMixin from 'ghost/mixins/pagination-controller'; import SettingsMenuMixin from 'ghost/mixins/settings-menu-controller'; import boundOneWay from 'ghost/utils/bound-one-way'; -var TagsController = Ember.ArrayController.extend(PaginationMixin, SettingsMenuMixin, { +export default Ember.ArrayController.extend(PaginationMixin, SettingsMenuMixin, { tags: Ember.computed.alias('model'), activeTag: null, @@ -20,10 +20,12 @@ var TagsController = Ember.ArrayController.extend(PaginationMixin, SettingsMenuM }, application: Ember.inject.controller(), + config: Ember.inject.service(), + notifications: Ember.inject.service(), showErrors: function (errors) { errors = Ember.isArray(errors) ? errors : [errors]; - this.notifications.showErrors(errors); + this.get('notifications').showErrors(errors); }, saveActiveTagProperty: function (propKey, newValue) { @@ -40,7 +42,7 @@ var TagsController = Ember.ArrayController.extend(PaginationMixin, SettingsMenuM activeTag.set(propKey, newValue); - this.notifications.closePassive(); + this.get('notifications').closePassive(); activeTag.save().catch(function (errors) { self.showErrors(errors); @@ -62,7 +64,7 @@ var TagsController = Ember.ArrayController.extend(PaginationMixin, SettingsMenuM }), seoURL: Ember.computed('activeTagSlugScratch', function () { - var blogUrl = this.get('config').blogUrl, + var blogUrl = this.get('config.blogUrl'), seoSlug = this.get('activeTagSlugScratch') ? this.get('activeTagSlugScratch') : '', seoURL = blogUrl + '/tag/' + seoSlug; @@ -137,5 +139,3 @@ var TagsController = Ember.ArrayController.extend(PaginationMixin, SettingsMenuM } } }); - -export default TagsController; diff --git a/core/client/app/controllers/settings/users/user.js b/core/client/app/controllers/settings/users/user.js index 091b514ffe..39ff4197c4 100644 --- a/core/client/app/controllers/settings/users/user.js +++ b/core/client/app/controllers/settings/users/user.js @@ -3,7 +3,9 @@ import SlugGenerator from 'ghost/models/slug-generator'; import isNumber from 'ghost/utils/isNumber'; import boundOneWay from 'ghost/utils/bound-one-way'; -var SettingsUserController = Ember.Controller.extend({ +export default Ember.Controller.extend({ + ghostPaths: Ember.inject.service('ghost-paths'), + notifications: Ember.inject.service(), user: Ember.computed.alias('model'), @@ -16,8 +18,10 @@ var SettingsUserController = Ember.Controller.extend({ coverDefault: Ember.computed('ghostPaths', function () { return this.get('ghostPaths.url').asset('/shared/img/user-cover.png'); }), + coverImageBackground: Ember.computed('user.cover', 'coverDefault', function () { var url = this.get('user.cover') || this.get('coverDefault'); + return `background-image: url(${url})`.htmlSafe(); }), @@ -28,8 +32,10 @@ var SettingsUserController = Ember.Controller.extend({ userDefault: Ember.computed('ghostPaths', function () { return this.get('ghostPaths.url').asset('/shared/img/user-image.png'); }), + userImageBackground: Ember.computed('user.image', 'userDefault', function () { var url = this.get('user.image') || this.get('userDefault'); + return `background-image: url(${url})`.htmlSafe(); }), @@ -68,14 +74,15 @@ var SettingsUserController = Ember.Controller.extend({ if (model.get('invited')) { model.destroyRecord().then(function () { var notificationText = 'Invitation revoked. (' + email + ')'; - self.notifications.showSuccess(notificationText, false); + + self.get('notifications').showSuccess(notificationText, false); }).catch(function (error) { - self.notifications.showAPIError(error); + self.get('notifications').showAPIError(error); }); } else { // if the user is no longer marked as "invited", then show a warning and reload the route self.get('target').send('reload'); - self.notifications.showError('This user has already accepted the invitation.', {delayed: 500}); + self.get('notifications').showError('This user has already accepted the invitation.', {delayed: 500}); } }); }, @@ -88,13 +95,13 @@ var SettingsUserController = 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 (result.users[0].status === 'invited-pending') { - self.notifications.showWarn('Invitation email was not sent. Please try resending.'); + self.get('notifications').showWarn('Invitation email was not sent. Please try resending.'); } else { self.get('model').set('status', result.users[0].status); - self.notifications.showSuccess(notificationText); + self.get('notifications').showSuccess(notificationText); } }).catch(function (error) { - self.notifications.showAPIError(error); + self.get('notifications').showAPIError(error); }); }, @@ -117,7 +124,7 @@ var SettingsUserController = Ember.Controller.extend({ var currentPath, newPath; - self.notifications.showSuccess('Settings successfully saved.'); + 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 @@ -133,7 +140,7 @@ var SettingsUserController = Ember.Controller.extend({ return model; }).catch(function (errors) { - self.notifications.showErrors(errors); + self.get('notifications').showErrors(errors); }); this.set('lastPromise', promise); @@ -152,14 +159,14 @@ var SettingsUserController = Ember.Controller.extend({ ne2Password: '' }); - self.notifications.showSuccess('Password updated.'); + self.get('notifications').showSuccess('Password updated.'); return model; }).catch(function (errors) { - self.notifications.showAPIError(errors); + self.get('notifications').showAPIError(errors); }); } else { - self.notifications.showErrors(user.get('passwordValidationErrors')); + self.get('notifications').showErrors(user.get('passwordValidationErrors')); } }, @@ -217,5 +224,3 @@ var SettingsUserController = Ember.Controller.extend({ } } }); - -export default SettingsUserController; diff --git a/core/client/app/controllers/setup.js b/core/client/app/controllers/setup.js index e4827ae183..84e4e891ec 100644 --- a/core/client/app/controllers/setup.js +++ b/core/client/app/controllers/setup.js @@ -12,12 +12,16 @@ export default Ember.Controller.extend(ValidationEngine, { // ValidationEngine settings validationType: 'setup', + ghostPaths: Ember.inject.service('ghost-paths'), + notifications: Ember.inject.service(), + actions: { setup: function () { var self = this, - data = self.getProperties('blogTitle', 'name', 'email', 'password'); + data = self.getProperties('blogTitle', 'name', 'email', 'password'), + notifications = this.get('notifications'); - self.notifications.closePassive(); + notifications.closePassive(); this.toggleProperty('submitting'); this.validate({format: false}).then(function () { @@ -39,11 +43,11 @@ export default Ember.Controller.extend(ValidationEngine, { }); }).catch(function (resp) { self.toggleProperty('submitting'); - self.notifications.showAPIError(resp); + notifications.showAPIError(resp); }); }).catch(function (errors) { self.toggleProperty('submitting'); - self.notifications.showErrors(errors); + notifications.showErrors(errors); }); } } diff --git a/core/client/app/controllers/signin.js b/core/client/app/controllers/signin.js index ae6774c9dc..a671aeda0a 100644 --- a/core/client/app/controllers/signin.js +++ b/core/client/app/controllers/signin.js @@ -7,6 +7,9 @@ export default Ember.Controller.extend(ValidationEngine, { submitting: false, + ghostPaths: Ember.inject.service('ghost-paths'), + notifications: Ember.inject.service(), + actions: { authenticate: function () { var model = this.get('model'), @@ -28,10 +31,10 @@ export default Ember.Controller.extend(ValidationEngine, { $('#login').find('input').trigger('change'); this.validate({format: false}).then(function () { - self.notifications.closePassive(); + self.get('notifications').closePassive(); self.send('authenticate'); }).catch(function (errors) { - self.notifications.showErrors(errors); + self.get('notifications').showErrors(errors); }); }, @@ -55,10 +58,10 @@ export default Ember.Controller.extend(ValidationEngine, { } }).then(function () { self.set('submitting', false); - self.notifications.showSuccess('Please check your email for instructions.'); + self.get('notifications').showSuccess('Please check your email for instructions.'); }).catch(function (resp) { self.set('submitting', false); - self.notifications.showAPIError(resp, {defaultErrorText: 'There was a problem with the reset, please try again.'}); + self.get('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 4d98d17017..ed29e2fb0e 100644 --- a/core/client/app/controllers/signup.js +++ b/core/client/app/controllers/signup.js @@ -3,18 +3,22 @@ import {request as ajax} from 'ic-ajax'; import ValidationEngine from 'ghost/mixins/validation-engine'; export default Ember.Controller.extend(ValidationEngine, { - submitting: false, - // ValidationEngine settings validationType: 'signup', + submitting: false, + + ghostPaths: Ember.inject.service('ghost-paths'), + notifications: Ember.inject.service(), + actions: { signup: function () { var self = this, model = this.get('model'), - data = model.getProperties('name', 'email', 'password', 'token'); + data = model.getProperties('name', 'email', 'password', 'token'), + notifications = this.get('notifications'); - self.notifications.closePassive(); + notifications.closePassive(); this.toggleProperty('submitting'); this.validate({format: false}).then(function () { @@ -37,11 +41,11 @@ export default Ember.Controller.extend(ValidationEngine, { }); }, function (resp) { self.toggleProperty('submitting'); - self.notifications.showAPIError(resp); + notifications.showAPIError(resp); }); }, function (errors) { self.toggleProperty('submitting'); - self.notifications.showErrors(errors); + notifications.showErrors(errors); }); } } diff --git a/core/client/app/initializers/dropdown.js b/core/client/app/initializers/dropdown.js deleted file mode 100644 index db339383f9..0000000000 --- a/core/client/app/initializers/dropdown.js +++ /dev/null @@ -1,24 +0,0 @@ -import DropdownService from 'ghost/utils/dropdown-service'; - -var dropdownInitializer = { - name: 'dropdown', - - initialize: function (container, application) { - application.register('dropdown:service', DropdownService); - - // Inject dropdowns - application.inject('component:gh-dropdown', 'dropdown', 'dropdown:service'); - application.inject('component:gh-dropdown-button', 'dropdown', 'dropdown:service'); - application.inject('controller:application', 'dropdown', 'dropdown:service'); - application.inject('controller:modals.delete-post', 'dropdown', 'dropdown:service'); - application.inject('controller:modals.transfer-owner', 'dropdown', 'dropdown:service'); - application.inject('route:application', 'dropdown', 'dropdown:service'); - - // Inject popovers - application.inject('component:gh-popover', 'dropdown', 'dropdown:service'); - application.inject('component:gh-popover-button', 'dropdown', 'dropdown:service'); - application.inject('route:application', 'dropdown', 'dropdown:service'); - } -}; - -export default dropdownInitializer; diff --git a/core/client/app/initializers/ghost-config.js b/core/client/app/initializers/ghost-config.js deleted file mode 100644 index a9fc300a1f..0000000000 --- a/core/client/app/initializers/ghost-config.js +++ /dev/null @@ -1,17 +0,0 @@ -import getConfig from 'ghost/utils/config-parser'; - -var ConfigInitializer = { - name: 'config', - - initialize: function (container, application) { - var config = getConfig(); - application.register('ghost:config', config, {instantiate: false}); - - application.inject('route', 'config', 'ghost:config'); - application.inject('model:post', 'config', 'ghost:config'); - application.inject('controller', 'config', 'ghost:config'); - application.inject('component', 'config', 'ghost:config'); - } -}; - -export default ConfigInitializer; diff --git a/core/client/app/initializers/ghost-paths.js b/core/client/app/initializers/ghost-paths.js deleted file mode 100644 index a72f26a8d3..0000000000 --- a/core/client/app/initializers/ghost-paths.js +++ /dev/null @@ -1,16 +0,0 @@ -import ghostPaths from 'ghost/utils/ghost-paths'; - -var ghostPathsInitializer = { - name: 'ghost-paths', - after: 'store', - - initialize: function (container, application) { - application.register('ghost:paths', ghostPaths(), {instantiate: false}); - - application.inject('route', 'ghostPaths', 'ghost:paths'); - application.inject('model', 'ghostPaths', 'ghost:paths'); - application.inject('controller', 'ghostPaths', 'ghost:paths'); - } -}; - -export default ghostPathsInitializer; diff --git a/core/client/app/initializers/notifications.js b/core/client/app/initializers/notifications.js deleted file mode 100644 index 3e0d4fac26..0000000000 --- a/core/client/app/initializers/notifications.js +++ /dev/null @@ -1,17 +0,0 @@ -import Notifications from 'ghost/utils/notifications'; - -var injectNotificationsInitializer = { - name: 'injectNotifications', - before: 'authentication', - - initialize: function (container, application) { - application.register('notifications:main', Notifications); - - application.inject('controller', 'notifications', 'notifications:main'); - application.inject('component', 'notifications', 'notifications:main'); - application.inject('router', 'notifications', 'notifications:main'); - application.inject('route', 'notifications', 'notifications:main'); - } -}; - -export default injectNotificationsInitializer; diff --git a/core/client/app/mixins/editor-base-controller.js b/core/client/app/mixins/editor-base-controller.js index 8fb61710af..422dad2e27 100644 --- a/core/client/app/mixins/editor-base-controller.js +++ b/core/client/app/mixins/editor-base-controller.js @@ -5,24 +5,24 @@ import PostModel from 'ghost/models/post'; import boundOneWay from 'ghost/utils/bound-one-way'; import imageManager from 'ghost/utils/ed-image-manager'; -var watchedProps, - EditorControllerMixin; - // this array will hold properties we need to watch // to know if the model has been changed (`controller.isDirty`) -watchedProps = ['model.scratch', 'model.titleScratch', 'model.isDirty', 'model.tags.[]']; +var watchedProps = ['model.scratch', 'model.titleScratch', 'model.isDirty', 'model.tags.[]']; PostModel.eachAttribute(function (name) { watchedProps.push('model.' + name); }); -EditorControllerMixin = Ember.Mixin.create({ - needs: ['post-tags-input', 'post-settings-menu'], +export default Ember.Mixin.create({ + postTagsInputController: Ember.inject.controller('post-tags-input'), + postSettingsMenuController: Ember.inject.controller('post-settings-menu'), autoSaveId: null, timedSaveId: null, editor: null, + notifications: Ember.inject.service(), + init: function () { var self = this; @@ -32,6 +32,7 @@ EditorControllerMixin = Ember.Mixin.create({ return self.get('isDirty') ? self.unloadDirtyMessage() : null; }; }, + autoSave: function () { // Don't save just because we swapped out models if (this.get('model.isDraft') && !this.get('model.isNew')) { @@ -213,21 +214,24 @@ EditorControllerMixin = Ember.Mixin.create({ showSaveNotification: function (prevStatus, status, delay) { var message = this.messageMap.success.post[prevStatus][status], path = this.get('model.absoluteUrl'), - type = this.get('postOrPage'); + type = this.get('postOrPage'), + notifications = this.get('notifications'); if (status === 'published') { message += ` View ${type}`; } - this.notifications.showSuccess(message.htmlSafe(), {delayed: delay}); + + notifications.showSuccess(message.htmlSafe(), {delayed: delay}); }, showErrorNotification: function (prevStatus, status, errors, delay) { var message = this.messageMap.errors.post[prevStatus][status], - error = (errors && errors[0] && errors[0].message) || 'Unknown Error'; + error = (errors && errors[0] && errors[0].message) || 'Unknown Error', + notifications = this.get('notifications'); message += '
' + error; - this.notifications.showError(message.htmlSafe(), {delayed: delay}); + notifications.showError(message.htmlSafe(), {delayed: delay}); }, shouldFocusTitle: Ember.computed.alias('model.isNew'), @@ -241,8 +245,9 @@ EditorControllerMixin = Ember.Mixin.create({ autoSaveId = this.get('autoSaveId'), timedSaveId = this.get('timedSaveId'), self = this, - psmController = this.get('controllers.post-settings-menu'), - promise; + psmController = this.get('postSettingsMenuController'), + promise, + notifications = this.get('notifications'); options = options || {}; @@ -263,10 +268,10 @@ EditorControllerMixin = Ember.Mixin.create({ this.set('timedSaveId', null); } - self.notifications.closePassive(); + notifications.closePassive(); // ensure an incomplete tag is finalised before save - this.get('controllers.post-tags-input').send('addNewTag'); + this.get('postTagsInputController').send('addNewTag'); // Set the properties that are indirected // set markdown equal to what's in the editor, minus the image markers. @@ -367,5 +372,3 @@ EditorControllerMixin = Ember.Mixin.create({ } } }); - -export default EditorControllerMixin; diff --git a/core/client/app/mixins/pagination-controller.js b/core/client/app/mixins/pagination-controller.js index 5f68111aa7..7957781561 100644 --- a/core/client/app/mixins/pagination-controller.js +++ b/core/client/app/mixins/pagination-controller.js @@ -2,6 +2,8 @@ import Ember from 'ember'; import getRequestErrorMessage from 'ghost/utils/ajax'; export default Ember.Mixin.create({ + notifications: Ember.inject.service(), + // set from PaginationRouteMixin paginationSettings: null, @@ -23,7 +25,7 @@ export default Ember.Mixin.create({ message += '.'; } - this.notifications.showError(message); + this.get('notifications').showError(message); }, actions: { diff --git a/core/client/app/mixins/selective-save.js b/core/client/app/mixins/selective-save.js deleted file mode 100644 index c3945b5d61..0000000000 --- a/core/client/app/mixins/selective-save.js +++ /dev/null @@ -1,122 +0,0 @@ -import Ember from 'ember'; -// SelectiveSaveMixin adds a saveOnly method to a DS.Model. -// -// saveOnly provides a way to save one or more properties of a model while -// preserving outstanding changes to other properties. -var SelectiveSaveMixin = Ember.Mixin.create({ - saveOnly: function () { - if (arguments.length === 0) { - return Ember.RSVP.resolve(); - } - - if (arguments.length === 1 && Ember.isArray(arguments[0])) { - return this.saveOnly.apply(this, Array.prototype.slice.call(arguments[0])); - } - - var propertiesToSave = Array.prototype.slice.call(arguments), - changed, - hasMany = {}, - belongsTo = {}, - self = this; - - changed = this.changedAttributes(); - - // disable observers so we can make changes to the model but not have - // them reflected by the UI - this.beginPropertyChanges(); - - // make a copy of any relations the model may have so they can - // be reapplied later - this.eachRelationship(function (name, meta) { - if (meta.kind === 'hasMany') { - hasMany[name] = self.get(name).slice(); - return; - } - - if (meta.kind === 'belongsTo') { - belongsTo[name] = self.get(name); - return; - } - }); - - try { - // roll back all changes to the model and then reapply only those that - // are part of the saveOnly - - self.rollback(); - - propertiesToSave.forEach(function (name) { - if (hasMany.hasOwnProperty(name)) { - self.get(name).clear(); - - hasMany[name].forEach(function (relatedType) { - self.get(name).pushObject(relatedType); - }); - - return; - } - - if (belongsTo.hasOwnProperty(name)) { - return self.updateBelongsTo(name, belongsTo[name]); - } - - if (changed.hasOwnProperty(name)) { - return self.set(name, changed[name][1]); - } - }); - } - catch (err) { - // if we were not able to get the model into the correct state - // put it back the way we found it and return a rejected promise - - Ember.keys(changed).forEach(function (name) { - self.set(name, changed[name][1]); - }); - - Ember.keys(hasMany).forEach(function (name) { - self.updateHasMany(name, hasMany[name]); - }); - - Ember.keys(belongsTo).forEach(function (name) { - self.updateBelongsTo(name, belongsTo[name]); - }); - - self.endPropertyChanges(); - - return Ember.RSVP.reject(new Error(err.message || 'Error during saveOnly. Changes NOT saved.')); - } - - return this.save().finally(function () { - // reapply any changes that were not part of the save - - Ember.keys(changed).forEach(function (name) { - if (propertiesToSave.hasOwnProperty(name)) { - return; - } - - self.set(name, changed[name][1]); - }); - - Ember.keys(hasMany).forEach(function (name) { - if (propertiesToSave.hasOwnProperty(name)) { - return; - } - - self.updateHasMany(name, hasMany[name]); - }); - - Ember.keys(belongsTo).forEach(function (name) { - if (propertiesToSave.hasOwnProperty(name)) { - return; - } - - self.updateBelongsTo(name, belongsTo[name]); - }); - - // signal that we're finished and normal model observation may continue - self.endPropertyChanges(); - }); - } -}); - -export default SelectiveSaveMixin; diff --git a/core/client/app/mixins/settings-menu-controller.js b/core/client/app/mixins/settings-menu-controller.js index a2837f394b..6a0509a0db 100644 --- a/core/client/app/mixins/settings-menu-controller.js +++ b/core/client/app/mixins/settings-menu-controller.js @@ -1,10 +1,11 @@ import Ember from 'ember'; -var SettingsMenuControllerMixin = Ember.Mixin.create({ - needs: 'application', - isViewingSubview: Ember.computed('controllers.application.showSettingsMenu', function (key, value) { +export default Ember.Mixin.create({ + application: Ember.inject.controller(), + + isViewingSubview: Ember.computed('application.showSettingsMenu', function (key, value) { // Not viewing a subview if we can't even see the PSM - if (!this.get('controllers.application.showSettingsMenu')) { + if (!this.get('application.showSettingsMenu')) { return false; } if (arguments.length > 1) { @@ -24,5 +25,3 @@ var SettingsMenuControllerMixin = Ember.Mixin.create({ } } }); - -export default SettingsMenuControllerMixin; diff --git a/core/client/app/models/post.js b/core/client/app/models/post.js index db9512e1c7..99c0f18fe7 100644 --- a/core/client/app/models/post.js +++ b/core/client/app/models/post.js @@ -2,7 +2,7 @@ import Ember from 'ember'; import DS from 'ember-data'; import ValidationEngine from 'ghost/mixins/validation-engine'; -var Post = DS.Model.extend(ValidationEngine, { +export default DS.Model.extend(ValidationEngine, { validationType: 'post', uuid: DS.attr('string'), @@ -28,6 +28,9 @@ var Post = DS.Model.extend(ValidationEngine, { tags: DS.hasMany('tag', {embedded: 'always'}), url: DS.attr('string'), + config: Ember.inject.service(), + ghostPaths: Ember.inject.service('ghost-paths'), + absoluteUrl: Ember.computed('url', 'ghostPaths.url', 'config.blogUrl', function () { var blogUrl = this.get('config.blogUrl'), postUrl = this.get('url'); @@ -69,5 +72,3 @@ var Post = DS.Model.extend(ValidationEngine, { } }); - -export default Post; diff --git a/core/client/app/models/slug-generator.js b/core/client/app/models/slug-generator.js index 2252421986..ac0051f8cd 100644 --- a/core/client/app/models/slug-generator.js +++ b/core/client/app/models/slug-generator.js @@ -2,10 +2,11 @@ import Ember from 'ember'; import {request as ajax} from 'ic-ajax'; export default Ember.Object.extend({ - ghostPaths: null, slugType: null, value: null, + ghostPaths: Ember.inject.service('ghost-paths'), + toString: function () { return this.get('value'); }, diff --git a/core/client/app/models/user.js b/core/client/app/models/user.js index 7536942c49..7243c43bc9 100644 --- a/core/client/app/models/user.js +++ b/core/client/app/models/user.js @@ -2,9 +2,8 @@ import Ember from 'ember'; import DS from 'ember-data'; import {request as ajax} from 'ic-ajax'; import ValidationEngine from 'ghost/mixins/validation-engine'; -import SelectiveSaveMixin from 'ghost/mixins/selective-save'; -export default DS.Model.extend(SelectiveSaveMixin, ValidationEngine, { +export default DS.Model.extend(ValidationEngine, { validationType: 'user', uuid: DS.attr('string'), @@ -28,6 +27,8 @@ export default DS.Model.extend(SelectiveSaveMixin, ValidationEngine, { updated_by: DS.attr('number'), roles: DS.hasMany('role', {embedded: 'always'}), + ghostPaths: Ember.inject.service('ghost-paths'), + role: Ember.computed('roles', function (name, value) { if (arguments.length > 1) { // Only one role per user, so remove any old data. @@ -93,13 +94,13 @@ export default DS.Model.extend(SelectiveSaveMixin, ValidationEngine, { isPasswordValid: Ember.computed.empty('passwordValidationErrors.[]'), - active: function () { + active: Ember.computed('status', function () { return ['active', 'warn-1', 'warn-2', 'warn-3', 'warn-4', 'locked'].indexOf(this.get('status')) > -1; - }.property('status'), + }), - invited: function () { + invited: Ember.computed('status', function () { return ['invited', 'invited-pending'].indexOf(this.get('status')) > -1; - }.property('status'), + }), pending: Ember.computed.equal('status', 'invited-pending').property('status') }); diff --git a/core/client/app/router.js b/core/client/app/router.js index bc44217408..68c17d7c57 100644 --- a/core/client/app/router.js +++ b/core/client/app/router.js @@ -6,9 +6,13 @@ var Router = Ember.Router.extend({ location: 'trailing-history', // use HTML5 History API instead of hash-tag based URLs rootURL: ghostPaths().adminRoot, // admin interface lives under sub-directory /ghost + notifications: Ember.inject.service(), + clearNotifications: Ember.on('didTransition', function () { - this.notifications.closePassive(); - this.notifications.displayDelayed(); + var notifications = this.get('notifications'); + + notifications.closePassive(); + notifications.displayDelayed(); }) }); diff --git a/core/client/app/routes/about.js b/core/client/app/routes/about.js index 11a0a74835..9c23a7ad6d 100644 --- a/core/client/app/routes/about.js +++ b/core/client/app/routes/about.js @@ -1,3 +1,4 @@ +import Ember from 'ember'; import {request as ajax} from 'ic-ajax'; import AuthenticatedRoute from 'ghost/routes/authenticated'; import styleBody from 'ghost/mixins/style-body'; @@ -7,6 +8,8 @@ export default AuthenticatedRoute.extend(styleBody, { classNames: ['view-about'], + ghostPaths: Ember.inject.service('ghost-paths'), + cachedConfig: false, model: function () { diff --git a/core/client/app/routes/application.js b/core/client/app/routes/application.js index cd486e7391..54a46473d3 100644 --- a/core/client/app/routes/application.js +++ b/core/client/app/routes/application.js @@ -6,16 +6,19 @@ import Configuration from 'simple-auth/configuration'; import ShortcutsRoute from 'ghost/mixins/shortcuts-route'; import ctrlOrCmd from 'ghost/utils/ctrl-or-cmd'; -var ApplicationRoute, - shortcuts = {}; +var shortcuts = {}; shortcuts.esc = {action: 'closePopups', scope: 'all'}; shortcuts.enter = {action: 'confirmModal', scope: 'modal'}; shortcuts[ctrlOrCmd + '+s'] = {action: 'save', scope: 'all'}; -ApplicationRoute = Ember.Route.extend(ApplicationRouteMixin, ShortcutsRoute, { +export default Ember.Route.extend(ApplicationRouteMixin, ShortcutsRoute, { shortcuts: shortcuts, + config: Ember.inject.service(), + dropdown: Ember.inject.service(), + notifications: Ember.inject.service(), + afterModel: function (model, transition) { if (this.get('session').isAuthenticated) { transition.send('loadServerNotifications'); @@ -68,10 +71,10 @@ ApplicationRoute = Ember.Route.extend(ApplicationRouteMixin, ShortcutsRoute, { err.message = err.message.htmlSafe(); }); - this.notifications.showErrors(error.errors); + this.get('notifications').showErrors(error.errors); } else { // connection errors don't return proper status message, only req.body - this.notifications.showError('There was a problem on the server.'); + this.get('notifications').showError('There was a problem on the server.'); } }, @@ -96,7 +99,7 @@ ApplicationRoute = Ember.Route.extend(ApplicationRouteMixin, ShortcutsRoute, { }, sessionInvalidationFailed: function (error) { - this.notifications.showError(error.message); + this.get('notifications').showError(error.message); }, openModal: function (modalName, model, type) { @@ -149,7 +152,7 @@ ApplicationRoute = Ember.Route.extend(ApplicationRouteMixin, ShortcutsRoute, { if (!user.get('isAuthor') && !user.get('isEditor')) { self.store.findAll('notification').then(function (serverNotifications) { serverNotifications.forEach(function (notification) { - self.notifications.handleNotification(notification, isDelayed); + self.get('notifications').handleNotification(notification, isDelayed); }); }); } @@ -158,11 +161,11 @@ ApplicationRoute = Ember.Route.extend(ApplicationRouteMixin, ShortcutsRoute, { }, handleErrors: function (errors) { - var self = this; + var notifications = this.get('notifications'); - this.notifications.clear(); + notifications.clear(); errors.forEach(function (errorObj) { - self.notifications.showError(errorObj.message || errorObj); + notifications.showError(errorObj.message || errorObj); if (errorObj.hasOwnProperty('el')) { errorObj.el.addClass('input-error'); @@ -174,5 +177,3 @@ ApplicationRoute = Ember.Route.extend(ApplicationRouteMixin, ShortcutsRoute, { save: Ember.K } }); - -export default ApplicationRoute; diff --git a/core/client/app/routes/reset.js b/core/client/app/routes/reset.js index ff9dc1b0b5..9497f6d121 100644 --- a/core/client/app/routes/reset.js +++ b/core/client/app/routes/reset.js @@ -2,12 +2,14 @@ import Ember from 'ember'; import Configuration from 'simple-auth/configuration'; import styleBody from 'ghost/mixins/style-body'; -var ResetRoute = Ember.Route.extend(styleBody, { +export default Ember.Route.extend(styleBody, { classNames: ['ghost-reset'], + notifications: Ember.inject.service(), + beforeModel: function () { if (this.get('session').isAuthenticated) { - this.notifications.showWarn('You can\'t reset your password while you\'re signed in.', {delayed: true}); + this.get('notifications').showWarn('You can\'t reset your password while you\'re signed in.', {delayed: true}); this.transitionTo(Configuration.routeAfterAuthentication); } }, @@ -22,5 +24,3 @@ var ResetRoute = Ember.Route.extend(styleBody, { this.controller.clearData(); } }); - -export default ResetRoute; diff --git a/core/client/app/routes/settings/apps.js b/core/client/app/routes/settings/apps.js index 143a64b1e0..93aeab1d09 100644 --- a/core/client/app/routes/settings/apps.js +++ b/core/client/app/routes/settings/apps.js @@ -1,12 +1,15 @@ +import Ember from 'ember'; import AuthenticatedRoute from 'ghost/routes/authenticated'; import CurrentUserSettings from 'ghost/mixins/current-user-settings'; import styleBody from 'ghost/mixins/style-body'; -var AppsRoute = AuthenticatedRoute.extend(styleBody, CurrentUserSettings, { +export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, { titleToken: 'Apps', classNames: ['settings-view-apps'], + config: Ember.inject.service(), + beforeModel: function () { if (!this.get('config.apps')) { return this.transitionTo('settings.general'); @@ -21,5 +24,3 @@ var AppsRoute = AuthenticatedRoute.extend(styleBody, CurrentUserSettings, { return this.store.find('app'); } }); - -export default AppsRoute; diff --git a/core/client/app/routes/setup.js b/core/client/app/routes/setup.js index d6a1fc19d7..a1397f98ab 100644 --- a/core/client/app/routes/setup.js +++ b/core/client/app/routes/setup.js @@ -8,9 +8,10 @@ export default Ember.Route.extend(styleBody, { classNames: ['ghost-setup'], + ghostPaths: Ember.inject.service('ghost-paths'), + // use the beforeModel hook to check to see whether or not setup has been // previously completed. If it has, stop the transition into the setup page. - beforeModel: function () { var self = this; diff --git a/core/client/app/routes/signout.js b/core/client/app/routes/signout.js index f20e920a02..12e0925d6d 100644 --- a/core/client/app/routes/signout.js +++ b/core/client/app/routes/signout.js @@ -2,13 +2,15 @@ import Ember from 'ember'; import AuthenticatedRoute from 'ghost/routes/authenticated'; import styleBody from 'ghost/mixins/style-body'; -var SignoutRoute = AuthenticatedRoute.extend(styleBody, { +export default AuthenticatedRoute.extend(styleBody, { titleToken: 'Sign Out', classNames: ['ghost-signout'], + notifications: Ember.inject.service(), + afterModel: function (model, transition) { - this.notifications.clear(); + this.get('notifications').closeAll(); if (Ember.canInvoke(transition, 'send')) { transition.send('invalidateSession'); transition.abort(); @@ -17,5 +19,3 @@ var SignoutRoute = AuthenticatedRoute.extend(styleBody, { } } }); - -export default SignoutRoute; diff --git a/core/client/app/routes/signup.js b/core/client/app/routes/signup.js index bcf27656fb..369aafba5a 100644 --- a/core/client/app/routes/signup.js +++ b/core/client/app/routes/signup.js @@ -6,9 +6,12 @@ import styleBody from 'ghost/mixins/style-body'; export default Ember.Route.extend(styleBody, { classNames: ['ghost-signup'], + ghostPaths: Ember.inject.service('ghost-paths'), + notifications: Ember.inject.service(), + beforeModel: function () { if (this.get('session').isAuthenticated) { - this.notifications.showWarn('You need to sign out to register as a new user.', {delayed: true}); + this.get('notifications').showWarn('You need to sign out to register as a new user.', {delayed: true}); this.transitionTo(Configuration.routeAfterAuthentication); } }, @@ -22,7 +25,7 @@ export default Ember.Route.extend(styleBody, { return new Ember.RSVP.Promise(function (resolve) { if (!re.test(params.token)) { - self.notifications.showError('Invalid token.', {delayed: true}); + self.get('notifications').showError('Invalid token.', {delayed: true}); return resolve(self.transitionTo('signin')); } @@ -42,7 +45,7 @@ export default Ember.Route.extend(styleBody, { } }).then(function (response) { if (response && response.invitation && response.invitation[0].valid === false) { - self.notifications.showError('The invitation does not exist or is no longer valid.', {delayed: true}); + self.get('notifications').showError('The invitation does not exist or is no longer valid.', {delayed: true}); return resolve(self.transitionTo('signin')); } diff --git a/core/client/app/services/config.js b/core/client/app/services/config.js new file mode 100644 index 0000000000..0d554de5b3 --- /dev/null +++ b/core/client/app/services/config.js @@ -0,0 +1,43 @@ +import Ember from 'ember'; + +function isNumeric(num) { + return !isNaN(num); +} + +function _mapType(val) { + if (val === '') { + return null; + } else if (val === 'true') { + return true; + } else if (val === 'false') { + return false; + } else if (isNumeric(val)) { + return +val; + } else if (val.indexOf('{') === 0) { + try { + return JSON.parse(val); + } catch (e) { + /*jshint unused:false */ + return val; + } + } else { + return val; + } +} + +export default Ember.Service.extend(Ember._ProxyMixin, { + content: Ember.computed(function () { + var metaConfigTags = Ember.$('meta[name^="env-"]'), + config = {}; + + metaConfigTags.each(function (i, el) { + var key = el.name, + value = el.content, + propertyName = key.substring(4); + + config[propertyName] = _mapType(value); + }); + + return config; + }) +}); diff --git a/core/client/app/utils/dropdown-service.js b/core/client/app/services/dropdown.js similarity index 82% rename from core/client/app/utils/dropdown-service.js rename to core/client/app/services/dropdown.js index e9aa2b1740..be25396944 100644 --- a/core/client/app/utils/dropdown-service.js +++ b/core/client/app/services/dropdown.js @@ -2,17 +2,17 @@ import Ember from 'ember'; // This is used by the dropdown initializer (and subsequently popovers) to manage closing & toggling import BodyEventListener from 'ghost/mixins/body-event-listener'; -var DropdownService = Ember.Object.extend(Ember.Evented, BodyEventListener, { +export default Ember.Service.extend(Ember.Evented, BodyEventListener, { bodyClick: function (event) { /*jshint unused:false */ this.closeDropdowns(); }, + closeDropdowns: function () { this.trigger('close'); }, + toggleDropdown: function (dropdownName, dropdownButton) { this.trigger('toggle', {target: dropdownName, button: dropdownButton}); } }); - -export default DropdownService; diff --git a/core/client/app/services/ghost-paths.js b/core/client/app/services/ghost-paths.js new file mode 100644 index 0000000000..12563a0b4f --- /dev/null +++ b/core/client/app/services/ghost-paths.js @@ -0,0 +1,6 @@ +import Ember from 'ember'; +import ghostPaths from 'ghost/utils/ghost-paths'; + +export default Ember.Service.extend(Ember._ProxyMixin, { + content: ghostPaths() +}); diff --git a/core/client/app/utils/notifications.js b/core/client/app/services/notifications.js similarity index 87% rename from core/client/app/utils/notifications.js rename to core/client/app/services/notifications.js index c59c3ccbe3..4e4c10d6b4 100644 --- a/core/client/app/utils/notifications.js +++ b/core/client/app/services/notifications.js @@ -1,8 +1,8 @@ import Ember from 'ember'; import Notification from 'ghost/models/notification'; -var Notifications = Ember.ArrayProxy.extend({ - delayedNotifications: [], +export default Ember.Service.extend({ + delayedNotifications: Ember.A(), content: Ember.A(), timeout: 3000, @@ -25,6 +25,7 @@ var Notifications = Ember.ArrayProxy.extend({ this._super(object); }, + handleNotification: function (message, delayed) { if (typeof message.toJSON === 'function') { // If this is a persistent message from the server, treat it as html safe @@ -42,11 +43,12 @@ var Notifications = Ember.ArrayProxy.extend({ } if (!delayed) { - this.pushObject(message); + this.get('content').pushObject(message); } else { - this.delayedNotifications.push(message); + this.delayedNotifications.pushObject(message); } }, + showError: function (message, options) { options = options || {}; @@ -59,6 +61,7 @@ var Notifications = Ember.ArrayProxy.extend({ message: message }, options.delayed); }, + showErrors: function (errors, options) { options = options || {}; @@ -70,6 +73,7 @@ var Notifications = Ember.ArrayProxy.extend({ this.showError(errors[i].message || errors[i], {doNotClosePassive: true}); } }, + showAPIError: function (resp, options) { options = options || {}; @@ -89,6 +93,7 @@ var Notifications = Ember.ArrayProxy.extend({ this.showError(options.defaultErrorText, {doNotClosePassive: true}); } }, + showInfo: function (message, options) { options = options || {}; @@ -101,6 +106,7 @@ var Notifications = Ember.ArrayProxy.extend({ message: message }, options.delayed); }, + showSuccess: function (message, options) { options = options || {}; @@ -113,6 +119,7 @@ var Notifications = Ember.ArrayProxy.extend({ message: message }, options.delayed); }, + showWarn: function (message, options) { options = options || {}; @@ -125,35 +132,38 @@ var Notifications = Ember.ArrayProxy.extend({ message: message }, options.delayed); }, + displayDelayed: function () { var self = this; self.delayedNotifications.forEach(function (message) { - self.pushObject(message); + self.get('content').pushObject(message); }); self.delayedNotifications = []; }, + closeNotification: function (notification) { - var self = this; + var content = this.get('content'); if (notification instanceof Notification) { notification.deleteRecord(); notification.save().finally(function () { - self.removeObject(notification); + content.removeObject(notification); }); } else { - this.removeObject(notification); + content.removeObject(notification); } }, + closePassive: function () { - this.set('content', this.rejectBy('status', 'passive')); + this.set('content', this.get('content').rejectBy('status', 'passive')); }, + closePersistent: function () { - this.set('content', this.rejectBy('status', 'persistent')); + this.set('content', this.get('content').rejectBy('status', 'persistent')); }, + closeAll: function () { - this.clear(); + this.get('content').clear(); } }); - -export default Notifications; diff --git a/core/client/app/utils/config-parser.js b/core/client/app/utils/config-parser.js deleted file mode 100644 index cb2a97ecc0..0000000000 --- a/core/client/app/utils/config-parser.js +++ /dev/null @@ -1,43 +0,0 @@ -var isNumeric = function (num) { - return !isNaN(num); - }, - - _mapType = function (val) { - if (val === '') { - return null; - } else if (val === 'true') { - return true; - } else if (val === 'false') { - return false; - } else if (isNumeric(val)) { - return +val; - } else if (val.indexOf('{') === 0) { - try { - return JSON.parse(val); - } catch (e) { - /*jshint unused:false */ - return val; - } - } else { - return val; - } - }, - - parseConfiguration = function () { - var metaConfigTags = $('meta[name^="env-"]'), - propertyName, - config = {}, - value, - key, - i; - - for (i = 0; i < metaConfigTags.length; i += 1) { - key = $(metaConfigTags[i]).prop('name'); - value = $(metaConfigTags[i]).prop('content'); - propertyName = key.substring(4); // produce config name ignoring the initial 'env-'. - config[propertyName] = _mapType(value); // map string values to types if possible - } - return config; - }; - -export default parseConfiguration;