From 3d6856614f019f8fc66248751a15d48f8c1a0b73 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Wed, 28 Oct 2015 11:36:45 +0000 Subject: [PATCH] Use es6 across client and add ember-suave to enforce rules no issue - add ember-suave dependency - upgrade grunt-jscs dependency - add a new .jscsrc for the client's tests directory that extends from client's base .jscsrc - separate client tests in Gruntfile jscs task so they pick up the test's .jscsrc - standardize es6 usage across client --- Gruntfile.js | 16 +- core/client/.jscsrc | 14 + core/client/app/adapters/application.js | 2 +- core/client/app/adapters/base.js | 23 +- .../app/adapters/embedded-relation-adapter.js | 65 ++--- core/client/app/adapters/setting.js | 8 +- core/client/app/adapters/user.js | 10 +- core/client/app/app.js | 8 +- core/client/app/assets/lib/uploader.js | 125 ++++---- core/client/app/authenticators/oauth2.js | 18 +- .../app/components/gh-activating-list-item.js | 10 +- core/client/app/components/gh-alert.js | 18 +- core/client/app/components/gh-alerts.js | 11 +- core/client/app/components/gh-app.js | 8 +- core/client/app/components/gh-blog-url.js | 7 +- core/client/app/components/gh-cm-editor.js | 20 +- .../client/app/components/gh-content-cover.js | 8 +- .../components/gh-content-preview-content.js | 16 +- .../components/gh-content-view-container.js | 8 +- .../app/components/gh-dropdown-button.js | 8 +- core/client/app/components/gh-dropdown.js | 45 +-- core/client/app/components/gh-ed-editor.js | 24 +- core/client/app/components/gh-ed-preview.js | 34 ++- .../app/components/gh-editor-save-button.js | 24 +- core/client/app/components/gh-editor.js | 79 ++--- .../client/app/components/gh-error-message.js | 20 +- core/client/app/components/gh-file-upload.js | 11 +- .../app/components/gh-infinite-scroll-box.js | 17 +- .../app/components/gh-infinite-scroll.js | 4 +- core/client/app/components/gh-input.js | 4 +- core/client/app/components/gh-main.js | 6 +- core/client/app/components/gh-menu-toggle.js | 33 +-- core/client/app/components/gh-modal-dialog.js | 81 +++--- core/client/app/components/gh-nav-menu.js | 20 +- core/client/app/components/gh-navigation.js | 23 +- .../app/components/gh-navitem-url-input.js | 61 ++-- core/client/app/components/gh-navitem.js | 18 +- core/client/app/components/gh-notification.js | 28 +- .../client/app/components/gh-notifications.js | 9 +- .../app/components/gh-popover-button.js | 18 +- core/client/app/components/gh-popover.js | 4 +- .../app/components/gh-posts-list-item.js | 49 ++-- .../client/app/components/gh-profile-image.js | 52 ++-- core/client/app/components/gh-search-input.js | 146 +++++----- .../client/app/components/gh-select-native.js | 28 +- core/client/app/components/gh-selectize.js | 54 ++-- core/client/app/components/gh-skip-link.js | 10 +- core/client/app/components/gh-spin-button.js | 23 +- core/client/app/components/gh-tab-pane.js | 19 +- core/client/app/components/gh-tab.js | 16 +- core/client/app/components/gh-tabs-manager.js | 21 +- .../app/components/gh-tag-settings-form.js | 52 ++-- .../gh-tags-management-container.js | 25 +- core/client/app/components/gh-textarea.js | 4 +- .../app/components/gh-trim-focus-input.js | 14 +- core/client/app/components/gh-upload-modal.js | 43 +-- core/client/app/components/gh-uploader.js | 44 ++- core/client/app/components/gh-url-preview.js | 24 +- core/client/app/components/gh-user-active.js | 16 +- core/client/app/components/gh-user-invited.js | 46 +-- .../gh-validation-status-container.js | 10 +- core/client/app/components/gh-view-title.js | 7 +- core/client/app/controllers/about.js | 6 +- core/client/app/controllers/application.js | 26 +- core/client/app/controllers/editor/edit.js | 6 +- core/client/app/controllers/editor/new.js | 17 +- core/client/app/controllers/error.js | 15 +- core/client/app/controllers/feature.js | 30 +- .../app/controllers/modals/copy-html.js | 7 +- .../app/controllers/modals/delete-all.js | 44 +-- .../app/controllers/modals/delete-post.js | 49 ++-- .../app/controllers/modals/delete-tag.js | 4 +- .../app/controllers/modals/delete-user.js | 65 ++--- .../app/controllers/modals/invite-new-user.js | 81 +++--- .../app/controllers/modals/leave-editor.js | 41 +-- core/client/app/controllers/modals/signin.js | 47 ++- .../app/controllers/modals/transfer-owner.js | 83 +++--- core/client/app/controllers/modals/upload.js | 16 +- .../app/controllers/post-settings-menu.js | 272 +++++++++--------- core/client/app/controllers/posts.js | 37 +-- core/client/app/controllers/reset.js | 50 ++-- .../controllers/settings/code-injection.js | 12 +- .../app/controllers/settings/general.js | 61 ++-- core/client/app/controllers/settings/labs.js | 76 ++--- .../app/controllers/settings/navigation.js | 92 +++--- core/client/app/controllers/settings/tags.js | 10 +- .../app/controllers/settings/tags/tag.js | 14 +- core/client/app/controllers/setup.js | 17 +- core/client/app/controllers/setup/three.js | 122 ++++---- core/client/app/controllers/setup/two.js | 87 +++--- core/client/app/controllers/signin.js | 84 +++--- core/client/app/controllers/signup.js | 66 ++--- core/client/app/controllers/team/index.js | 15 +- core/client/app/controllers/team/user.js | 134 +++++---- .../client/app/helpers/gh-count-characters.js | 11 +- .../app/helpers/gh-count-down-characters.js | 14 +- core/client/app/helpers/gh-count-words.js | 11 +- core/client/app/helpers/gh-format-html.js | 8 +- core/client/app/helpers/gh-format-markdown.js | 12 +- core/client/app/helpers/gh-format-timeago.js | 6 +- core/client/app/helpers/gh-path.js | 12 +- core/client/app/helpers/gh-user-can-admin.js | 5 +- core/client/app/helpers/is-equal.js | 6 +- core/client/app/helpers/is-not.js | 4 +- core/client/app/helpers/read-path.js | 10 +- .../app/initializers/ember-simple-auth.js | 4 +- .../app/initializers/trailing-history.js | 10 +- .../instance-initializers/oauth-prefilter.js | 10 +- core/client/app/mirage/config.js | 25 +- core/client/app/mirage/fixtures/roles.js | 1 + core/client/app/mirage/fixtures/settings.js | 1 + core/client/app/mixins/body-event-listener.js | 32 ++- .../app/mixins/current-user-settings.js | 18 +- core/client/app/mixins/dropdown-mixin.js | 7 +- core/client/app/mixins/ed-editor-api.js | 32 ++- core/client/app/mixins/ed-editor-scroll.js | 55 ++-- core/client/app/mixins/ed-editor-shortcuts.js | 65 ++--- .../app/mixins/editor-base-controller.js | 158 +++++----- core/client/app/mixins/editor-base-route.js | 49 ++-- core/client/app/mixins/infinite-scroll.js | 20 +- core/client/app/mixins/pagination-route.js | 62 ++-- .../app/mixins/settings-menu-controller.js | 16 +- core/client/app/mixins/settings-save.js | 12 +- core/client/app/mixins/shortcuts-route.js | 37 +-- core/client/app/mixins/slug-url.js | 2 +- core/client/app/mixins/style-body.js | 26 +- core/client/app/mixins/text-input.js | 8 +- core/client/app/mixins/validation-engine.js | 59 ++-- core/client/app/mixins/validation-state.js | 17 +- core/client/app/models/notification.js | 12 +- core/client/app/models/post.js | 81 +++--- core/client/app/models/role.js | 22 +- core/client/app/models/setting.js | 37 +-- core/client/app/models/slug-generator.js | 20 +- core/client/app/models/tag.js | 33 ++- core/client/app/models/user.js | 139 ++++----- core/client/app/router.js | 8 +- core/client/app/routes/about.js | 17 +- core/client/app/routes/application.js | 75 ++--- core/client/app/routes/authenticated.js | 4 +- core/client/app/routes/editor/edit.js | 28 +- core/client/app/routes/editor/index.js | 6 +- core/client/app/routes/editor/new.js | 23 +- core/client/app/routes/error404.js | 6 +- core/client/app/routes/mobile-index-route.js | 15 +- core/client/app/routes/posts.js | 41 ++- core/client/app/routes/posts/index.js | 28 +- core/client/app/routes/posts/post.js | 25 +- core/client/app/routes/reset.js | 16 +- .../app/routes/settings/code-injection.js | 8 +- core/client/app/routes/settings/general.js | 8 +- core/client/app/routes/settings/labs.js | 6 +- core/client/app/routes/settings/navigation.js | 10 +- core/client/app/routes/settings/tags.js | 30 +- core/client/app/routes/settings/tags/index.js | 7 +- core/client/app/routes/settings/tags/new.js | 6 +- core/client/app/routes/settings/tags/tag.js | 7 +- core/client/app/routes/setup.js | 23 +- core/client/app/routes/setup/index.js | 6 +- core/client/app/routes/setup/one.js | 40 ++- core/client/app/routes/setup/three.js | 6 +- core/client/app/routes/signin.js | 19 +- core/client/app/routes/signout.js | 8 +- core/client/app/routes/signup.js | 48 ++-- core/client/app/routes/team/index.js | 10 +- core/client/app/routes/team/user.js | 31 +- core/client/app/serializers/application.js | 10 +- core/client/app/serializers/post.js | 25 +- core/client/app/serializers/setting.js | 24 +- core/client/app/serializers/tag.js | 7 +- core/client/app/serializers/user.js | 20 +- core/client/app/services/config.js | 18 +- core/client/app/services/dropdown.js | 10 +- core/client/app/services/ghost-paths.js | 4 +- core/client/app/services/media-queries.js | 16 +- core/client/app/services/notifications.js | 84 +++--- core/client/app/services/session.js | 8 +- core/client/app/transforms/moment-date.js | 11 +- core/client/app/transforms/raw.js | 4 +- core/client/app/utils/ajax.js | 14 +- core/client/app/utils/bind.js | 8 +- core/client/app/utils/bound-one-way.js | 13 +- core/client/app/utils/caja-sanitizers.js | 11 +- core/client/app/utils/date-formatting.js | 47 ++- core/client/app/utils/document-title.js | 18 +- core/client/app/utils/ed-image-manager.js | 19 +- core/client/app/utils/editor-shortcuts.js | 18 +- core/client/app/utils/ghost-paths.js | 48 ++-- core/client/app/utils/isNumber.js | 2 +- core/client/app/utils/link-component.js | 10 +- core/client/app/utils/random-password.js | 4 +- core/client/app/utils/set-scroll-classname.js | 6 +- core/client/app/utils/text-field.js | 4 +- core/client/app/utils/titleize.js | 8 +- core/client/app/utils/validator-extensions.js | 6 +- core/client/app/utils/window-proxy.js | 4 +- core/client/app/utils/word-count.js | 3 +- core/client/app/validators/base.js | 13 +- core/client/app/validators/nav-item.js | 34 ++- core/client/app/validators/new-user.js | 14 +- core/client/app/validators/post.js | 12 +- core/client/app/validators/reset.js | 7 +- core/client/app/validators/setting.js | 21 +- core/client/app/validators/setup.js | 4 +- core/client/app/validators/signin.js | 14 +- core/client/app/validators/tag-settings.js | 20 +- core/client/app/validators/user.js | 39 ++- core/client/config/environment.js | 2 +- core/client/ember-cli-build.js | 1 + core/client/lib/asset-delivery/index.js | 1 + core/client/package.json | 2 +- core/client/tests/.jscsrc | 4 + .../tests/acceptance/authentication-test.js | 15 +- .../acceptance/settings/navigation-test.js | 9 +- .../tests/acceptance/settings/tags-test.js | 66 ++--- core/client/tests/acceptance/setup-test.js | 4 +- core/client/tests/helpers/resolver.js | 2 +- core/client/tests/helpers/start-app.js | 11 +- .../integration/components/gh-alerts-test.js | 14 +- .../components/gh-navigation-test.js | 6 +- .../integration/components/gh-navitem-test.js | 8 +- .../components/gh-navitem-url-input-test.js | 89 ++++-- .../components/gh-notifications-test.js | 12 +- .../components/gh-profile-image-test.js | 31 +- .../components/gh-tag-settings-form-test.js | 20 +- .../tests/unit/components/gh-alert-test.js | 6 +- .../tests/unit/components/gh-app-test.js | 2 +- .../gh-content-preview-content-test.js | 2 +- .../components/gh-editor-save-button-test.js | 2 +- .../tests/unit/components/gh-editor-test.js | 2 +- .../components/gh-infinite-scroll-box-test.js | 2 +- .../components/gh-infinite-scroll-test.js | 2 +- .../components/gh-navitem-url-input-test.js | 8 +- .../unit/components/gh-notification-test.js | 12 +- .../components/gh-posts-list-item-test.js | 2 +- .../unit/components/gh-search-input-test.js | 2 +- .../unit/components/gh-select-native-test.js | 2 +- .../unit/components/gh-selectize-test.js | 7 +- .../unit/components/gh-spin-button-test.js | 2 +- .../components/gh-trim-focus-input_test.js | 6 +- .../unit/components/gh-url-preview_test.js | 4 +- .../unit/components/gh-user-active-test.js | 2 +- .../unit/components/gh-user-invited-test.js | 2 +- .../controllers/post-settings-menu-test.js | 232 ++++++++------- .../unit/controllers/settings/general-test.js | 16 +- .../controllers/settings/navigation-test.js | 65 +++-- .../unit/helpers/gh-user-can-admin-test.js | 32 ++- .../tests/unit/helpers/is-equal-test.js | 2 +- core/client/tests/unit/helpers/is-not-test.js | 2 +- .../tests/unit/helpers/read-path-test.js | 2 +- .../tests/unit/mixins/infinite-scroll-test.js | 4 +- core/client/tests/unit/models/post-test.js | 24 +- core/client/tests/unit/models/role-test.js | 6 +- core/client/tests/unit/models/setting-test.js | 2 +- core/client/tests/unit/models/tag-test.js | 2 +- core/client/tests/unit/models/user-test.js | 18 +- .../client/tests/unit/services/config-test.js | 4 +- .../tests/unit/services/notifications-test.js | 81 +++--- .../tests/unit/utils/ghost-paths-test.js | 8 +- .../tests/unit/validators/nav-item-test.js | 11 +- .../unit/validators/tag-settings-test.js | 91 +++--- package.json | 2 +- 262 files changed, 3560 insertions(+), 3159 deletions(-) create mode 100644 core/client/.jscsrc create mode 100644 core/client/tests/.jscsrc diff --git a/Gruntfile.js b/Gruntfile.js index ddddc5e3ee..8e90bcd1ca 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -126,8 +126,7 @@ var _ = require('lodash'), client: { options: { - esnext: true, - disallowObjectController: true + config: 'core/client/.jscsrc' }, files: { @@ -135,6 +134,7 @@ var _ = require('lodash'), 'core/client/**/*.js', '!core/client/node_modules/**/*.js', '!core/client/bower_components/**/*.js', + '!core/client/tests/**/*.js', '!core/client/tmp/**/*.js', '!core/client/dist/**/*.js', '!core/client/vendor/**/*.js' @@ -142,6 +142,18 @@ var _ = require('lodash'), } }, + client_tests: { + options: { + config: 'core/client/tests/.jscsrc' + }, + + files: { + src: [ + 'core/client/tests/**/*.js' + ] + } + }, + server: { files: { src: [ diff --git a/core/client/.jscsrc b/core/client/.jscsrc new file mode 100644 index 0000000000..15197078ce --- /dev/null +++ b/core/client/.jscsrc @@ -0,0 +1,14 @@ +{ + "preset": "ember-suave", + "validateIndentation": 4, + "disallowSpacesInFunction": null, + "disallowSpacesInNamedFunctionExpression": { + "beforeOpeningRoundBrace": true + }, + "disallowSpacesInFunctionDeclaration": { + "beforeOpeningRoundBrace": true + }, + "disallowSpacesInsideObjectBrackets": "all", + "requireCommentsToIncludeAccess": null, + "requireSpacesInsideObjectBrackets": null +} diff --git a/core/client/app/adapters/application.js b/core/client/app/adapters/application.js index 42d45faf65..924f2e53c1 100644 --- a/core/client/app/adapters/application.js +++ b/core/client/app/adapters/application.js @@ -2,7 +2,7 @@ import EmbeddedRelationAdapter from 'ghost/adapters/embedded-relation-adapter'; export default EmbeddedRelationAdapter.extend({ - shouldBackgroundReloadRecord: function () { + shouldBackgroundReloadRecord() { return false; } diff --git a/core/client/app/adapters/base.js b/core/client/app/adapters/base.js index d1de3edea5..88066723a9 100644 --- a/core/client/app/adapters/base.js +++ b/core/client/app/adapters/base.js @@ -1,21 +1,22 @@ +import Ember from 'ember'; import DS from 'ember-data'; import ghostPaths from 'ghost/utils/ghost-paths'; -import Ember from 'ember'; const {inject} = Ember; +const {RESTAdapter} = DS; -export default DS.RESTAdapter.extend({ +export default RESTAdapter.extend({ host: window.location.origin, namespace: ghostPaths().apiRoot.slice(1), session: inject.service('session'), - shouldBackgroundReloadRecord: function () { + shouldBackgroundReloadRecord() { return false; }, - query: function (store, type, query) { - var id; + query(store, type, query) { + let id; if (query.id) { id = query.id; @@ -25,9 +26,9 @@ export default DS.RESTAdapter.extend({ return this.ajax(this.buildURL(type.modelName, id), 'GET', {data: query}); }, - buildURL: function (type, id) { + buildURL(type, id) { // Ensure trailing slashes - var url = this._super(type, id); + let url = this._super(type, id); if (url.slice(-1) !== '/') { url += '/'; @@ -42,15 +43,15 @@ export default DS.RESTAdapter.extend({ // response body for successful DELETEs. // Non-2xx (failure) responses will still work correctly as Ember will turn // them into rejected promises. - deleteRecord: function () { - var response = this._super.apply(this, arguments); + deleteRecord() { + let response = this._super(...arguments); - return response.then(function () { + return response.then(() => { return null; }); }, - handleResponse: function (status) { + handleResponse(status) { if (status === 401) { if (this.get('session.isAuthenticated')) { this.get('session').invalidate(); diff --git a/core/client/app/adapters/embedded-relation-adapter.js b/core/client/app/adapters/embedded-relation-adapter.js index b029435419..f68c5b5c0d 100644 --- a/core/client/app/adapters/embedded-relation-adapter.js +++ b/core/client/app/adapters/embedded-relation-adapter.js @@ -1,6 +1,8 @@ import Ember from 'ember'; import BaseAdapter from 'ghost/adapters/base'; +const {get, isNone} = Ember; + // EmbeddedRelationAdapter will augment the query object in calls made to // DS.Store#findRecord, findAll, query, and queryRecord with the correct "includes" // (?include=relatedType) by introspecting on the provided subclass of the DS.Model. @@ -12,16 +14,16 @@ import BaseAdapter from 'ghost/adapters/base'; // roles: DS.hasMany('role', { embedded: 'always' }) => ?include=roles export default BaseAdapter.extend({ - find: function (store, type, id, snapshot) { + find(store, type, id, snapshot) { return this.ajax(this.buildIncludeURL(store, type.modelName, id, snapshot, 'find'), 'GET'); }, - findRecord: function (store, type, id, snapshot) { + findRecord(store, type, id, snapshot) { return this.ajax(this.buildIncludeURL(store, type.modelName, id, snapshot, 'findRecord'), 'GET'); }, - findAll: function (store, type, sinceToken) { - var query, url; + findAll(store, type, sinceToken) { + let query, url; if (sinceToken) { query = {since: sinceToken}; @@ -32,60 +34,59 @@ export default BaseAdapter.extend({ return this.ajax(url, 'GET', {data: query}); }, - query: function (store, type, query) { + query(store, type, query) { return this._super(store, type, this.buildQuery(store, type.modelName, query)); }, - queryRecord: function (store, type, query) { + queryRecord(store, type, query) { return this._super(store, type, this.buildQuery(store, type.modelName, query)); }, - createRecord: function (store, type, snapshot) { + createRecord(store, type, snapshot) { return this.saveRecord(store, type, snapshot, {method: 'POST'}); }, - updateRecord: function (store, type, snapshot) { - var options = { + updateRecord(store, type, snapshot) { + let options = { method: 'PUT', - id: Ember.get(snapshot, 'id') + id: get(snapshot, 'id') }; return this.saveRecord(store, type, snapshot, options); }, - saveRecord: function (store, type, snapshot, options) { - options = options || {}; + saveRecord(store, type, snapshot, options) { + let _options = options || {}; + let url = this.buildIncludeURL(store, type.modelName, _options.id, snapshot, 'createRecord'); + let payload = this.preparePayload(store, type, snapshot); - var url = this.buildIncludeURL(store, type.modelName, options.id, snapshot, 'createRecord'), - payload = this.preparePayload(store, type, snapshot); - - return this.ajax(url, options.method, payload); + return this.ajax(url, _options.method, payload); }, - preparePayload: function (store, type, snapshot) { - var serializer = store.serializerFor(type.modelName), - payload = {}; + preparePayload(store, type, snapshot) { + let serializer = store.serializerFor(type.modelName); + let payload = {}; serializer.serializeIntoHash(payload, type, snapshot); return {data: payload}; }, - buildIncludeURL: function (store, modelName, id, snapshot, requestType, query) { - var url = this.buildURL(modelName, id, snapshot, requestType, query), - includes = this.getEmbeddedRelations(store, modelName); + buildIncludeURL(store, modelName, id, snapshot, requestType, query) { + let includes = this.getEmbeddedRelations(store, modelName); + let url = this.buildURL(modelName, id, snapshot, requestType, query); if (includes.length) { - url += '?include=' + includes.join(','); + url += `?include=${includes.join(',')}`; } return url; }, - buildQuery: function (store, modelName, options) { - var toInclude = this.getEmbeddedRelations(store, modelName), - query = options || {}, - deDupe = {}; + buildQuery(store, modelName, options) { + let deDupe = {}; + let toInclude = this.getEmbeddedRelations(store, modelName); + let query = options || {}; if (toInclude.length) { // If this is a find by id, build a query object and attach the includes @@ -93,7 +94,7 @@ export default BaseAdapter.extend({ query = {}; query.id = options; query.include = toInclude.join(','); - } else if (typeof options === 'object' || Ember.isNone(options)) { + } else if (typeof options === 'object' || isNone(options)) { // If this is a find all (no existing query object) build one and attach // the includes. // If this is a find with an existing query object then merge the includes @@ -101,7 +102,7 @@ export default BaseAdapter.extend({ query = query || {}; toInclude = toInclude.concat(query.include ? query.include.split(',') : []); - toInclude.forEach(function (include) { + toInclude.forEach((include) => { deDupe[include] = true; }); @@ -112,9 +113,9 @@ export default BaseAdapter.extend({ return query; }, - getEmbeddedRelations: function (store, modelName) { - var model = store.modelFor(modelName), - ret = []; + getEmbeddedRelations(store, modelName) { + let model = store.modelFor(modelName); + let ret = []; // Iterate through the model's relationships and build a list // of those that need to be pulled in via "include" from the API diff --git a/core/client/app/adapters/setting.js b/core/client/app/adapters/setting.js index 97f21a6b36..ccc266e885 100644 --- a/core/client/app/adapters/setting.js +++ b/core/client/app/adapters/setting.js @@ -1,9 +1,9 @@ import ApplicationAdapter from 'ghost/adapters/application'; export default ApplicationAdapter.extend({ - updateRecord: function (store, type, record) { - var data = {}, - serializer = store.serializerFor(type.modelName); + updateRecord(store, type, record) { + let data = {}; + let serializer = store.serializerFor(type.modelName); // remove the fake id that we added onto the model. delete record.id; @@ -14,6 +14,6 @@ export default ApplicationAdapter.extend({ // use the ApplicationAdapter's buildURL method but do not // pass in an id. - return this.ajax(this.buildURL(type.modelName), 'PUT', {data: data}); + return this.ajax(this.buildURL(type.modelName), 'PUT', {data}); } }); diff --git a/core/client/app/adapters/user.js b/core/client/app/adapters/user.js index 89f769ddb9..578f737cc6 100644 --- a/core/client/app/adapters/user.js +++ b/core/client/app/adapters/user.js @@ -2,14 +2,14 @@ import ApplicationAdapter from 'ghost/adapters/application'; import SlugUrl from 'ghost/mixins/slug-url'; export default ApplicationAdapter.extend(SlugUrl, { - find: function (store, type, id) { - return this.findQuery(store, type, {id: id, status: 'all'}); + find(store, type, id) { + return this.findQuery(store, type, {id, status: 'all'}); }, // TODO: This is needed because the API currently expects you to know the // status of the record before retrieving by ID. Quick fix is to always // include status=all in the query - findRecord: function (store, type, id, snapshot) { + findRecord(store, type, id, snapshot) { let url = this.buildIncludeURL(store, type.modelName, id, snapshot, 'findRecord'); url += '&status=all'; @@ -17,7 +17,7 @@ export default ApplicationAdapter.extend(SlugUrl, { return this.ajax(url, 'GET'); }, - findAll: function (store, type, id) { - return this.query(store, type, {id: id, status: 'all'}); + findAll(store, type, id) { + return this.query(store, type, {id, status: 'all'}); } }); diff --git a/core/client/app/app.js b/core/client/app/app.js index 29dea243bf..6c692bf351 100755 --- a/core/client/app/app.js +++ b/core/client/app/app.js @@ -5,12 +5,14 @@ import 'ghost/utils/link-component'; import 'ghost/utils/text-field'; import config from './config/environment'; +const {Application} = Ember; + Ember.MODEL_FACTORY_INJECTIONS = true; -var App = Ember.Application.extend({ +let App = Application.extend({ + Resolver, modulePrefix: config.modulePrefix, - podModulePrefix: config.podModulePrefix, - Resolver: Resolver + podModulePrefix: config.podModulePrefix }); loadInitializers(App, config.modulePrefix); diff --git a/core/client/app/assets/lib/uploader.js b/core/client/app/assets/lib/uploader.js index 4bd8ef1968..e97bca261d 100644 --- a/core/client/app/assets/lib/uploader.js +++ b/core/client/app/assets/lib/uploader.js @@ -1,12 +1,11 @@ import ghostPaths from 'ghost/utils/ghost-paths'; -var UploadUi, - Ghost = ghostPaths(); +let Ghost = ghostPaths(); -UploadUi = function ($dropzone, settings) { - var $url = '
', - $cancel = '', - $progress = $('
', { +let UploadUi = function ($dropzone, settings) { + let $url = '
'; + let $cancel = ''; + let $progress = $('
', { class: 'js-upload-progress progress progress-success active', role: 'progressbar', 'aria-valuemin': '0', @@ -17,38 +16,36 @@ UploadUi = function ($dropzone, settings) { })); $.extend(this, { - complete: function (result) { - var self = this; - + complete(result) { function showImage(width, height) { - $dropzone.find('img.js-upload-target').attr({width: width, height: height}).css({display: 'block'}); + $dropzone.find('img.js-upload-target').attr({width, height}).css({display: 'block'}); $dropzone.find('.fileupload-loading').remove(); $dropzone.css({height: 'auto'}); - $dropzone.delay(250).animate({opacity: 100}, 1000, function () { + $dropzone.delay(250).animate({opacity: 100}, 1000, () => { $('.js-button-accept').prop('disabled', false); - self.init(); + this.init(); }); } function animateDropzone($img) { - $dropzone.animate({opacity: 0}, 250, function () { + $dropzone.animate({opacity: 0}, 250, () => { $dropzone.removeClass('image-uploader').addClass('pre-image-uploader'); $dropzone.css({minHeight: 0}); - self.removeExtras(); - $dropzone.animate({height: $img.height()}, 250, function () { + this.removeExtras(); + $dropzone.animate({height: $img.height()}, 250, () => { showImage($img.width(), $img.height()); }); }); } function preLoadImage() { - var $img = $dropzone.find('img.js-upload-target') + let $img = $dropzone.find('img.js-upload-target') .attr({src: '', width: 'auto', height: 'auto'}); - $progress.animate({opacity: 0}, 250, function () { - $dropzone.find('span.media').after(''); + $progress.animate({opacity: 0}, 250, () => { + $dropzone.find('span.media').after(``); }); - $img.one('load', function () { + $img.one('load', () => { $dropzone.trigger('uploadsuccess', [result]); animateDropzone($img); }).attr('src', result); @@ -56,12 +53,10 @@ UploadUi = function ($dropzone, settings) { preLoadImage(); }, - bindFileUpload: function () { - var self = this; - + bindFileUpload() { $dropzone.find('.js-fileupload').fileupload().fileupload('option', { - url: Ghost.apiRoot + '/uploads/', - add: function (e, data) { + url: `${Ghost.apiRoot}/uploads/`, + add(e, data) { /*jshint unused:false*/ $('.js-button-accept').prop('disabled', true); $dropzone.find('.js-fileupload').removeClass('right'); @@ -69,7 +64,7 @@ UploadUi = function ($dropzone, settings) { $progress.find('.js-upload-progress-bar').removeClass('fail'); $dropzone.trigger('uploadstart', [$dropzone.attr('id')]); $dropzone.find('span.media, div.description, a.image-url, a.image-webcam') - .animate({opacity: 0}, 250, function () { + .animate({opacity: 0}, 250, () => { $dropzone.find('div.description').hide().css({opacity: 100}); if (settings.progressbar) { $dropzone.find('div.js-fail').after($progress); @@ -79,15 +74,15 @@ UploadUi = function ($dropzone, settings) { }); }, dropZone: settings.fileStorage ? $dropzone : null, - progressall: function (e, data) { + progressall(e, data) { /*jshint unused:false*/ - var progress = parseInt(data.loaded / data.total * 100, 10); + let progress = parseInt(data.loaded / data.total * 100, 10); if (settings.progressbar) { $dropzone.trigger('uploadprogress', [progress, data]); - $progress.find('.js-upload-progress-bar').css('width', progress + '%'); + $progress.find('.js-upload-progress-bar').css('width', `${progress}%`); } }, - fail: function (e, data) { + fail(e, data) { /*jshint unused:false*/ $('.js-button-accept').prop('disabled', false); $dropzone.trigger('uploadfailure', [data.result]); @@ -100,21 +95,21 @@ UploadUi = function ($dropzone, settings) { $dropzone.find('div.js-fail').text('Something went wrong :('); } $dropzone.find('div.js-fail, button.js-fail').fadeIn(1500); - $dropzone.find('button.js-fail').on('click', function () { + $dropzone.find('button.js-fail').on('click', () => { $dropzone.css({minHeight: 0}); $dropzone.find('div.description').show(); - self.removeExtras(); - self.init(); + this.removeExtras(); + this.init(); }); }, - done: function (e, data) { + done(e, data) { /*jshint unused:false*/ - self.complete(data.result); + this.complete(data.result); } }); }, - buildExtras: function () { + buildExtras() { if (!$dropzone.find('span.media')[0]) { $dropzone.prepend(''); } @@ -135,13 +130,11 @@ UploadUi = function ($dropzone, settings) { // } }, - removeExtras: function () { + removeExtras() { $dropzone.find('span.media, div.js-upload-progress, a.image-url, a.image-upload, a.image-webcam, div.js-fail, button.js-fail, a.js-cancel').remove(); }, - initWithDropzone: function () { - var self = this; - + initWithDropzone() { // This is the start point if no image exists $dropzone.find('img.js-upload-target').css({display: 'none'}); $dropzone.find('div.description').show(); @@ -150,24 +143,23 @@ UploadUi = function ($dropzone, settings) { this.buildExtras(); this.bindFileUpload(); if (!settings.fileStorage) { - self.initUrl(); + this.initUrl(); return; } - $dropzone.find('a.image-url').on('click', function () { - self.initUrl(); + $dropzone.find('a.image-url').on('click', () => { + this.initUrl(); }); }, - initUrl: function () { - var self = this, val; + initUrl() { this.removeExtras(); $dropzone.addClass('image-uploader-url').removeClass('pre-image-uploader'); $dropzone.find('.js-fileupload').addClass('right'); - $dropzone.find('.js-cancel').on('click', function () { + $dropzone.find('.js-cancel').on('click', () => { $dropzone.find('.js-url').remove(); $dropzone.find('.js-fileupload').removeClass('right'); $dropzone.trigger('imagecleared'); - self.removeExtras(); - self.initWithDropzone(); + this.removeExtras(); + this.initWithDropzone(); }); $dropzone.find('div.description').before($url); @@ -177,16 +169,17 @@ UploadUi = function ($dropzone, settings) { $dropzone.find('div.description').hide(); } - $dropzone.find('.js-button-accept').on('click', function () { - val = $dropzone.find('.js-upload-url').val(); + $dropzone.find('.js-button-accept').on('click', () => { + let val = $dropzone.find('.js-upload-url').val(); + $dropzone.find('div.description').hide(); $dropzone.find('.js-fileupload').removeClass('right'); $dropzone.find('.js-url').remove(); if (val === '') { $dropzone.trigger('uploadsuccess', 'http://'); - self.initWithDropzone(); + this.initWithDropzone(); } else { - self.complete(val); + this.complete(val); } }); @@ -195,37 +188,35 @@ UploadUi = function ($dropzone, settings) { $dropzone.append(''); } - $dropzone.find('a.image-upload').on('click', function () { + $dropzone.find('a.image-upload').on('click', () => { $dropzone.find('.js-url').remove(); $dropzone.find('.js-fileupload').removeClass('right'); - self.initWithDropzone(); + this.initWithDropzone(); }); }, - initWithImage: function () { - var self = this; - + initWithImage() { // This is the start point if an image already exists this.removeExtras(); $dropzone.removeClass('image-uploader image-uploader-url').addClass('pre-image-uploader'); $dropzone.find('div.description').hide(); $dropzone.find('img.js-upload-target').show(); $dropzone.append($cancel); - $dropzone.find('.js-cancel').on('click', function () { + $dropzone.find('.js-cancel').on('click', () => { $dropzone.find('img.js-upload-target').attr({src: ''}); $dropzone.find('div.description').show(); $dropzone.trigger('imagecleared'); - $dropzone.delay(250).animate({opacity: 100}, 1000, function () { - self.init(); + $dropzone.delay(250).animate({opacity: 100}, 1000, () => { + this.init(); }); $dropzone.trigger('uploadsuccess', 'http://'); - self.initWithDropzone(); + this.initWithDropzone(); }); }, - init: function () { - var imageTarget = $dropzone.find('img.js-upload-target'); + init() { + let imageTarget = $dropzone.find('img.js-upload-target'); // First check if field image is defined by checking for js-upload-target class if (!imageTarget[0]) { // This ensures there is an image we can hook into to display uploaded image @@ -239,7 +230,7 @@ UploadUi = function ($dropzone, settings) { } }, - reset: function () { + reset() { $dropzone.find('.js-url').remove(); $dropzone.find('.js-fileupload').removeClass('right'); this.removeExtras(); @@ -249,17 +240,15 @@ UploadUi = function ($dropzone, settings) { }; export default function (options) { - var settings = $.extend({ + let settings = $.extend({ progressbar: true, editor: false, fileStorage: true }, options); return this.each(function () { - var $dropzone = $(this), - ui; - - ui = new UploadUi($dropzone, settings); + let $dropzone = $(this); + let ui = new UploadUi($dropzone, settings); $(this).attr('data-uploaderui', true); this.uploaderUi = ui; ui.init(); diff --git a/core/client/app/authenticators/oauth2.js b/core/client/app/authenticators/oauth2.js index 03b293ca26..a75cc62cba 100644 --- a/core/client/app/authenticators/oauth2.js +++ b/core/client/app/authenticators/oauth2.js @@ -1,21 +1,25 @@ import Ember from 'ember'; import Authenticator from 'ember-simple-auth/authenticators/oauth2-password-grant'; +const {computed, inject} = Ember; + export default Authenticator.extend({ - config: Ember.inject.service(), - ghostPaths: Ember.inject.service('ghost-paths'), + config: inject.service(), + ghostPaths: inject.service('ghost-paths'), - serverTokenEndpoint: Ember.computed('ghostPaths.apiRoot', function () { - return this.get('ghostPaths.apiRoot') + '/authentication/token'; + serverTokenEndpoint: computed('ghostPaths.apiRoot', function () { + return `${this.get('ghostPaths.apiRoot')}/authentication/token`; }), - serverTokenRevocationEndpoint: Ember.computed('ghostPaths.apiRoot', function () { - return this.get('ghostPaths.apiRoot') + '/authentication/revoke'; + serverTokenRevocationEndpoint: computed('ghostPaths.apiRoot', function () { + return `${this.get('ghostPaths.apiRoot')}/authentication/revoke`; }), - makeRequest: function (url, data) { + makeRequest(url, data) { + /* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */ data.client_id = this.get('config.clientId'); data.client_secret = this.get('config.clientSecret'); + /* jscs:enable requireCamelCaseOrUpperCaseIdentifiers */ return this._super(url, data); } }); diff --git a/core/client/app/components/gh-activating-list-item.js b/core/client/app/components/gh-activating-list-item.js index 57ca5e4d53..ee3e480ed4 100644 --- a/core/client/app/components/gh-activating-list-item.js +++ b/core/client/app/components/gh-activating-list-item.js @@ -1,18 +1,20 @@ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component, on, run} = Ember; + +export default Component.extend({ tagName: 'li', classNameBindings: ['active'], active: false, linkClasses: null, - unfocusLink: Ember.on('click', function () { + unfocusLink: on('click', function () { this.$('a').blur(); }), actions: { - setActive: function (value) { - Ember.run.schedule('afterRender', this, function () { + setActive(value) { + run.schedule('afterRender', this, function () { this.set('active', value); }); } diff --git a/core/client/app/components/gh-alert.js b/core/client/app/components/gh-alert.js index d10842701b..2e67d7808b 100644 --- a/core/client/app/components/gh-alert.js +++ b/core/client/app/components/gh-alert.js @@ -1,16 +1,18 @@ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component, computed, inject} = Ember; + +export default Component.extend({ tagName: 'article', classNames: ['gh-alert'], classNameBindings: ['typeClass'], - notifications: Ember.inject.service(), + notifications: inject.service(), - typeClass: Ember.computed('message.type', function () { - var classes = '', - type = this.get('message.type'), - typeMapping; + typeClass: computed('message.type', function () { + let type = this.get('message.type'); + let classes = ''; + let typeMapping; typeMapping = { success: 'green', @@ -20,14 +22,14 @@ export default Ember.Component.extend({ }; if (typeMapping[type] !== undefined) { - classes += 'gh-alert-' + typeMapping[type]; + classes += `gh-alert-${typeMapping[type]}`; } return classes; }), actions: { - closeNotification: function () { + closeNotification() { this.get('notifications').closeNotification(this.get('message')); } } diff --git a/core/client/app/components/gh-alerts.js b/core/client/app/components/gh-alerts.js index 6e04c4dfb4..00c19e17a5 100644 --- a/core/client/app/components/gh-alerts.js +++ b/core/client/app/components/gh-alerts.js @@ -1,14 +1,17 @@ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component, computed, inject, observer} = Ember; +const {alias} = computed; + +export default Component.extend({ tagName: 'aside', classNames: 'gh-alerts', - notifications: Ember.inject.service(), + notifications: inject.service(), - messages: Ember.computed.alias('notifications.alerts'), + messages: alias('notifications.alerts'), - messageCountObserver: Ember.observer('messages.[]', function () { + messageCountObserver: observer('messages.[]', function () { this.sendAction('notify', this.get('messages').length); }) }); diff --git a/core/client/app/components/gh-app.js b/core/client/app/components/gh-app.js index 3b93f2ed45..0fa2af8b83 100644 --- a/core/client/app/components/gh-app.js +++ b/core/client/app/components/gh-app.js @@ -1,12 +1,14 @@ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component, observer} = Ember; + +export default Component.extend({ classNames: ['gh-app'], showSettingsMenu: false, - toggleSettingsMenuBodyClass: Ember.observer('showSettingsMenu', function () { - var showSettingsMenu = this.get('showSettingsMenu'); + toggleSettingsMenuBodyClass: observer('showSettingsMenu', function () { + let showSettingsMenu = this.get('showSettingsMenu'); Ember.$('body').toggleClass('settings-menu-expanded', showSettingsMenu); }) diff --git a/core/client/app/components/gh-blog-url.js b/core/client/app/components/gh-blog-url.js index 6762549159..ec0b873c80 100644 --- a/core/client/app/components/gh-blog-url.js +++ b/core/client/app/components/gh-blog-url.js @@ -1,6 +1,9 @@ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component, inject} = Ember; + +export default Component.extend({ tagName: '', - config: Ember.inject.service() + + config: inject.service() }); diff --git a/core/client/app/components/gh-cm-editor.js b/core/client/app/components/gh-cm-editor.js index 281476060c..33f7aa2f1b 100644 --- a/core/client/app/components/gh-cm-editor.js +++ b/core/client/app/components/gh-cm-editor.js @@ -1,14 +1,13 @@ /* global CodeMirror */ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component} = Ember; - // DOM stuff +export default Component.extend({ classNameBindings: ['isFocused:focused'], - isFocused: false, value: '', // make sure a value exists - _editor: null, // reference to CodeMirror editor + isFocused: false, // options for the editor lineNumbers: true, @@ -16,9 +15,11 @@ export default Ember.Component.extend({ mode: 'htmlmixed', theme: 'xq-light', - didInsertElement: function () { - var options = this.getProperties('lineNumbers', 'indentUnit', 'mode', 'theme'), - editor = new CodeMirror(this.get('element'), options); + _editor: null, // reference to CodeMirror editor + + didInsertElement() { + let options = this.getProperties('lineNumbers', 'indentUnit', 'mode', 'theme'); + let editor = new CodeMirror(this.get('element'), options); editor.getDoc().setValue(this.get('value')); @@ -34,10 +35,9 @@ export default Ember.Component.extend({ this._editor = editor; }, - willDestroyElement: function () { - var editor = this._editor.getWrapperElement(); + willDestroyElement() { + let editor = this._editor.getWrapperElement(); editor.parentNode.removeChild(editor); this._editor = null; } - }); diff --git a/core/client/app/components/gh-content-cover.js b/core/client/app/components/gh-content-cover.js index 167d5cdf2b..05ec3b4a1a 100644 --- a/core/client/app/components/gh-content-cover.js +++ b/core/client/app/components/gh-content-cover.js @@ -12,17 +12,19 @@ Example: import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component} = Ember; + +export default Component.extend({ classNames: ['content-cover'], onClick: null, onMouseEnter: null, - click: function () { + click() { this.sendAction('onClick'); }, - mouseEnter: function () { + mouseEnter() { this.sendAction('onMouseEnter'); } }); diff --git a/core/client/app/components/gh-content-preview-content.js b/core/client/app/components/gh-content-preview-content.js index 0008ba0a16..11d4779ca4 100644 --- a/core/client/app/components/gh-content-preview-content.js +++ b/core/client/app/components/gh-content-preview-content.js @@ -1,21 +1,23 @@ import Ember from 'ember'; import setScrollClassName from 'ghost/utils/set-scroll-classname'; -export default Ember.Component.extend({ +const {Component, run} = Ember; + +export default Component.extend({ classNames: ['content-preview-content'], content: null, - didInsertElement: function () { - var el = this.$(); + didInsertElement() { + let el = this.$(); - el.on('scroll', Ember.run.bind(el, setScrollClassName, { + el.on('scroll', run.bind(el, setScrollClassName, { target: el.closest('.content-preview'), offset: 10 })); }, - didReceiveAttrs: function (options) { + didReceiveAttrs(options) { // adjust when didReceiveAttrs gets both newAttrs and oldAttrs if (options.newAttrs.content && this.get('content') !== options.newAttrs.content.value) { let el = this.$(); @@ -26,8 +28,8 @@ export default Ember.Component.extend({ } }, - willDestroyElement: function () { - var el = this.$(); + willDestroyElement() { + let el = this.$(); el.off('scroll'); } diff --git a/core/client/app/components/gh-content-view-container.js b/core/client/app/components/gh-content-view-container.js index 7dd5602437..498ae03f0d 100644 --- a/core/client/app/components/gh-content-view-container.js +++ b/core/client/app/components/gh-content-view-container.js @@ -1,9 +1,11 @@ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component, computed, inject} = Ember; + +export default Component.extend({ tagName: 'section', classNames: ['gh-view', 'content-view-container'], - mediaQueries: Ember.inject.service(), - previewIsHidden: Ember.computed.reads('mediaQueries.maxWidth900') + mediaQueries: inject.service(), + previewIsHidden: computed.reads('mediaQueries.maxWidth900') }); diff --git a/core/client/app/components/gh-dropdown-button.js b/core/client/app/components/gh-dropdown-button.js index d0f982e099..8f7abc3f8a 100644 --- a/core/client/app/components/gh-dropdown-button.js +++ b/core/client/app/components/gh-dropdown-button.js @@ -1,7 +1,9 @@ import Ember from 'ember'; import DropdownMixin from 'ghost/mixins/dropdown-mixin'; -export default Ember.Component.extend(DropdownMixin, { +const {Component, inject} = Ember; + +export default Component.extend(DropdownMixin, { tagName: 'button', attributeBindings: 'role', role: 'button', @@ -9,10 +11,10 @@ export default Ember.Component.extend(DropdownMixin, { // matches with the dropdown this button toggles dropdownName: null, - dropdown: Ember.inject.service(), + dropdown: inject.service(), // Notify dropdown service this dropdown should be toggled - click: function (event) { + click(event) { this._super(event); this.get('dropdown').toggleDropdown(this.get('dropdownName'), this); } diff --git a/core/client/app/components/gh-dropdown.js b/core/client/app/components/gh-dropdown.js index 1bba5b3a99..192412d76d 100644 --- a/core/client/app/components/gh-dropdown.js +++ b/core/client/app/components/gh-dropdown.js @@ -1,7 +1,9 @@ import Ember from 'ember'; import DropdownMixin from 'ghost/mixins/dropdown-mixin'; -export default Ember.Component.extend(DropdownMixin, { +const {Component, computed, inject} = Ember; + +export default Component.extend(DropdownMixin, { classNames: 'dropdown', classNameBindings: ['fadeIn:fade-in-scale:fade-out', 'isOpen:open:closed'], @@ -15,29 +17,28 @@ export default Ember.Component.extend(DropdownMixin, { isOpen: false, // Managed the toggle between the fade-in and fade-out classes - fadeIn: Ember.computed('isOpen', 'closing', function () { + fadeIn: computed('isOpen', 'closing', function () { return this.get('isOpen') && !this.get('closing'); }), - dropdown: Ember.inject.service(), + dropdown: inject.service(), - open: function () { + open() { this.set('isOpen', true); this.set('closing', false); this.set('button.isOpen', true); }, - close: function () { - var self = this; - + close() { this.set('closing', true); if (this.get('button')) { this.set('button.isOpen', false); } - this.$().on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', function (event) { + + this.$().on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', (event) => { if (event.originalEvent.animationName === 'fade-out') { - Ember.run(self, function () { + Ember.run(this, function () { if (this.get('closing')) { this.set('isOpen', false); this.set('closing', false); @@ -48,12 +49,12 @@ export default Ember.Component.extend(DropdownMixin, { }, // Called by the dropdown service when any dropdown button is clicked. - toggle: function (options) { - var isClosing = this.get('closing'), - isOpen = this.get('isOpen'), - name = this.get('name'), - button = this.get('button'), - targetDropdownName = options.target; + toggle(options) { + let isClosing = this.get('closing'); + let isOpen = this.get('isOpen'); + let name = this.get('name'); + let targetDropdownName = options.target; + let button = this.get('button'); if (name === targetDropdownName && (!isOpen || isClosing)) { if (!button) { @@ -66,7 +67,7 @@ export default Ember.Component.extend(DropdownMixin, { } }, - click: function (event) { + click(event) { this._super(event); if (this.get('closeOnClick')) { @@ -74,19 +75,19 @@ export default Ember.Component.extend(DropdownMixin, { } }, - didInsertElement: function () { - this._super(); + didInsertElement() { + let dropdownService = this.get('dropdown'); - var dropdownService = this.get('dropdown'); + this._super(...arguments); dropdownService.on('close', this, this.close); dropdownService.on('toggle', this, this.toggle); }, - willDestroyElement: function () { - this._super(); + willDestroyElement() { + let dropdownService = this.get('dropdown'); - var dropdownService = this.get('dropdown'); + this._super(...arguments); dropdownService.off('close', this, this.close); dropdownService.off('toggle', this, this.toggle); diff --git a/core/client/app/components/gh-ed-editor.js b/core/client/app/components/gh-ed-editor.js index 3c17124ef2..e0385f9105 100644 --- a/core/client/app/components/gh-ed-editor.js +++ b/core/client/app/components/gh-ed-editor.js @@ -3,20 +3,22 @@ import EditorAPI from 'ghost/mixins/ed-editor-api'; import EditorShortcuts from 'ghost/mixins/ed-editor-shortcuts'; import EditorScroll from 'ghost/mixins/ed-editor-scroll'; -export default Ember.TextArea.extend(EditorAPI, EditorShortcuts, EditorScroll, { +const {TextArea, run} = Ember; + +export default TextArea.extend(EditorAPI, EditorShortcuts, EditorScroll, { focus: false, /** * Tell the controller about focusIn events, will trigger an autosave on a new document */ - focusIn: function () { + focusIn() { this.sendAction('onFocusIn'); }, /** * Sets the focus of the textarea if needed */ - setFocus: function () { + setFocus() { if (this.get('focus')) { this.$().val(this.$().val()).focus(); } @@ -25,17 +27,17 @@ export default Ember.TextArea.extend(EditorAPI, EditorShortcuts, EditorScroll, { /** * Sets up properties at render time */ - didInsertElement: function () { - this._super(); + didInsertElement() { + this._super(...arguments); this.setFocus(); this.sendAction('setEditor', this); - Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent); + run.scheduleOnce('afterRender', this, this.afterRenderEvent); }, - afterRenderEvent: function () { + afterRenderEvent() { if (this.get('focus') && this.get('focusCursorAtEnd')) { this.setSelection('end'); } @@ -44,16 +46,16 @@ export default Ember.TextArea.extend(EditorAPI, EditorShortcuts, EditorScroll, { /** * Disable editing in the textarea (used while an upload is in progress) */ - disable: function () { - var textarea = this.get('element'); + disable() { + let textarea = this.get('element'); textarea.setAttribute('readonly', 'readonly'); }, /** * Reenable editing in the textarea */ - enable: function () { - var textarea = this.get('element'); + enable() { + let textarea = this.get('element'); textarea.removeAttribute('readonly'); } }); diff --git a/core/client/app/components/gh-ed-preview.js b/core/client/app/components/gh-ed-preview.js index fb182a30d1..58e32c3e4f 100644 --- a/core/client/app/components/gh-ed-preview.js +++ b/core/client/app/components/gh-ed-preview.js @@ -1,39 +1,43 @@ import Ember from 'ember'; import uploader from 'ghost/assets/lib/uploader'; -export default Ember.Component.extend({ - config: Ember.inject.service(), +const {Component, inject, run} = Ember; + +export default Component.extend({ + config: inject.service(), _scrollWrapper: null, - didInsertElement: function () { + didInsertElement() { this._scrollWrapper = this.$().closest('.entry-preview-content'); this.adjustScrollPosition(this.get('scrollPosition')); - Ember.run.scheduleOnce('afterRender', this, this.dropzoneHandler); + run.scheduleOnce('afterRender', this, this.dropzoneHandler); }, - didReceiveAttrs: function (attrs) { - if (!attrs.oldAttrs) { return; } + didReceiveAttrs(attrs) { + if (!attrs.oldAttrs) { + return; + } if (attrs.newAttrs.scrollPosition && attrs.newAttrs.scrollPosition.value !== attrs.oldAttrs.scrollPosition.value) { this.adjustScrollPosition(attrs.newAttrs.scrollPosition.value); } if (attrs.newAttrs.markdown.value !== attrs.oldAttrs.markdown.value) { - Ember.run.scheduleOnce('afterRender', this, this.dropzoneHandler); + run.scheduleOnce('afterRender', this, this.dropzoneHandler); } }, - adjustScrollPosition: function (scrollPosition) { - var scrollWrapper = this._scrollWrapper; + adjustScrollPosition(scrollPosition) { + let scrollWrapper = this._scrollWrapper; if (scrollWrapper) { scrollWrapper.scrollTop(scrollPosition); } }, - dropzoneHandler: function () { - var dropzones = $('.js-drop-zone[data-uploaderui!="true"]'); + dropzoneHandler() { + let dropzones = $('.js-drop-zone[data-uploaderui!="true"]'); if (dropzones.length) { uploader.call(dropzones, { @@ -41,10 +45,10 @@ export default Ember.Component.extend({ fileStorage: this.get('config.fileStorage') }); - dropzones.on('uploadstart', Ember.run.bind(this, 'sendAction', 'uploadStarted')); - dropzones.on('uploadfailure', Ember.run.bind(this, 'sendAction', 'uploadFinished')); - dropzones.on('uploadsuccess', Ember.run.bind(this, 'sendAction', 'uploadFinished')); - dropzones.on('uploadsuccess', Ember.run.bind(this, 'sendAction', 'uploadSuccess')); + dropzones.on('uploadstart', run.bind(this, 'sendAction', 'uploadStarted')); + dropzones.on('uploadfailure', run.bind(this, 'sendAction', 'uploadFinished')); + dropzones.on('uploadsuccess', run.bind(this, 'sendAction', 'uploadFinished')); + dropzones.on('uploadsuccess', run.bind(this, 'sendAction', 'uploadSuccess')); // Set the current height so we can listen this.sendAction('updateHeight', this.$().height()); diff --git a/core/client/app/components/gh-editor-save-button.js b/core/client/app/components/gh-editor-save-button.js index 5ddc2ca8dd..9b04b131eb 100644 --- a/core/client/app/components/gh-editor-save-button.js +++ b/core/client/app/components/gh-editor-save-button.js @@ -1,6 +1,8 @@ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component, computed} = Ember; + +export default Component.extend({ tagName: 'section', classNames: ['splitbtn', 'js-publish-splitbutton'], classNameBindings: ['isNew:unsaved'], @@ -12,36 +14,36 @@ export default Ember.Component.extend({ submitting: false, // Tracks whether we're going to change the state of the post on save - isDangerous: Ember.computed('isPublished', 'willPublish', function () { + isDangerous: computed('isPublished', 'willPublish', function () { return this.get('isPublished') !== this.get('willPublish'); }), - publishText: Ember.computed('isPublished', 'postOrPage', function () { - return this.get('isPublished') ? 'Update ' + this.get('postOrPage') : 'Publish Now'; + publishText: computed('isPublished', 'postOrPage', function () { + return this.get('isPublished') ? `Update ${this.get('postOrPage')}` : 'Publish Now'; }), - draftText: Ember.computed('isPublished', function () { + draftText: computed('isPublished', function () { return this.get('isPublished') ? 'Unpublish' : 'Save Draft'; }), - deleteText: Ember.computed('postOrPage', function () { - return 'Delete ' + this.get('postOrPage'); + deleteText: computed('postOrPage', function () { + return `Delete ${this.get('postOrPage')}`; }), - saveText: Ember.computed('willPublish', 'publishText', 'draftText', function () { + saveText: computed('willPublish', 'publishText', 'draftText', function () { return this.get('willPublish') ? this.get('publishText') : this.get('draftText'); }), actions: { - save: function () { + save() { this.sendAction('save'); }, - setSaveType: function (saveType) { + setSaveType(saveType) { this.sendAction('setSaveType', saveType); }, - delete: function () { + delete() { this.sendAction('delete'); } } diff --git a/core/client/app/components/gh-editor.js b/core/client/app/components/gh-editor.js index 9e2380a934..49d12afd3f 100644 --- a/core/client/app/components/gh-editor.js +++ b/core/client/app/components/gh-editor.js @@ -1,54 +1,37 @@ import Ember from 'ember'; import setScrollClassName from 'ghost/utils/set-scroll-classname'; -export default Ember.Component.extend({ +const {Component, computed, run} = Ember; +const {equal} = computed; + +export default Component.extend({ tagName: 'section', classNames: ['gh-view'], - scheduleAfterRender: function () { - Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent); - }, - - didInsertElement: function () { - this.scheduleAfterRender(); - }, - - afterRenderEvent: function () { - var $previewViewPort = this.$('.js-entry-preview-content'); - - // cache these elements for use in other methods - this.set('$previewViewPort', $previewViewPort); - this.set('$previewContent', this.$('.js-rendered-markdown')); - - $previewViewPort.on('scroll', Ember.run.bind($previewViewPort, setScrollClassName, { - target: this.$('.js-entry-preview'), - offset: 10 - })); - }, - - willDestroyElement: function () { - // removes scroll handlers from the view - this.get('$previewViewPort').off('scroll'); - }, - // updated when gh-ed-editor component scrolls editorScrollInfo: null, // updated when markdown is rendered height: null, + activeTab: 'markdown', + + markdownActive: equal('activeTab', 'markdown'), + previewActive: equal('activeTab', 'preview'), // HTML Preview listens to scrollPosition and updates its scrollTop value // This property receives scrollInfo from the textEditor, and height from the preview pane, and will update the // scrollPosition value such that when either scrolling or typing-at-the-end of the text editor the preview pane // stays in sync - scrollPosition: Ember.computed('editorScrollInfo', 'height', function () { - if (!this.get('editorScrollInfo') || !this.get('$previewContent') || !this.get('$previewViewPort')) { + scrollPosition: computed('editorScrollInfo', 'height', function () { + let scrollInfo = this.get('editorScrollInfo'); + let $previewContent = this.get('$previewContent'); + let $previewViewPort = this.get('$previewViewPort'); + + if (!scrollInfo || !$previewContent || !$previewViewPort) { return 0; } - var scrollInfo = this.get('editorScrollInfo'), - previewHeight = this.get('$previewContent').height() - this.get('$previewViewPort').height(), - previewPosition, - ratio; + let previewHeight = $previewContent.height() - $previewViewPort.height(); + let previewPosition, ratio; ratio = previewHeight / scrollInfo.diff; previewPosition = scrollInfo.top * ratio; @@ -56,12 +39,34 @@ export default Ember.Component.extend({ return previewPosition; }), - activeTab: 'markdown', - markdownActive: Ember.computed.equal('activeTab', 'markdown'), - previewActive: Ember.computed.equal('activeTab', 'preview'), + scheduleAfterRender() { + run.scheduleOnce('afterRender', this, this.afterRenderEvent); + }, + + didInsertElement() { + this.scheduleAfterRender(); + }, + + afterRenderEvent() { + let $previewViewPort = this.$('.js-entry-preview-content'); + + // cache these elements for use in other methods + this.set('$previewViewPort', $previewViewPort); + this.set('$previewContent', this.$('.js-rendered-markdown')); + + $previewViewPort.on('scroll', run.bind($previewViewPort, setScrollClassName, { + target: this.$('.js-entry-preview'), + offset: 10 + })); + }, + + willDestroyElement() { + // removes scroll handlers from the view + this.get('$previewViewPort').off('scroll'); + }, actions: { - selectTab: function (tab) { + selectTab(tab) { this.set('activeTab', tab); } } diff --git a/core/client/app/components/gh-error-message.js b/core/client/app/components/gh-error-message.js index 1dc4e3b786..281fe3c226 100644 --- a/core/client/app/components/gh-error-message.js +++ b/core/client/app/components/gh-error-message.js @@ -1,5 +1,7 @@ import Ember from 'ember'; +const {Component, computed, isEmpty} = Ember; + /** * Renders one random error message when passed a DS.Errors object * and a property name. The message will be one of the ones associated with @@ -8,23 +10,23 @@ import Ember from 'ember'; * @param {DS.Errors} errors The DS.Errors object * @param {string} property The property name */ -export default Ember.Component.extend({ +export default Component.extend({ tagName: 'p', classNames: ['response'], errors: null, property: '', - isVisible: Ember.computed.notEmpty('errors'), + isVisible: computed.notEmpty('errors'), - message: Ember.computed('errors.[]', 'property', function () { - var property = this.get('property'), - errors = this.get('errors'), - messages = [], - index; + message: computed('errors.[]', 'property', function () { + let property = this.get('property'); + let errors = this.get('errors'); + let messages = []; + let index; - if (!Ember.isEmpty(errors) && errors.get(property)) { - errors.get(property).forEach(function (error) { + if (!isEmpty(errors) && errors.get(property)) { + errors.get(property).forEach((error) => { messages.push(error); }); index = Math.floor(Math.random() * messages.length); diff --git a/core/client/app/components/gh-file-upload.js b/core/client/app/components/gh-file-upload.js index f8dc48472a..e65b8817fd 100644 --- a/core/client/app/components/gh-file-upload.js +++ b/core/client/app/components/gh-file-upload.js @@ -1,10 +1,11 @@ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component} = Ember; + +export default Component.extend({ _file: null, uploadButtonText: 'Text', - uploadButtonDisabled: true, onUpload: null, @@ -12,14 +13,14 @@ export default Ember.Component.extend({ shouldResetForm: true, - change: function (event) { + change(event) { this.set('uploadButtonDisabled', false); this.sendAction('onAdd'); this._file = event.target.files[0]; }, actions: { - upload: function () { + upload() { if (!this.get('uploadButtonDisabled') && this._file) { this.sendAction('onUpload', this._file); } @@ -29,7 +30,7 @@ export default Ember.Component.extend({ // Reset form if (this.get('shouldResetForm')) { - this.$().closest('form').get(0).reset(); + this.$().closest('form')[0].reset(); } } } diff --git a/core/client/app/components/gh-infinite-scroll-box.js b/core/client/app/components/gh-infinite-scroll-box.js index f69537228a..5e42f7694e 100644 --- a/core/client/app/components/gh-infinite-scroll-box.js +++ b/core/client/app/components/gh-infinite-scroll-box.js @@ -2,21 +2,22 @@ import Ember from 'ember'; import InfiniteScrollMixin from 'ghost/mixins/infinite-scroll'; import setScrollClassName from 'ghost/utils/set-scroll-classname'; -export default Ember.Component.extend(InfiniteScrollMixin, { - didRender: function () { - this._super(); +const {Component, run} = Ember; - var el = this.$(); +export default Component.extend(InfiniteScrollMixin, { + didRender() { + let el = this.$(); - el.on('scroll', Ember.run.bind(el, setScrollClassName, { + this._super(...arguments); + + el.on('scroll', run.bind(el, setScrollClassName, { target: el.closest('.content-list'), offset: 10 })); }, - willDestroyElement: function () { - this._super(); - + willDestroyElement() { + this._super(...arguments); this.$().off('scroll'); } }); diff --git a/core/client/app/components/gh-infinite-scroll.js b/core/client/app/components/gh-infinite-scroll.js index f15dd0d1bd..128d5341ff 100644 --- a/core/client/app/components/gh-infinite-scroll.js +++ b/core/client/app/components/gh-infinite-scroll.js @@ -1,4 +1,6 @@ import Ember from 'ember'; import InfiniteScrollMixin from 'ghost/mixins/infinite-scroll'; -export default Ember.Component.extend(InfiniteScrollMixin); +const {Component} = Ember; + +export default Component.extend(InfiniteScrollMixin); diff --git a/core/client/app/components/gh-input.js b/core/client/app/components/gh-input.js index fff8fc2763..43d8ef131e 100644 --- a/core/client/app/components/gh-input.js +++ b/core/client/app/components/gh-input.js @@ -1,6 +1,8 @@ import Ember from 'ember'; import TextInputMixin from 'ghost/mixins/text-input'; -export default Ember.TextField.extend(TextInputMixin, { +const {TextField} = Ember; + +export default TextField.extend(TextInputMixin, { classNames: 'gh-input' }); diff --git a/core/client/app/components/gh-main.js b/core/client/app/components/gh-main.js index e9fdacacb8..e4bd8916df 100644 --- a/core/client/app/components/gh-main.js +++ b/core/client/app/components/gh-main.js @@ -1,11 +1,13 @@ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component} = Ember; + +export default Component.extend({ tagName: 'main', classNames: ['gh-main'], ariaRole: 'main', - mouseEnter: function () { + mouseEnter() { this.sendAction('onMouseEnter'); } }); diff --git a/core/client/app/components/gh-menu-toggle.js b/core/client/app/components/gh-menu-toggle.js index de6a005992..72114808cb 100644 --- a/core/client/app/components/gh-menu-toggle.js +++ b/core/client/app/components/gh-menu-toggle.js @@ -1,24 +1,25 @@ -/* -This cute little component has two jobs. - -On desktop, it toggles autoNav behaviour. It tracks -that state via the maximise property, and uses the -state to render the appropriate icon. - -On mobile, it renders a closing icon, and clicking it -closes the mobile menu -*/ - import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component, computed, inject} = Ember; + +/* + This cute little component has two jobs. + + On desktop, it toggles autoNav behaviour. It tracks + that state via the maximise property, and uses the + state to render the appropriate icon. + + On mobile, it renders a closing icon, and clicking it + closes the mobile menu +*/ +export default Component.extend({ classNames: ['gh-menu-toggle'], - mediaQueries: Ember.inject.service(), - isMobile: Ember.computed.reads('mediaQueries.isMobile'), + mediaQueries: inject.service(), + isMobile: computed.reads('mediaQueries.isMobile'), maximise: false, - iconClass: Ember.computed('maximise', 'isMobile', function () { + iconClass: computed('maximise', 'isMobile', function () { if (this.get('maximise') && !this.get('isMobile')) { return 'icon-maximise'; } else { @@ -26,7 +27,7 @@ export default Ember.Component.extend({ } }), - click: function () { + click() { if (this.get('isMobile')) { this.sendAction('mobileAction'); } else { diff --git a/core/client/app/components/gh-modal-dialog.js b/core/client/app/components/gh-modal-dialog.js index 22622022e1..d122f0c348 100644 --- a/core/client/app/components/gh-modal-dialog.js +++ b/core/client/app/components/gh-modal-dialog.js @@ -1,59 +1,66 @@ import Ember from 'ember'; -export default Ember.Component.extend({ - didInsertElement: function () { - this.$('.js-modal-container, .js-modal-background').addClass('fade-in open'); - this.$('.js-modal').addClass('open'); - }, +const {Component, computed} = Ember; - close: function () { - var self = this; +function K() { + return this; +} - this.$('.js-modal, .js-modal-background').removeClass('fade-in').addClass('fade-out'); - - // The background should always be the last thing to fade out, so check on that instead of the content - this.$('.js-modal-background').on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', function (event) { - if (event.originalEvent.animationName === 'fade-out') { - self.$('.js-modal, .js-modal-background').removeClass('open'); - } - }); - - this.sendAction(); - }, +export default Component.extend({ confirmaccept: 'confirmAccept', confirmreject: 'confirmReject', - actions: { - closeModal: function () { - this.close(); - }, - confirm: function (type) { - this.sendAction('confirm' + type); - this.close(); - }, - noBubble: Ember.K - }, + klass: computed('type', 'style', function () { + let classNames = []; - klass: Ember.computed('type', 'style', function () { - var classNames = []; - - classNames.push(this.get('type') ? 'modal-' + this.get('type') : 'modal'); + classNames.push(this.get('type') ? `modal-${this.get('type')}` : 'modal'); if (this.get('style')) { - this.get('style').split(',').forEach(function (style) { - classNames.push('modal-style-' + style); + this.get('style').split(',').forEach((style) => { + classNames.push(`modal-style-${style}`); }); } return classNames.join(' '); }), - acceptButtonClass: Ember.computed('confirm.accept.buttonClass', function () { + acceptButtonClass: computed('confirm.accept.buttonClass', function () { return this.get('confirm.accept.buttonClass') ? this.get('confirm.accept.buttonClass') : 'btn btn-green'; }), - rejectButtonClass: Ember.computed('confirm.reject.buttonClass', function () { + rejectButtonClass: computed('confirm.reject.buttonClass', function () { return this.get('confirm.reject.buttonClass') ? this.get('confirm.reject.buttonClass') : 'btn btn-red'; - }) + }), + + didInsertElement() { + this.$('.js-modal-container, .js-modal-background').addClass('fade-in open'); + this.$('.js-modal').addClass('open'); + }, + + close() { + this.$('.js-modal, .js-modal-background').removeClass('fade-in').addClass('fade-out'); + + // The background should always be the last thing to fade out, so check on that instead of the content + this.$('.js-modal-background').on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', (event) => { + if (event.originalEvent.animationName === 'fade-out') { + this.$('.js-modal, .js-modal-background').removeClass('open'); + } + }); + + this.sendAction(); + }, + + actions: { + closeModal() { + this.close(); + }, + + confirm(type) { + this.sendAction(`confirm${type}`); + this.close(); + }, + + noBubble: K + } }); diff --git a/core/client/app/components/gh-nav-menu.js b/core/client/app/components/gh-nav-menu.js index 9c2359c8e6..def7a79599 100644 --- a/core/client/app/components/gh-nav-menu.js +++ b/core/client/app/components/gh-nav-menu.js @@ -1,33 +1,35 @@ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component, inject} = Ember; + +export default Component.extend({ tagName: 'nav', classNames: ['gh-nav'], classNameBindings: ['open'], - config: Ember.inject.service(), - session: Ember.inject.service(), - open: false, - mouseEnter: function () { + config: inject.service(), + session: inject.service(), + + mouseEnter() { this.sendAction('onMouseEnter'); }, actions: { - toggleAutoNav: function () { + toggleAutoNav() { this.sendAction('toggleMaximise'); }, - openModal: function (modal) { + openModal(modal) { this.sendAction('openModal', modal); }, - closeMobileMenu: function () { + closeMobileMenu() { this.sendAction('closeMobileMenu'); }, - openAutoNav: function () { + openAutoNav() { this.sendAction('openAutoNav'); } } diff --git a/core/client/app/components/gh-navigation.js b/core/client/app/components/gh-navigation.js index 3eba3c4aaf..e1d40fc25e 100644 --- a/core/client/app/components/gh-navigation.js +++ b/core/client/app/components/gh-navigation.js @@ -1,13 +1,14 @@ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component, run} = Ember; + +export default Component.extend({ tagName: 'section', classNames: 'gh-view', - didInsertElement: function () { - var navContainer = this.$('.js-gh-blognav'), - navElements = '.gh-blognav-item:not(.gh-blognav-item:last-child)', - self = this; + didInsertElement() { + let navContainer = this.$('.js-gh-blognav'); + let navElements = '.gh-blognav-item:not(.gh-blognav-item:last-child)'; this._super(...arguments); @@ -15,21 +16,21 @@ export default Ember.Component.extend({ handle: '.gh-blognav-grab', items: navElements, - start: function (event, ui) { - Ember.run(function () { + start(event, ui) { + run(() => { ui.item.data('start-index', ui.item.index()); }); }, - update: function (event, ui) { - Ember.run(function () { - self.sendAction('moveItem', ui.item.data('start-index'), ui.item.index()); + update(event, ui) { + run(() => { + this.sendAction('moveItem', ui.item.data('start-index'), ui.item.index()); }); } }); }, - willDestroyElement: function () { + willDestroyElement() { this.$('.ui-sortable').sortable('destroy'); } }); diff --git a/core/client/app/components/gh-navitem-url-input.js b/core/client/app/components/gh-navitem-url-input.js index 1c74ba74a7..05ec796f54 100644 --- a/core/client/app/components/gh-navitem-url-input.js +++ b/core/client/app/components/gh-navitem-url-input.js @@ -1,11 +1,10 @@ import Ember from 'ember'; -var joinUrlParts, - isRelative; +const {TextField, computed, run} = Ember; -joinUrlParts = function (url, path) { +let joinUrlParts = function (url, path) { if (path[0] !== '/' && url.slice(-1) !== '/') { - path = '/' + path; + path = `/${path}`; } else if (path[0] === '/' && url.slice(-1) === '/') { path = path.slice(1); } @@ -13,19 +12,27 @@ joinUrlParts = function (url, path) { return url + path; }; -isRelative = function (url) { +let isRelative = function (url) { // "protocol://", "//example.com", "scheme:", "#anchor", & invalid paths // should all be treated as absolute return !url.match(/\s/) && !validator.isURL(url) && !url.match(/^(\/\/|#|[a-zA-Z0-9\-]+:)/); }; -export default Ember.TextField.extend({ +export default TextField.extend({ classNames: 'gh-input', classNameBindings: ['fakePlaceholder'], - didReceiveAttrs: function () { - var url = this.get('url'), - baseUrl = this.get('baseUrl'); + isBaseUrl: computed('baseUrl', 'value', function () { + return this.get('baseUrl') === this.get('value'); + }), + + fakePlaceholder: computed('isBaseUrl', 'hasFocus', function () { + return this.get('isBaseUrl') && this.get('last') && !this.get('hasFocus'); + }), + + didReceiveAttrs() { + let baseUrl = this.get('baseUrl'); + let url = this.get('url'); // if we have a relative url, create the absolute url to be displayed in the input if (isRelative(url)) { @@ -35,28 +42,20 @@ export default Ember.TextField.extend({ this.set('value', url); }, - isBaseUrl: Ember.computed('baseUrl', 'value', function () { - return this.get('baseUrl') === this.get('value'); - }), - - fakePlaceholder: Ember.computed('isBaseUrl', 'hasFocus', function () { - return this.get('isBaseUrl') && this.get('last') && !this.get('hasFocus'); - }), - - focusIn: function (event) { + focusIn(event) { this.set('hasFocus', true); if (this.get('isBaseUrl')) { // position the cursor at the end of the input - Ember.run.next(function (el) { - var length = el.value.length; + run.next(function (el) { + let {length} = el.value; el.setSelectionRange(length, length); }, event.target); } }, - keyDown: function (event) { + keyDown(event) { // delete the "placeholder" value all at once if (this.get('isBaseUrl') && (event.keyCode === 8 || event.keyCode === 46)) { this.set('value', ''); @@ -70,7 +69,7 @@ export default Ember.TextField.extend({ } }, - keyPress: function (event) { + keyPress(event) { // enter key if (event.keyCode === 13) { event.preventDefault(); @@ -80,19 +79,21 @@ export default Ember.TextField.extend({ return true; }, - focusOut: function () { + focusOut() { this.set('hasFocus', false); this.notifyUrlChanged(); }, - notifyUrlChanged: function () { - this.set('value', this.get('value').trim()); + notifyUrlChanged() { + let value = this.get('value').trim(); + let urlParts = document.createElement('a'); + let baseUrl = this.get('baseUrl'); + let baseUrlParts = document.createElement('a'); + let url = value; - var url = this.get('value'), - urlParts = document.createElement('a'), - baseUrl = this.get('baseUrl'), - baseUrlParts = document.createElement('a'); + // ensure value property is trimmed + this.set('value', value); // leverage the browser's native URI parsing urlParts.href = url; @@ -117,7 +118,7 @@ export default Ember.TextField.extend({ url = url.replace(baseUrlParts.host, ''); url = url.replace(baseUrlParts.pathname, ''); if (!url.match(/^\//)) { - url = '/' + url; + url = `/${url}`; } } diff --git a/core/client/app/components/gh-navitem.js b/core/client/app/components/gh-navitem.js index 12c7c8e131..cc2438a7d2 100644 --- a/core/client/app/components/gh-navitem.js +++ b/core/client/app/components/gh-navitem.js @@ -1,21 +1,23 @@ import Ember from 'ember'; import ValidationStateMixin from 'ghost/mixins/validation-state'; -export default Ember.Component.extend(ValidationStateMixin, { +const {Component, computed} = Ember; + +export default Component.extend(ValidationStateMixin, { classNames: 'gh-blognav-item', classNameBindings: ['errorClass'], attributeBindings: ['order:data-order'], - order: Ember.computed.readOnly('navItem.order'), - errors: Ember.computed.readOnly('navItem.errors'), + order: computed.readOnly('navItem.order'), + errors: computed.readOnly('navItem.errors'), - errorClass: Ember.computed('hasError', function () { + errorClass: computed('hasError', function () { if (this.get('hasError')) { return 'gh-blognav-item--error'; } }), - keyPress: function (event) { + keyPress(event) { // enter key if (event.keyCode === 13) { event.preventDefault(); @@ -26,15 +28,15 @@ export default Ember.Component.extend(ValidationStateMixin, { }, actions: { - addItem: function () { + addItem() { this.sendAction('addItem'); }, - deleteItem: function (item) { + deleteItem(item) { this.sendAction('deleteItem', item); }, - updateUrl: function (value) { + updateUrl(value) { this.sendAction('updateUrl', value, this.get('navItem')); } } diff --git a/core/client/app/components/gh-notification.js b/core/client/app/components/gh-notification.js index 525d252db1..e421b309db 100644 --- a/core/client/app/components/gh-notification.js +++ b/core/client/app/components/gh-notification.js @@ -1,18 +1,20 @@ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component, computed, inject} = Ember; + +export default Component.extend({ tagName: 'article', classNames: ['gh-notification', 'gh-notification-passive'], classNameBindings: ['typeClass'], message: null, - notifications: Ember.inject.service(), + notifications: inject.service(), - typeClass: Ember.computed('message.type', function () { - var classes = '', - type = this.get('message.type'), - typeMapping; + typeClass: computed('message.type', function () { + let type = this.get('message.type'); + let classes = ''; + let typeMapping; typeMapping = { success: 'green', @@ -21,28 +23,26 @@ export default Ember.Component.extend({ }; if (typeMapping[type] !== undefined) { - classes += 'gh-notification-' + typeMapping[type]; + classes += `gh-notification-${typeMapping[type]}`; } return classes; }), - didInsertElement: function () { - var self = this; - - self.$().on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', function (event) { + didInsertElement() { + this.$().on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', (event) => { if (event.originalEvent.animationName === 'fade-out') { - self.get('notifications').closeNotification(self.get('message')); + this.get('notifications').closeNotification(this.get('message')); } }); }, - willDestroyElement: function () { + willDestroyElement() { this.$().off('animationend webkitAnimationEnd oanimationend MSAnimationEnd'); }, actions: { - closeNotification: function () { + closeNotification() { 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 a8c4c81288..d0b94d7b9c 100644 --- a/core/client/app/components/gh-notifications.js +++ b/core/client/app/components/gh-notifications.js @@ -1,10 +1,13 @@ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component, computed, inject} = Ember; +const {alias} = computed; + +export default Component.extend({ tagName: 'aside', classNames: 'gh-notifications', - notifications: Ember.inject.service(), + notifications: inject.service(), - messages: Ember.computed.alias('notifications.notifications') + messages: alias('notifications.notifications') }); diff --git a/core/client/app/components/gh-popover-button.js b/core/client/app/components/gh-popover-button.js index 0b9fb4baf2..44a56cedd4 100644 --- a/core/client/app/components/gh-popover-button.js +++ b/core/client/app/components/gh-popover-button.js @@ -1,18 +1,24 @@ import Ember from 'ember'; import DropdownButton from 'ghost/components/gh-dropdown-button'; +const {inject} = Ember; + +function K() { + return this; +} + export default DropdownButton.extend({ - dropdown: Ember.inject.service(), + dropdown: inject.service(), - click: Ember.K, + click: K, - mouseEnter: function (event) { - this._super(event); + mouseEnter() { + this._super(...arguments); this.get('dropdown').toggleDropdown(this.get('popoverName'), this); }, - mouseLeave: function (event) { - this._super(event); + mouseLeave() { + this._super(...arguments); this.get('dropdown').toggleDropdown(this.get('popoverName'), this); } }); diff --git a/core/client/app/components/gh-popover.js b/core/client/app/components/gh-popover.js index 70c2e8f9e8..882ed82a57 100644 --- a/core/client/app/components/gh-popover.js +++ b/core/client/app/components/gh-popover.js @@ -1,7 +1,9 @@ import Ember from 'ember'; import GhostDropdown from 'ghost/components/gh-dropdown'; +const {inject} = Ember; + export default GhostDropdown.extend({ classNames: 'ghost-popover', - dropdown: Ember.inject.service() + dropdown: inject.service() }); diff --git a/core/client/app/components/gh-posts-list-item.js b/core/client/app/components/gh-posts-list-item.js index 093494a005..03870e9f27 100644 --- a/core/client/app/components/gh-posts-list-item.js +++ b/core/client/app/components/gh-posts-list-item.js @@ -1,6 +1,9 @@ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component, computed, inject} = Ember; +const {alias, equal} = computed; + +export default Component.extend({ tagName: 'li', classNameBindings: ['active', 'isFeatured:featured', 'isPage:page'], @@ -8,60 +11,56 @@ export default Ember.Component.extend({ active: false, previewIsHidden: false, - ghostPaths: Ember.inject.service('ghost-paths'), + isFeatured: alias('post.featured'), + isPage: alias('post.page'), + isPublished: equal('post.status', 'published'), - isFeatured: Ember.computed.alias('post.featured'), + ghostPaths: inject.service('ghost-paths'), - isPage: Ember.computed.alias('post.page'), - - isPublished: Ember.computed.equal('post.status', 'published'), - - authorName: Ember.computed('post.author.name', 'post.author.email', function () { + authorName: computed('post.author.name', 'post.author.email', function () { return this.get('post.author.name') || this.get('post.author.email'); }), - authorAvatar: Ember.computed('post.author.image', function () { + authorAvatar: computed('post.author.image', function () { return this.get('post.author.image') || this.get('ghostPaths.url').asset('/shared/img/user-image.png'); }), - authorAvatarBackground: Ember.computed('authorAvatar', function () { + authorAvatarBackground: computed('authorAvatar', function () { return Ember.String.htmlSafe(`background-image: url(${this.get('authorAvatar')})`); }), - viewOrEdit: Ember.computed('previewIsHidden', function () { + viewOrEdit: computed('previewIsHidden', function () { return this.get('previewIsHidden') ? 'editor.edit' : 'posts.post'; }), - click: function () { + click() { this.sendAction('onClick', this.get('post')); }, - doubleClick: function () { + doubleClick() { this.sendAction('onDoubleClick', this.get('post')); }, - didInsertElement: function () { + didInsertElement() { this.addObserver('active', this, this.scrollIntoView); }, - willDestroyElement: function () { + willDestroyElement() { this.removeObserver('active', this, this.scrollIntoView); }, - scrollIntoView: function () { + scrollIntoView() { if (!this.get('active')) { return; } - var element = this.$(), - offset = element.offset().top, - elementHeight = element.height(), - container = Ember.$('.js-content-scrollbox'), - containerHeight = container.height(), - currentScroll = container.scrollTop(), - isBelowTop, - isAboveBottom, - isOnScreen; + let element = this.$(); + let offset = element.offset().top; + let elementHeight = element.height(); + let container = $('.js-content-scrollbox'); + let containerHeight = container.height(); + let currentScroll = container.scrollTop(); + let isBelowTop, isAboveBottom, isOnScreen; isAboveBottom = offset < containerHeight; isBelowTop = offset > elementHeight; diff --git a/core/client/app/components/gh-profile-image.js b/core/client/app/components/gh-profile-image.js index 211ea38361..04fd697973 100644 --- a/core/client/app/components/gh-profile-image.js +++ b/core/client/app/components/gh-profile-image.js @@ -1,5 +1,8 @@ import Ember from 'ember'; +const {Component, computed, inject, run} = Ember; +const {notEmpty} = computed; + /** * A component to manage a user profile image. By default it just handles picture uploads, * but if passed a bound 'email' property it will render the user's gravatar image @@ -14,7 +17,7 @@ import Ember from 'ember'; * @property {String} defaultImage String containing the background-image css property of the default user profile image * @property {String} imageBackground String containing the background-image css property with the gravatar url */ -export default Ember.Component.extend({ +export default Component.extend({ email: '', size: 90, debounce: 300, @@ -23,35 +26,35 @@ export default Ember.Component.extend({ hasUploadedImage: false, fileStorage: true, - ghostPaths: Ember.inject.service('ghost-paths'), - displayGravatar: Ember.computed.notEmpty('validEmail'), + ghostPaths: inject.service('ghost-paths'), + displayGravatar: notEmpty('validEmail'), - init: function () { + init() { this._super(...arguments); // Fire this immediately in case we're initialized with a valid email this.trySetValidEmail(); }, - defaultImage: Ember.computed('ghostPaths', function () { - const url = this.get('ghostPaths.url').asset('/shared/img/user-image.png'); + defaultImage: computed('ghostPaths', function () { + let url = this.get('ghostPaths.url').asset('/shared/img/user-image.png'); return Ember.String.htmlSafe(`background-image: url(${url})`); }), - trySetValidEmail: function () { + trySetValidEmail() { if (!this.get('isDestroyed')) { - const email = this.get('email'); + let email = this.get('email'); this.set('validEmail', validator.isEmail(email) ? email : ''); } }, - didReceiveAttrs: function (attrs) { - const timeout = parseInt(attrs.newAttrs.throttle || this.get('debounce')); - Ember.run.debounce(this, 'trySetValidEmail', timeout); + didReceiveAttrs(attrs) { + let timeout = parseInt(attrs.newAttrs.throttle || this.get('debounce')); + run.debounce(this, 'trySetValidEmail', timeout); }, - imageBackground: Ember.computed('validEmail', 'size', function () { - const email = this.get('validEmail'), - size = this.get('size'); + imageBackground: computed('validEmail', 'size', function () { + let email = this.get('validEmail'); + let size = this.get('size'); let style = ''; if (email) { @@ -61,9 +64,9 @@ export default Ember.Component.extend({ return Ember.String.htmlSafe(style); }), - didInsertElement: function () { - var size = this.get('size'), - uploadElement = this.$('.js-file-input'); + didInsertElement() { + let size = this.get('size'); + let uploadElement = this.$('.js-file-input'); // while theoretically the 'add' and 'processalways' functions could be // added as properties of the hash passed to fileupload(), for some reason @@ -77,26 +80,27 @@ export default Ember.Component.extend({ maxNumberOfFiles: 1, autoUpload: false }) - .on('fileuploadadd', Ember.run.bind(this, this.queueFile)) - .on('fileuploadprocessalways', Ember.run.bind(this, this.triggerPreview)); + .on('fileuploadadd', run.bind(this, this.queueFile)) + .on('fileuploadprocessalways', run.bind(this, this.triggerPreview)); }, - willDestroyElement: function () { + willDestroyElement() { if (this.$('.js-file-input').data()['blueimp-fileupload']) { this.$('.js-file-input').fileupload('destroy'); } }, - queueFile: function (e, data) { - const fileName = data.files[0].name; + queueFile(e, data) { + let fileName = data.files[0].name; if ((/\.(gif|jpe?g|png|svg?z)$/i).test(fileName)) { this.sendAction('setImage', data); } }, - triggerPreview: function (e, data) { - const file = data.files[data.index]; + triggerPreview(e, data) { + let file = data.files[data.index]; + if (file.preview) { this.set('hasUploadedImage', true); // necessary jQuery code because file.preview is a raw DOM object diff --git a/core/client/app/components/gh-search-input.js b/core/client/app/components/gh-search-input.js index 0994d571ce..1103e3c6a6 100644 --- a/core/client/app/components/gh-search-input.js +++ b/core/client/app/components/gh-search-input.js @@ -1,8 +1,12 @@ +/* global key */ +/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */ import Ember from 'ember'; import {request as ajax} from 'ic-ajax'; -/* global key */ -export default Ember.Component.extend({ +const {Component, RSVP, computed, inject, observer} = Ember; +const {filterBy} = computed; + +export default Component.extend({ selection: null, content: [], @@ -10,48 +14,49 @@ export default Ember.Component.extend({ contentExpiry: 10 * 1000, contentExpiresAt: false, - posts: Ember.computed.filterBy('content', 'category', 'Posts'), - pages: Ember.computed.filterBy('content', 'category', 'Pages'), - users: Ember.computed.filterBy('content', 'category', 'Users'), - tags: Ember.computed.filterBy('content', 'category', 'Tags'), + posts: filterBy('content', 'category', 'Posts'), + pages: filterBy('content', 'category', 'Pages'), + users: filterBy('content', 'category', 'Users'), + tags: filterBy('content', 'category', 'Tags'), - _store: Ember.inject.service('store'), - _routing: Ember.inject.service('-routing'), - _selectize: Ember.computed(function () { + _store: inject.service('store'), + _routing: inject.service('-routing'), + + _selectize: computed(function () { return this.$('select')[0].selectize; }), - refreshContent: function () { - var promises = [], - now = new Date(), - contentExpiry = this.get('contentExpiry'), - contentExpiresAt = this.get('contentExpiresAt'), - self = this; + refreshContent() { + let promises = []; + let now = new Date(); + let contentExpiry = this.get('contentExpiry'); + let contentExpiresAt = this.get('contentExpiresAt'); - if (self.get('isLoading') || contentExpiresAt > now) { return; } + if (this.get('isLoading') || contentExpiresAt > now) { + return; + } - self.set('isLoading', true); + this.set('isLoading', true); promises.pushObject(this._loadPosts()); promises.pushObject(this._loadUsers()); promises.pushObject(this._loadTags()); - Ember.RSVP.all(promises).then(function () { }).finally(function () { - self.set('isLoading', false); - self.set('contentExpiresAt', new Date(now.getTime() + contentExpiry)); + RSVP.all(promises).then(() => { }).finally(() => { + this.set('isLoading', false); + this.set('contentExpiresAt', new Date(now.getTime() + contentExpiry)); }); }, - _loadPosts: function () { - var store = this.get('_store'), - postsUrl = store.adapterFor('post').urlForQuery({}, 'post') + '/', - postsQuery = {fields: 'id,title,page', limit: 'all', status: 'all', staticPages: 'all'}, - content = this.get('content'), - self = this; + _loadPosts() { + let store = this.get('_store'); + let postsUrl = `${store.adapterFor('post').urlForQuery({}, 'post')}/`; + let postsQuery = {fields: 'id,title,page', limit: 'all', status: 'all', staticPages: 'all'}; + let content = this.get('content'); - return ajax(postsUrl, {data: postsQuery}).then(function (posts) { - content.removeObjects(self.get('posts')); - content.removeObjects(self.get('pages')); - content.pushObjects(posts.posts.map(function (post) { + return ajax(postsUrl, {data: postsQuery}).then((posts) => { + content.removeObjects(this.get('posts')); + content.removeObjects(this.get('pages')); + content.pushObjects(posts.posts.map((post) => { return { id: `post.${post.id}`, title: post.title, @@ -61,16 +66,15 @@ export default Ember.Component.extend({ }); }, - _loadUsers: function () { - var store = this.get('_store'), - usersUrl = store.adapterFor('user').urlForQuery({}, 'user') + '/', - usersQuery = {fields: 'name,slug', limit: 'all'}, - content = this.get('content'), - self = this; + _loadUsers() { + let store = this.get('_store'); + let usersUrl = `${store.adapterFor('user').urlForQuery({}, 'user')}/`; + let usersQuery = {fields: 'name,slug', limit: 'all'}; + let content = this.get('content'); - return ajax(usersUrl, {data: usersQuery}).then(function (users) { - content.removeObjects(self.get('users')); - content.pushObjects(users.users.map(function (user) { + return ajax(usersUrl, {data: usersQuery}).then((users) => { + content.removeObjects(this.get('users')); + content.pushObjects(users.users.map((user) => { return { id: `user.${user.slug}`, title: user.name, @@ -80,16 +84,15 @@ export default Ember.Component.extend({ }); }, - _loadTags: function () { - var store = this.get('_store'), - tagsUrl = store.adapterFor('tag').urlForQuery({}, 'tag') + '/', - tagsQuery = {fields: 'name,slug', limit: 'all'}, - content = this.get('content'), - self = this; + _loadTags() { + let store = this.get('_store'); + let tagsUrl = `${store.adapterFor('tag').urlForQuery({}, 'tag')}/`; + let tagsQuery = {fields: 'name,slug', limit: 'all'}; + let content = this.get('content'); - return ajax(tagsUrl, {data: tagsQuery}).then(function (tags) { - content.removeObjects(self.get('tags')); - content.pushObjects(tags.tags.map(function (tag) { + return ajax(tagsUrl, {data: tagsQuery}).then((tags) => { + content.removeObjects(this.get('tags')); + content.pushObjects(tags.tags.map((tag) => { return { id: `tag.${tag.slug}`, title: tag.name, @@ -99,80 +102,81 @@ export default Ember.Component.extend({ }); }, - _keepSelectionClear: Ember.observer('selection', function () { + _keepSelectionClear: observer('selection', function () { if (this.get('selection') !== null) { this.set('selection', null); } }), - _setKeymasterScope: function () { + _setKeymasterScope() { key.setScope('search-input'); }, - _resetKeymasterScope: function () { + _resetKeymasterScope() { key.setScope('default'); }, - willDestroy: function () { + willDestroy() { this._resetKeymasterScope(); }, actions: { - openSelected: function (selected) { - var transition = null, - self = this; + openSelected(selected) { + let transition = null; - if (!selected) { return; } + if (!selected) { + return; + } if (selected.category === 'Posts' || selected.category === 'Pages') { let id = selected.id.replace('post.', ''); - transition = self.get('_routing.router').transitionTo('editor.edit', id); + transition = this.get('_routing.router').transitionTo('editor.edit', id); } if (selected.category === 'Users') { let id = selected.id.replace('user.', ''); - transition = self.get('_routing.router').transitionTo('team.user', id); + transition = this.get('_routing.router').transitionTo('team.user', id); } if (selected.category === 'Tags') { let id = selected.id.replace('tag.', ''); - transition = self.get('_routing.router').transitionTo('settings.tags.tag', id); + transition = this.get('_routing.router').transitionTo('settings.tags.tag', id); } - transition.then(function () { - if (self.get('_selectize').$control_input.is(':focus')) { - self._setKeymasterScope(); + transition.then(() => { + if (this.get('_selectize').$control_input.is(':focus')) { + this._setKeymasterScope(); } }); }, - focusInput: function () { + focusInput() { this.get('_selectize').focus(); }, - onInit: function () { - var selectize = this.get('_selectize'), - html = ''; + onInit() { + let selectize = this.get('_selectize'); + let html = ''; selectize.$empty_results_container = $(html); selectize.$empty_results_container.hide(); selectize.$dropdown.append(selectize.$empty_results_container); }, - onFocus: function () { + onFocus() { this._setKeymasterScope(); this.refreshContent(); }, - onBlur: function () { - var selectize = this.get('_selectize'); + onBlur() { + let selectize = this.get('_selectize'); this._resetKeymasterScope(); selectize.$empty_results_container.hide(); }, - onType: function () { - var selectize = this.get('_selectize'); + onType() { + let selectize = this.get('_selectize'); if (!selectize.hasOptions) { selectize.open(); diff --git a/core/client/app/components/gh-select-native.js b/core/client/app/components/gh-select-native.js index 3e588adf0b..0c5d49e958 100644 --- a/core/client/app/components/gh-select-native.js +++ b/core/client/app/components/gh-select-native.js @@ -1,28 +1,34 @@ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component, computed} = Ember; +const {reads} = computed; + +function K() { + return this; +} + +export default Component.extend({ content: null, prompt: null, optionValuePath: 'id', optionLabelPath: 'title', selection: null, - action: Ember.K, // action to fire on change + action: K, // action to fire on change // shadow the passed-in `selection` to avoid // leaking changes to it via a 2-way binding - _selection: Ember.computed.reads('selection'), + _selection: reads('selection'), actions: { - change: function () { - var selectEl = this.$('select')[0], - selectedIndex = selectEl.selectedIndex, - content = this.get('content'), + change() { + let [selectEl] = this.$('select'); + let {selectedIndex} = selectEl; - // decrement index by 1 if we have a prompt - hasPrompt = !!this.get('prompt'), - contentIndex = hasPrompt ? selectedIndex - 1 : selectedIndex, + // decrement index by 1 if we have a prompt + let hasPrompt = !!this.get('prompt'); + let contentIndex = hasPrompt ? selectedIndex - 1 : selectedIndex; - selection = content.objectAt(contentIndex); + let selection = this.get('content').objectAt(contentIndex); // set the local, shadowed selection to avoid leaking // changes to `selection` out via 2-way binding diff --git a/core/client/app/components/gh-selectize.js b/core/client/app/components/gh-selectize.js index ad842750c4..24e185413f 100644 --- a/core/client/app/components/gh-selectize.js +++ b/core/client/app/components/gh-selectize.js @@ -1,30 +1,34 @@ +/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */ import Ember from 'ember'; import EmberSelectizeComponent from 'ember-cli-selectize/components/ember-selectize'; +const {computed, isBlank, get, on, run} = Ember; +const emberA = Ember.A; + export default EmberSelectizeComponent.extend({ - selectizeOptions: Ember.computed(function () { - const options = this._super(...arguments); + selectizeOptions: computed(function () { + let options = this._super(...arguments); - options.onChange = Ember.run.bind(this, '_onChange'); + options.onChange = run.bind(this, '_onChange'); return options; }), - _dontOpenWhenBlank: Ember.on('didInsertElement', function () { - var openOnFocus = this.get('openOnFocus'); + _dontOpenWhenBlank: on('didInsertElement', function () { + let openOnFocus = this.get('openOnFocus'); if (!openOnFocus) { - Ember.run.schedule('afterRender', this, function () { - var selectize = this._selectize; + run.schedule('afterRender', this, function () { + let selectize = this._selectize; if (selectize) { selectize.on('dropdown_open', function () { - if (Ember.isBlank(selectize.$control_input.val())) { + if (isBlank(selectize.$control_input.val())) { selectize.close(); } }); selectize.on('type', function (filter) { - if (Ember.isBlank(filter)) { + if (isBlank(filter)) { selectize.close(); } }); @@ -37,15 +41,15 @@ export default EmberSelectizeComponent.extend({ * Event callback that is triggered when user creates a tag * - modified to pass the caret position to the action */ - _create: function (input, callback) { - var caret = this._selectize.caretPos; + _create(input, callback) { + let caret = this._selectize.caretPos; // Delete user entered text this._selectize.setTextboxValue(''); // Send create action // allow the observers and computed properties to run first - Ember.run.schedule('actions', this, function () { + run.schedule('actions', this, function () { this.sendAction('create-item', input, caret); }); // We cancel the creation here, so it's up to you to include the created element @@ -53,10 +57,10 @@ export default EmberSelectizeComponent.extend({ callback(null); }, - _addSelection: function (obj) { - var _valuePath = this.get('_valuePath'), - val = Ember.get(obj, _valuePath), - caret = this._selectize.caretPos; + _addSelection(obj) { + let _valuePath = this.get('_valuePath'); + let val = get(obj, _valuePath); + let caret = this._selectize.caretPos; // caret position is always 1 more than the desired index as this method // is called after selectize has inserted the item and the caret has moved @@ -65,15 +69,15 @@ export default EmberSelectizeComponent.extend({ this.get('selection').insertAt(caret, obj); - Ember.run.schedule('actions', this, function () { + run.schedule('actions', this, function () { this.sendAction('add-item', obj); this.sendAction('add-value', val); }); }, - _onChange: function (args) { - const selection = Ember.get(this, 'selection'), - valuePath = Ember.get(this, '_valuePath'); + _onChange(args) { + let selection = Ember.get(this, 'selection'); + let valuePath = Ember.get(this, '_valuePath'); if (!args || !selection || !Ember.isArray(selection) || args.length !== selection.length) { return; @@ -87,11 +91,13 @@ export default EmberSelectizeComponent.extend({ return; } - let reorderedSelection = Ember.A([]); + let reorderedSelection = emberA([]); - args.forEach(function (value) { - const obj = selection.find(function (item) { - return (Ember.get(item, valuePath) + '') === value; + args.forEach((value) => { + let obj = selection.find(function (item) { + // jscs:disable + return (get(item, valuePath) + '') === value; + // jscs:enable }); if (obj) { diff --git a/core/client/app/components/gh-skip-link.js b/core/client/app/components/gh-skip-link.js index 7da190835a..05cac41796 100644 --- a/core/client/app/components/gh-skip-link.js +++ b/core/client/app/components/gh-skip-link.js @@ -1,7 +1,9 @@ /*jshint scripturl:true*/ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component, on} = Ember; + +export default Component.extend({ tagName: 'a', anchor: '', classNames: ['sr-only', 'sr-only-focusable'], @@ -13,9 +15,9 @@ export default Ember.Component.extend({ // anchor behaviors or ignored href: Ember.String.htmlSafe('javascript:;'), - scrollTo: Ember.on('click', function () { - var anchor = this.get('anchor'), - $el = Ember.$(anchor); + scrollTo: on('click', function () { + let anchor = this.get('anchor'); + let $el = Ember.$(anchor); if ($el) { // Scrolls to the top of main content or whatever diff --git a/core/client/app/components/gh-spin-button.js b/core/client/app/components/gh-spin-button.js index 4765313123..c47dd72dc2 100644 --- a/core/client/app/components/gh-spin-button.js +++ b/core/client/app/components/gh-spin-button.js @@ -1,6 +1,9 @@ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component, computed, observer, run} = Ember; +const {equal} = computed; + +export default Component.extend({ tagName: 'button', buttonText: '', submitting: false, @@ -12,9 +15,9 @@ export default Ember.Component.extend({ attributeBindings: ['disabled', 'type', 'tabindex'], // Must be set on the controller - disabled: Ember.computed.equal('showSpinner', true), + disabled: equal('showSpinner', true), - click: function () { + click() { if (this.get('action')) { this.sendAction('action'); return false; @@ -22,13 +25,13 @@ export default Ember.Component.extend({ return true; }, - toggleSpinner: Ember.observer('submitting', function () { - var submitting = this.get('submitting'), - timeout = this.get('showSpinnerTimeout'); + toggleSpinner: observer('submitting', function () { + let submitting = this.get('submitting'); + let timeout = this.get('showSpinnerTimeout'); if (submitting) { this.set('showSpinner', true); - this.set('showSpinnerTimeout', Ember.run.later(this, function () { + this.set('showSpinnerTimeout', run.later(this, function () { if (!this.get('submitting')) { this.set('showSpinner', false); } @@ -39,7 +42,7 @@ export default Ember.Component.extend({ } }), - setSize: Ember.observer('showSpinner', function () { + setSize: observer('showSpinner', function () { if (this.get('showSpinner') && this.get('autoWidth')) { this.$().width(this.$().width()); this.$().height(this.$().height()); @@ -49,7 +52,7 @@ export default Ember.Component.extend({ } }), - willDestroy: function () { - Ember.run.cancel(this.get('showSpinnerTimeout')); + willDestroy() { + run.cancel(this.get('showSpinnerTimeout')); } }); diff --git a/core/client/app/components/gh-tab-pane.js b/core/client/app/components/gh-tab-pane.js index e304bc87cf..bcf0bcf3c2 100644 --- a/core/client/app/components/gh-tab-pane.js +++ b/core/client/app/components/gh-tab-pane.js @@ -1,28 +1,31 @@ import Ember from 'ember'; +const {Component, computed} = Ember; +const {alias} = computed; + // See gh-tabs-manager.js for use -export default Ember.Component.extend({ +export default Component.extend({ classNameBindings: ['active'], - tabsManager: Ember.computed(function () { + tabsManager: computed(function () { return this.nearestWithProperty('isTabsManager'); }), - tab: Ember.computed('tabsManager.tabs.[]', 'tabsManager.tabPanes.[]', function () { - var index = this.get('tabsManager.tabPanes').indexOf(this), - tabs = this.get('tabsManager.tabs'); + tab: computed('tabsManager.tabs.[]', 'tabsManager.tabPanes.[]', function () { + let index = this.get('tabsManager.tabPanes').indexOf(this); + let tabs = this.get('tabsManager.tabs'); return tabs && tabs.objectAt(index); }), - active: Ember.computed.alias('tab.active'), + active: alias('tab.active'), - willRender: function () { + willRender() { // Register with the tabs manager this.get('tabsManager').registerTabPane(this); }, - willDestroyElement: function () { + willDestroyElement() { // Deregister with the tabs manager this.get('tabsManager').unregisterTabPane(this); } diff --git a/core/client/app/components/gh-tab.js b/core/client/app/components/gh-tab.js index 56a54a81f4..2d6dd89558 100644 --- a/core/client/app/components/gh-tab.js +++ b/core/client/app/components/gh-tab.js @@ -1,30 +1,32 @@ import Ember from 'ember'; +const {Component, computed} = Ember; + // See gh-tabs-manager.js for use -export default Ember.Component.extend({ - tabsManager: Ember.computed(function () { +export default Component.extend({ + tabsManager: computed(function () { return this.nearestWithProperty('isTabsManager'); }), - active: Ember.computed('tabsManager.activeTab', function () { + active: computed('tabsManager.activeTab', function () { return this.get('tabsManager.activeTab') === this; }), - index: Ember.computed('tabsManager.tabs.[]', function () { + index: computed('tabsManager.tabs.[]', function () { return this.get('tabsManager.tabs').indexOf(this); }), // Select on click - click: function () { + click() { this.get('tabsManager').select(this); }, - willRender: function () { + willRender() { // register the tabs with the tab manager this.get('tabsManager').registerTab(this); }, - willDestroyElement: function () { + willDestroyElement() { // unregister the tabs with the tab manager this.get('tabsManager').unregisterTab(this); } diff --git a/core/client/app/components/gh-tabs-manager.js b/core/client/app/components/gh-tabs-manager.js index 16fa003f6b..32c023cd42 100644 --- a/core/client/app/components/gh-tabs-manager.js +++ b/core/client/app/components/gh-tabs-manager.js @@ -1,4 +1,7 @@ import Ember from 'ember'; + +const {Component} = Ember; + /** Heavily inspired by ic-tabs (https://github.com/instructure/ic-tabs) @@ -48,35 +51,35 @@ Both tab and tab-pane elements have an "active" class applied when they are active. */ -export default Ember.Component.extend({ +export default Component.extend({ activeTab: null, tabs: [], tabPanes: [], + // Used by children to find this tabsManager + isTabsManager: true, + // Called when a gh-tab is clicked. - select: function (tab) { + select(tab) { this.set('activeTab', tab); this.sendAction('selected'); }, - // Used by children to find this tabsManager - isTabsManager: true, - // Register tabs and their panes to allow for // interaction between components. - registerTab: function (tab) { + registerTab(tab) { this.get('tabs').addObject(tab); }, - unregisterTab: function (tab) { + unregisterTab(tab) { this.get('tabs').removeObject(tab); }, - registerTabPane: function (tabPane) { + registerTabPane(tabPane) { this.get('tabPanes').addObject(tabPane); }, - unregisterTabPane: function (tabPane) { + unregisterTabPane(tabPane) { this.get('tabPanes').removeObject(tabPane); } }); diff --git a/core/client/app/components/gh-tag-settings-form.js b/core/client/app/components/gh-tag-settings-form.js index 2ad9461e05..c738aafffd 100644 --- a/core/client/app/components/gh-tag-settings-form.js +++ b/core/client/app/components/gh-tag-settings-form.js @@ -2,9 +2,9 @@ import Ember from 'ember'; import boundOneWay from 'ghost/utils/bound-one-way'; -const {get} = Ember; +const {Component, Handlebars, computed, get, inject} = Ember; -export default Ember.Component.extend({ +export default Component.extend({ tag: null, @@ -16,12 +16,12 @@ export default Ember.Component.extend({ isViewingSubview: false, - config: Ember.inject.service(), + config: inject.service(), mediaQueries: Ember.inject.service(), isMobile: Ember.computed.reads('mediaQueries.maxWidth600'), - title: Ember.computed('tag.isNew', function () { + title: computed('tag.isNew', function () { if (this.get('tag.isNew')) { return 'New Tag'; } else { @@ -29,25 +29,25 @@ export default Ember.Component.extend({ } }), - seoTitle: Ember.computed('scratchName', 'scratchMetaTitle', function () { + seoTitle: computed('scratchName', 'scratchMetaTitle', function () { let metaTitle = this.get('scratchMetaTitle') || ''; metaTitle = metaTitle.length > 0 ? metaTitle : this.get('scratchName'); if (metaTitle && metaTitle.length > 70) { metaTitle = metaTitle.substring(0, 70).trim(); - metaTitle = Ember.Handlebars.Utils.escapeExpression(metaTitle); - metaTitle = Ember.String.htmlSafe(metaTitle + '…'); + metaTitle = Handlebars.Utils.escapeExpression(metaTitle); + metaTitle = Ember.String.htmlSafe(`${metaTitle}…`); } return metaTitle; }), - seoURL: Ember.computed('scratchSlug', function () { - const blogUrl = this.get('config.blogUrl'), - seoSlug = this.get('scratchSlug') || ''; + seoURL: computed('scratchSlug', function () { + let blogUrl = this.get('config.blogUrl'); + let seoSlug = this.get('scratchSlug') || ''; - let seoURL = blogUrl + '/tag/' + seoSlug; + let seoURL = `${blogUrl}/tag/${seoSlug}`; // only append a slash to the URL if the slug exists if (seoSlug) { @@ -56,73 +56,73 @@ export default Ember.Component.extend({ if (seoURL.length > 70) { seoURL = seoURL.substring(0, 70).trim(); - seoURL = Ember.String.htmlSafe(seoURL + '…'); + seoURL = Ember.String.htmlSafe(`${seoURL}…`); } return seoURL; }), - seoDescription: Ember.computed('scratchDescription', 'scratchMetaDescription', function () { + seoDescription: computed('scratchDescription', 'scratchMetaDescription', function () { let metaDescription = this.get('scratchMetaDescription') || ''; metaDescription = metaDescription.length > 0 ? metaDescription : this.get('scratchDescription'); if (metaDescription && metaDescription.length > 156) { metaDescription = metaDescription.substring(0, 156).trim(); - metaDescription = Ember.Handlebars.Utils.escapeExpression(metaDescription); - metaDescription = Ember.String.htmlSafe(metaDescription + '…'); + metaDescription = Handlebars.Utils.escapeExpression(metaDescription); + metaDescription = Ember.String.htmlSafe(`${metaDescription}…`); } return metaDescription; }), - didReceiveAttrs: function (attrs) { + didReceiveAttrs(attrs) { if (get(attrs, 'newAttrs.tag.value.id') !== get(attrs, 'oldAttrs.tag.value.id')) { this.reset(); } }, - reset: function () { + reset() { this.set('isViewingSubview', false); if (this.$()) { this.$('.settings-menu-pane').scrollTop(0); } }, - focusIn: function () { + focusIn() { key.setScope('tag-settings-form'); }, - focusOut: function () { + focusOut() { key.setScope('default'); }, actions: { - setProperty: function (property, value) { + setProperty(property, value) { this.attrs.setProperty(property, value); }, - setCoverImage: function (image) { + setCoverImage(image) { this.attrs.setProperty('image', image); }, - clearCoverImage: function () { + clearCoverImage() { this.attrs.setProperty('image', ''); }, - setUploaderReference: function () { + setUploaderReference() { // noop }, - openMeta: function () { + openMeta() { this.set('isViewingSubview', true); }, - closeMeta: function () { + closeMeta() { this.set('isViewingSubview', false); }, - deleteTag: function () { + deleteTag() { this.sendAction('openModal', 'delete-tag', this.get('tag')); } } diff --git a/core/client/app/components/gh-tags-management-container.js b/core/client/app/components/gh-tags-management-container.js index b212a5bd1a..bf62a35c86 100644 --- a/core/client/app/components/gh-tags-management-container.js +++ b/core/client/app/components/gh-tags-management-container.js @@ -1,28 +1,29 @@ import Ember from 'ember'; -const {isBlank} = Ember; +const {Component, computed, inject, isBlank, observer, run} = Ember; +const {equal, reads} = computed; -export default Ember.Component.extend({ +export default Component.extend({ classNames: ['view-container'], classNameBindings: ['isMobile'], - mediaQueries: Ember.inject.service(), + mediaQueries: inject.service(), tags: null, selectedTag: null, - isMobile: Ember.computed.reads('mediaQueries.maxWidth600'), - isEmpty: Ember.computed.equal('tags.length', 0), + isMobile: reads('mediaQueries.maxWidth600'), + isEmpty: equal('tags.length', 0), - init: function () { + init() { this._super(...arguments); - Ember.run.schedule('actions', this, this.fireMobileChangeActions); + run.schedule('actions', this, this.fireMobileChangeActions); }, - displaySettingsPane: Ember.computed('isEmpty', 'selectedTag', 'isMobile', function () { - const isEmpty = this.get('isEmpty'), - selectedTag = this.get('selectedTag'), - isMobile = this.get('isMobile'); + displaySettingsPane: computed('isEmpty', 'selectedTag', 'isMobile', function () { + let isEmpty = this.get('isEmpty'); + let selectedTag = this.get('selectedTag'); + let isMobile = this.get('isMobile'); // always display settings pane for blank-slate on mobile if (isMobile && isEmpty) { @@ -38,7 +39,7 @@ export default Ember.Component.extend({ return true; }), - fireMobileChangeActions: Ember.observer('isMobile', function () { + fireMobileChangeActions: observer('isMobile', function () { if (!this.get('isMobile')) { this.sendAction('leftMobile'); } diff --git a/core/client/app/components/gh-textarea.js b/core/client/app/components/gh-textarea.js index 2304a56624..e668dd220f 100644 --- a/core/client/app/components/gh-textarea.js +++ b/core/client/app/components/gh-textarea.js @@ -1,6 +1,8 @@ import Ember from 'ember'; import TextInputMixin from 'ghost/mixins/text-input'; -export default Ember.TextArea.extend(TextInputMixin, { +const {TextArea} = Ember; + +export default TextArea.extend(TextInputMixin, { classNames: 'gh-input' }); diff --git a/core/client/app/components/gh-trim-focus-input.js b/core/client/app/components/gh-trim-focus-input.js index 16b8ce3313..ddd1645d13 100644 --- a/core/client/app/components/gh-trim-focus-input.js +++ b/core/client/app/components/gh-trim-focus-input.js @@ -1,12 +1,14 @@ -import Ember from 'ember'; /*global device*/ +import Ember from 'ember'; -export default Ember.TextField.extend({ +const {TextField, computed, on} = Ember; + +export default TextField.extend({ focus: true, classNames: 'gh-input', attributeBindings: ['autofocus'], - autofocus: Ember.computed(function () { + autofocus: computed(function () { if (this.get('focus')) { return (device.ios()) ? false : 'autofocus'; } @@ -14,7 +16,7 @@ export default Ember.TextField.extend({ return false; }), - focusField: Ember.on('didInsertElement', function () { + focusField: on('didInsertElement', function () { // This fix is required until Mobile Safari has reliable // autofocus, select() or focus() support if (this.get('focus') && !device.ios()) { @@ -22,8 +24,8 @@ export default Ember.TextField.extend({ } }), - trimValue: Ember.on('focusOut', function () { - var text = this.$().val(); + trimValue: on('focusOut', function () { + let text = this.$().val(); this.$().val(text.trim()); }) }); diff --git a/core/client/app/components/gh-upload-modal.js b/core/client/app/components/gh-upload-modal.js index fb46ce3dc2..1f39601d8b 100644 --- a/core/client/app/components/gh-upload-modal.js +++ b/core/client/app/components/gh-upload-modal.js @@ -3,44 +3,50 @@ import ModalDialog from 'ghost/components/gh-modal-dialog'; import upload from 'ghost/assets/lib/uploader'; import cajaSanitizers from 'ghost/utils/caja-sanitizers'; +const {inject, isEmpty} = Ember; + export default ModalDialog.extend({ layoutName: 'components/gh-modal-dialog', - config: Ember.inject.service(), + config: inject.service(), - didInsertElement: function () { - this._super(); + didInsertElement() { + this._super(...arguments); upload.call(this.$('.js-drop-zone'), {fileStorage: this.get('config.fileStorage')}); }, - keyDown: function () { + + keyDown() { this.setErrorState(false); }, - setErrorState: function (state) { + + setErrorState(state) { if (state) { this.$('.js-upload-url').addClass('error'); } else { this.$('.js-upload-url').removeClass('error'); } }, + confirm: { reject: { - func: function () { // The function called on rejection - return true; - }, buttonClass: 'btn btn-default', - text: 'Cancel' // The reject button text + text: 'Cancel', // The reject button text + func() { // The function called on rejection + return true; + } }, + accept: { buttonClass: 'btn btn-blue right', text: 'Save', // The accept button text: 'Save' - func: function () { - var imageType = 'model.' + this.get('imageType'), - value; + func() { + let imageType = `model.${this.get('imageType')}`; + let value; if (this.$('.js-upload-url').val()) { value = this.$('.js-upload-url').val(); - if (!Ember.isEmpty(value) && !cajaSanitizers.url(value)) { + if (!isEmpty(value) && !cajaSanitizers.url(value)) { this.setErrorState(true); return {message: 'Image URI is not valid'}; } @@ -55,12 +61,13 @@ export default ModalDialog.extend({ }, actions: { - closeModal: function () { + closeModal() { this.sendAction(); }, - confirm: function (type) { - var result, - func = this.get('confirm.' + type + '.func'); + + confirm(type) { + let func = this.get(`confirm.${type}.func`); + let result; if (typeof func === 'function') { result = func.apply(this); @@ -68,7 +75,7 @@ export default ModalDialog.extend({ if (!result.message) { this.sendAction(); - this.sendAction('confirm' + type); + this.sendAction(`confirm${type}`); } } } diff --git a/core/client/app/components/gh-uploader.js b/core/client/app/components/gh-uploader.js index 0497f529ba..1e8524fc5b 100644 --- a/core/client/app/components/gh-uploader.js +++ b/core/client/app/components/gh-uploader.js @@ -1,18 +1,20 @@ import Ember from 'ember'; import uploader from 'ghost/assets/lib/uploader'; -export default Ember.Component.extend({ +const {Component, computed, get, inject, isEmpty, run} = Ember; + +export default Component.extend({ classNames: ['image-uploader', 'js-post-image-upload'], - config: Ember.inject.service(), + config: inject.service(), - imageSource: Ember.computed('image', function () { + imageSource: computed('image', function () { return this.get('image') || ''; }), // removes event listeners from the uploader - removeListeners: function () { - var $this = this.$(); + removeListeners() { + let $this = this.$(); $this.off(); $this.find('.js-cancel').off(); @@ -22,20 +24,19 @@ export default Ember.Component.extend({ // between transitions Glimmer will re-use the existing elements including // those that arealready decorated by jQuery. The following works around // situations where the image is changed without a full teardown/rebuild - didReceiveAttrs: function (attrs) { - var oldValue = attrs.oldAttrs && Ember.get(attrs.oldAttrs, 'image.value'), - newValue = attrs.newAttrs && Ember.get(attrs.newAttrs, 'image.value'), - self = this; + didReceiveAttrs(attrs) { + let oldValue = attrs.oldAttrs && get(attrs.oldAttrs, 'image.value'); + let newValue = attrs.newAttrs && get(attrs.newAttrs, 'image.value'); // always reset when we receive a blank image // - handles navigating to populated image from blank image - if (Ember.isEmpty(newValue) && !Ember.isEmpty(oldValue)) { - self.$()[0].uploaderUi.reset(); + if (isEmpty(newValue) && !isEmpty(oldValue)) { + this.$()[0].uploaderUi.reset(); } // re-init if we receive a new image but the uploader is blank // - handles back button navigating from blank image to populated image - if (!Ember.isEmpty(newValue) && this.$()) { + if (!isEmpty(newValue) && this.$()) { if (this.$('.js-upload-target').attr('src') === '') { this.$()[0].uploaderUi.reset(); this.$()[0].uploaderUi.initWithImage(); @@ -43,34 +44,31 @@ export default Ember.Component.extend({ } }, - didInsertElement: function () { + didInsertElement() { this.send('initUploader'); }, - willDestroyElement: function () { + willDestroyElement() { this.removeListeners(); }, actions: { - initUploader: function () { - var ref, - el = this.$(), - self = this; - - ref = uploader.call(el, { + initUploader() { + let el = this.$(); + let ref = uploader.call(el, { editor: true, fileStorage: this.get('config.fileStorage') }); - el.on('uploadsuccess', function (event, result) { + el.on('uploadsuccess', (event, result) => { if (result && result !== '' && result !== 'http://') { - Ember.run(self, function () { + run(this, function () { this.sendAction('uploaded', result); }); } }); - el.on('imagecleared', Ember.run.bind(self, 'sendAction', 'canceled')); + el.on('imagecleared', run.bind(this, 'sendAction', 'canceled')); this.sendAction('initUploader', ref); } diff --git a/core/client/app/components/gh-url-preview.js b/core/client/app/components/gh-url-preview.js index d7c4951be8..ad66873916 100644 --- a/core/client/app/components/gh-url-preview.js +++ b/core/client/app/components/gh-url-preview.js @@ -1,26 +1,30 @@ import Ember from 'ember'; + +const {Component, computed, inject} = Ember; + /* Example usage: {{gh-url-preview prefix="tag" slug=theSlugValue tagName="p" classNames="description"}} */ -export default Ember.Component.extend({ +export default Component.extend({ classNames: 'ghost-url-preview', prefix: null, slug: null, - config: Ember.inject.service(), + config: inject.service(), - url: Ember.computed('slug', function () { + url: computed('slug', function () { // Get the blog URL and strip the scheme - var blogUrl = this.get('config.blogUrl'), - noSchemeBlogUrl = blogUrl.substr(blogUrl.indexOf('://') + 3), // Remove `http[s]://` + let blogUrl = this.get('config.blogUrl'); + // Remove `http[s]://` + let noSchemeBlogUrl = blogUrl.substr(blogUrl.indexOf('://') + 3); - // Get the prefix and slug values - prefix = this.get('prefix') ? this.get('prefix') + '/' : '', - slug = this.get('slug') ? this.get('slug') + '/' : '', + // Get the prefix and slug values + let prefix = this.get('prefix') ? `${this.get('prefix')}/` : ''; + let slug = this.get('slug') ? `${this.get('slug')}/` : ''; - // Join parts of the URL together with slashes - theUrl = noSchemeBlogUrl + '/' + prefix + slug; + // Join parts of the URL together with slashes + let theUrl = `${noSchemeBlogUrl}/${prefix}${slug}`; return theUrl; }) diff --git a/core/client/app/components/gh-user-active.js b/core/client/app/components/gh-user-active.js index 137ca8cf10..c333dce4bc 100644 --- a/core/client/app/components/gh-user-active.js +++ b/core/client/app/components/gh-user-active.js @@ -1,24 +1,26 @@ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component, computed, inject} = Ember; + +export default Component.extend({ tagName: '', user: null, - ghostPaths: Ember.inject.service('ghost-paths'), + ghostPaths: inject.service('ghost-paths'), - userDefault: Ember.computed('ghostPaths', function () { + userDefault: 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'); + userImageBackground: computed('user.image', 'userDefault', function () { + let url = this.get('user.image') || this.get('userDefault'); return Ember.String.htmlSafe(`background-image: url(${url})`); }), - lastLogin: Ember.computed('user.last_login', function () { - var lastLogin = this.get('user.last_login'); + lastLogin: computed('user.last_login', function () { + let lastLogin = this.get('user.last_login'); return lastLogin ? lastLogin.fromNow() : '(Never)'; }) diff --git a/core/client/app/components/gh-user-invited.js b/core/client/app/components/gh-user-invited.js index b120e9169e..192b9344c9 100644 --- a/core/client/app/components/gh-user-invited.js +++ b/core/client/app/components/gh-user-invited.js @@ -1,28 +1,29 @@ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component, computed, inject} = Ember; + +export default Component.extend({ tagName: '', user: null, isSending: false, - notifications: Ember.inject.service(), + notifications: inject.service(), - createdAt: Ember.computed('user.created_at', function () { - var createdAt = this.get('user.created_at'); + createdAt: computed('user.created_at', function () { + let createdAt = this.get('user.created_at'); return createdAt ? createdAt.fromNow() : ''; }), actions: { - resend: function () { - var user = this.get('user'), - notifications = this.get('notifications'), - self = this; + resend() { + let user = this.get('user'); + let notifications = this.get('notifications'); this.set('isSending', true); - user.resendInvite().then(function (result) { - var notificationText = 'Invitation resent! (' + user.get('email') + ')'; + user.resendInvite().then((result) => { + let notificationText = `Invitation resent! (${user.get('email')})`; // 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'. @@ -33,32 +34,31 @@ export default Ember.Component.extend({ notifications.showNotification(notificationText); notifications.closeAlerts('invite.resend'); } - }).catch(function (error) { + }).catch((error) => { notifications.showAPIError(error, {key: 'invite.resend'}); - }).finally(function () { - self.set('isSending', false); + }).finally(() => { + this.set('isSending', false); }); }, - revoke: function () { - var user = this.get('user'), - email = user.get('email'), - notifications = this.get('notifications'), - self = this; + revoke() { + let user = this.get('user'); + let email = user.get('email'); + let notifications = this.get('notifications'); // reload the user to get the most up-to-date information - user.reload().then(function () { + user.reload().then(() => { if (user.get('invited')) { - user.destroyRecord().then(function () { - var notificationText = 'Invitation revoked. (' + email + ')'; + user.destroyRecord().then(() => { + let notificationText = `Invitation revoked. (${email})`; notifications.showNotification(notificationText); notifications.closeAlerts('invite.revoke'); - }).catch(function (error) { + }).catch((error) => { notifications.showAPIError(error, {key: 'invite.revoke'}); }); } else { // if the user is no longer marked as "invited", then show a warning and reload the route - self.sendAction('reload'); + this.sendAction('reload'); notifications.showAlert('This user has already accepted the invitation.', {type: 'error', delayed: true, key: 'invite.revoke.already-accepted'}); } }); diff --git a/core/client/app/components/gh-validation-status-container.js b/core/client/app/components/gh-validation-status-container.js index 61c0c37215..5ddfb3ba13 100644 --- a/core/client/app/components/gh-validation-status-container.js +++ b/core/client/app/components/gh-validation-status-container.js @@ -1,6 +1,8 @@ import Ember from 'ember'; import ValidationStateMixin from 'ghost/mixins/validation-state'; +const {Component, computed} = Ember; + /** * Handles the CSS necessary to show a specific property state. When passed a * DS.Errors object and a property name, if the DS.Errors object has errors for @@ -8,12 +10,12 @@ import ValidationStateMixin from 'ghost/mixins/validation-state'; * @param {DS.Errors} errors The DS.Errors object * @param {string} property Name of the property */ -export default Ember.Component.extend(ValidationStateMixin, { +export default Component.extend(ValidationStateMixin, { classNameBindings: ['errorClass'], - errorClass: Ember.computed('property', 'hasError', 'hasValidated.[]', function () { - let hasValidated = this.get('hasValidated'), - property = this.get('property'); + errorClass: computed('property', 'hasError', 'hasValidated.[]', function () { + let hasValidated = this.get('hasValidated'); + let property = this.get('property'); if (hasValidated && hasValidated.contains(property)) { return this.get('hasError') ? 'error' : 'success'; diff --git a/core/client/app/components/gh-view-title.js b/core/client/app/components/gh-view-title.js index 0aff247c5d..0c70b0c9d2 100644 --- a/core/client/app/components/gh-view-title.js +++ b/core/client/app/components/gh-view-title.js @@ -1,10 +1,13 @@ import Ember from 'ember'; -export default Ember.Component.extend({ +const {Component} = Ember; + +export default Component.extend({ tagName: 'h2', classNames: ['view-title'], + actions: { - openMobileMenu: function () { + openMobileMenu() { this.sendAction('openMobileMenu'); } } diff --git a/core/client/app/controllers/about.js b/core/client/app/controllers/about.js index 7784da82a7..b29839e5a7 100644 --- a/core/client/app/controllers/about.js +++ b/core/client/app/controllers/about.js @@ -1,10 +1,12 @@ import Ember from 'ember'; -export default Ember.Controller.extend({ +const {Controller} = Ember; + +export default Controller.extend({ updateNotificationCount: 0, actions: { - updateNotificationChange: function (count) { + updateNotificationChange(count) { this.set('updateNotificationCount', count); } } diff --git a/core/client/app/controllers/application.js b/core/client/app/controllers/application.js index 0f8745fabd..3b5bb2fb82 100644 --- a/core/client/app/controllers/application.js +++ b/core/client/app/controllers/application.js @@ -1,22 +1,22 @@ import Ember from 'ember'; -export default Ember.Controller.extend({ - dropdown: Ember.inject.service(), +const {Controller, computed, inject} = Ember; - // jscs: disable - signedOut: Ember.computed.match('currentPath', /(signin|signup|setup|reset)/), - // jscs: enable +export default Controller.extend({ + dropdown: inject.service(), + + signedOut: computed.match('currentPath', /(signin|signup|setup|reset)/), topNotificationCount: 0, showMobileMenu: false, showSettingsMenu: false, autoNav: false, - autoNavOpen: Ember.computed('autoNav', { - get: function () { + autoNavOpen: computed('autoNav', { + get() { return false; }, - set: function (key, value) { + set(key, value) { if (this.get('autoNav')) { return value; } @@ -25,23 +25,23 @@ export default Ember.Controller.extend({ }), actions: { - topNotificationChange: function (count) { + topNotificationChange(count) { this.set('topNotificationCount', count); }, - toggleAutoNav: function () { + toggleAutoNav() { this.toggleProperty('autoNav'); }, - openAutoNav: function () { + openAutoNav() { this.set('autoNavOpen', true); }, - closeAutoNav: function () { + closeAutoNav() { this.set('autoNavOpen', false); }, - closeMobileMenu: function () { + closeMobileMenu() { this.set('showMobileMenu', false); } } diff --git a/core/client/app/controllers/editor/edit.js b/core/client/app/controllers/editor/edit.js index c0904b6571..828e21f746 100644 --- a/core/client/app/controllers/editor/edit.js +++ b/core/client/app/controllers/editor/edit.js @@ -1,9 +1,11 @@ import Ember from 'ember'; import EditorControllerMixin from 'ghost/mixins/editor-base-controller'; -export default Ember.Controller.extend(EditorControllerMixin, { +const {Controller} = Ember; + +export default Controller.extend(EditorControllerMixin, { actions: { - openDeleteModal: function () { + openDeleteModal() { this.send('openModal', 'delete-post', this.get('model')); } } diff --git a/core/client/app/controllers/editor/new.js b/core/client/app/controllers/editor/new.js index 8e2f9dbfb0..6dc2ea5040 100644 --- a/core/client/app/controllers/editor/new.js +++ b/core/client/app/controllers/editor/new.js @@ -1,18 +1,23 @@ import Ember from 'ember'; import EditorControllerMixin from 'ghost/mixins/editor-base-controller'; -export default Ember.Controller.extend(EditorControllerMixin, { +const {Controller} = Ember; + +function K() { + return this; +} + +export default Controller.extend(EditorControllerMixin, { // Overriding autoSave on the base controller, as the new controller shouldn't be autosaving - autoSave: Ember.K, + autoSave: K, actions: { /** * Redirect to editor after the first save */ - save: function (options) { - var self = this; - return this._super(options).then(function (model) { + save(options) { + return this._super(options).then((model) => { if (model.get('id')) { - self.replaceRoute('editor.edit', model); + this.replaceRoute('editor.edit', model); } }); } diff --git a/core/client/app/controllers/error.js b/core/client/app/controllers/error.js index e13faba3f7..fae8a96ac5 100644 --- a/core/client/app/controllers/error.js +++ b/core/client/app/controllers/error.js @@ -1,15 +1,20 @@ import Ember from 'ember'; -export default Ember.Controller.extend({ - code: Ember.computed('content.status', function () { +const {Controller, computed} = Ember; + +export default Controller.extend({ + + stack: false, + + code: computed('content.status', function () { return this.get('content.status') > 200 ? this.get('content.status') : 500; }), - message: Ember.computed('content.statusText', function () { + + message: computed('content.statusText', function () { if (this.get('code') === 404) { return 'Page not found'; } return this.get('content.statusText') !== 'error' ? this.get('content.statusText') : 'Internal Server Error'; - }), - stack: false + }) }); diff --git a/core/client/app/controllers/feature.js b/core/client/app/controllers/feature.js index 5aa36375eb..5c2fc05981 100644 --- a/core/client/app/controllers/feature.js +++ b/core/client/app/controllers/feature.js @@ -1,20 +1,14 @@ import Ember from 'ember'; -export default Ember.Controller.extend(Ember.PromiseProxyMixin, { - init: function () { - var promise; +const {Controller, PromiseProxyMixin, computed} = Ember; +const {alias} = computed; - promise = this.store.query('setting', {type: 'blog,theme'}).then(function (settings) { - return settings.get('firstObject'); - }); +export default Controller.extend(PromiseProxyMixin, { - this.set('promise', promise); - }, + setting: alias('content'), - setting: Ember.computed.alias('content'), - - labs: Ember.computed('isSettled', 'setting.labs', function () { - var value = {}; + labs: computed('isSettled', 'setting.labs', function () { + let value = {}; if (this.get('isFulfilled')) { try { @@ -27,7 +21,15 @@ export default Ember.Controller.extend(Ember.PromiseProxyMixin, { return value; }), - publicAPI: Ember.computed('config.publicAPI', 'labs.publicAPI', function () { + publicAPI: computed('config.publicAPI', 'labs.publicAPI', function () { return this.get('config.publicAPI') || this.get('labs.publicAPI'); - }) + }), + + init() { + let promise = this.store.query('setting', {type: 'blog,theme'}).then((settings) => { + return settings.get('firstObject'); + }); + + this.set('promise', promise); + } }); diff --git a/core/client/app/controllers/modals/copy-html.js b/core/client/app/controllers/modals/copy-html.js index c4550ab9a4..7905d43949 100644 --- a/core/client/app/controllers/modals/copy-html.js +++ b/core/client/app/controllers/modals/copy-html.js @@ -1,5 +1,8 @@ import Ember from 'ember'; -export default Ember.Controller.extend({ - generatedHTML: Ember.computed.alias('model.generatedHTML') +const {Controller, computed} = Ember; +const {alias} = computed; + +export default Controller.extend({ + generatedHTML: alias('model.generatedHTML') }); diff --git a/core/client/app/controllers/modals/delete-all.js b/core/client/app/controllers/modals/delete-all.js index 16cbe61a7f..3980a3e25a 100644 --- a/core/client/app/controllers/modals/delete-all.js +++ b/core/client/app/controllers/modals/delete-all.js @@ -1,29 +1,11 @@ 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(), +const {Controller, inject} = Ember; - actions: { - confirmAccept: function () { - var self = this; - - ajax(this.get('ghostPaths.url').api('db'), { - type: 'DELETE' - }).then(function () { - self.get('notifications').showAlert('All content deleted from database.', {type: 'success', key: 'all-content.delete.success'}); - self.store.unloadAll('post'); - self.store.unloadAll('tag'); - }).catch(function (response) { - self.get('notifications').showAPIError(response, {key: 'all-content.delete'}); - }); - }, - - confirmReject: function () { - return false; - } - }, +export default Controller.extend({ + ghostPaths: inject.service('ghost-paths'), + notifications: inject.service(), confirm: { accept: { @@ -34,5 +16,23 @@ export default Ember.Controller.extend({ text: 'Cancel', buttonClass: 'btn btn-default btn-minor' } + }, + + actions: { + confirmAccept() { + ajax(this.get('ghostPaths.url').api('db'), { + type: 'DELETE' + }).then(() => { + this.get('notifications').showAlert('All content deleted from database.', {type: 'success', key: 'all-content.delete.success'}); + this.store.unloadAll('post'); + this.store.unloadAll('tag'); + }).catch((response) => { + this.get('notifications').showAPIError(response, {key: 'all-content.delete'}); + }); + }, + + confirmReject() { + return false; + } } }); diff --git a/core/client/app/controllers/modals/delete-post.js b/core/client/app/controllers/modals/delete-post.js index b12b62d791..560f1e4ff7 100644 --- a/core/client/app/controllers/modals/delete-post.js +++ b/core/client/app/controllers/modals/delete-post.js @@ -1,30 +1,10 @@ import Ember from 'ember'; -export default Ember.Controller.extend({ - dropdown: Ember.inject.service(), - notifications: Ember.inject.service(), +const {Controller, inject} = Ember; - actions: { - confirmAccept: function () { - var self = this, - model = this.get('model'); - - // definitely want to clear the data store and post of any unsaved, client-generated tags - model.updateTags(); - - model.destroyRecord().then(function () { - self.get('dropdown').closeDropdowns(); - self.get('notifications').closeAlerts('post.delete'); - self.transitionToRoute('posts.index'); - }, function () { - self.get('notifications').showAlert('Your post could not be deleted. Please try again.', {type: 'error', key: 'post.delete.failed'}); - }); - }, - - confirmReject: function () { - return false; - } - }, +export default Controller.extend({ + dropdown: inject.service(), + notifications: inject.service(), confirm: { accept: { @@ -35,5 +15,26 @@ export default Ember.Controller.extend({ text: 'Cancel', buttonClass: 'btn btn-default btn-minor' } + }, + + actions: { + confirmAccept() { + let model = this.get('model'); + + // definitely want to clear the data store and post of any unsaved, client-generated tags + model.updateTags(); + + model.destroyRecord().then(() => { + this.get('dropdown').closeDropdowns(); + this.get('notifications').closeAlerts('post.delete'); + this.transitionToRoute('posts.index'); + }, () => { + this.get('notifications').showAlert('Your post could not be deleted. Please try again.', {type: 'error', key: 'post.delete.failed'}); + }); + }, + + confirmReject() { + return false; + } } }); diff --git a/core/client/app/controllers/modals/delete-tag.js b/core/client/app/controllers/modals/delete-tag.js index 8ef0b694c1..08c1dfa5d2 100644 --- a/core/client/app/controllers/modals/delete-tag.js +++ b/core/client/app/controllers/modals/delete-tag.js @@ -11,7 +11,7 @@ export default Controller.extend({ }), actions: { - confirmAccept: function () { + confirmAccept() { let tag = this.get('model'); this.send('closeMenus'); @@ -27,7 +27,7 @@ export default Controller.extend({ }); }, - confirmReject: function () { + confirmReject() { return false; } }, diff --git a/core/client/app/controllers/modals/delete-user.js b/core/client/app/controllers/modals/delete-user.js index 38f545048f..8844b37cd9 100644 --- a/core/client/app/controllers/modals/delete-user.js +++ b/core/client/app/controllers/modals/delete-user.js @@ -1,47 +1,30 @@ import Ember from 'ember'; -export default Ember.Controller.extend({ - notifications: Ember.inject.service(), +const {Controller, PromiseProxyMixin, computed, inject} = Ember; +const {alias} = computed; - userPostCount: Ember.computed('model.id', function () { - var promise, - query = { - filter: `author:${this.get('model.slug')}`, - status: 'all' - }; +export default Controller.extend({ + notifications: inject.service(), - promise = this.store.query('post', query).then(function (results) { + userPostCount: computed('model.id', function () { + let query = { + filter: `author:${this.get('model.slug')}`, + status: 'all' + }; + + let promise = this.store.query('post', query).then((results) => { return results.meta.pagination.total; }); - return Ember.Object.extend(Ember.PromiseProxyMixin, { - count: Ember.computed.alias('content'), + return Ember.Object.extend(PromiseProxyMixin, { + count: alias('content'), - inflection: Ember.computed('count', function () { + inflection: computed('count', function () { return this.get('count') > 1 ? 'posts' : 'post'; }) - }).create({promise: promise}); + }).create({promise}); }), - actions: { - confirmAccept: function () { - var self = this, - user = this.get('model'); - - user.destroyRecord().then(function () { - self.get('notifications').closeAlerts('user.delete'); - self.store.unloadAll('post'); - self.transitionToRoute('team'); - }, function () { - self.get('notifications').showAlert('The user could not be deleted. Please try again.', {type: 'error', key: 'user.delete.failed'}); - }); - }, - - confirmReject: function () { - return false; - } - }, - confirm: { accept: { text: 'Delete User', @@ -51,5 +34,23 @@ export default Ember.Controller.extend({ text: 'Cancel', buttonClass: 'btn btn-default btn-minor' } + }, + + actions: { + confirmAccept() { + let user = this.get('model'); + + user.destroyRecord().then(() => { + this.get('notifications').closeAlerts('user.delete'); + this.store.unloadAll('post'); + this.transitionToRoute('team'); + }, () => { + this.get('notifications').showAlert('The user could not be deleted. Please try again.', {type: 'error', key: 'user.delete.failed'}); + }); + }, + + confirmReject() { + return false; + } } }); diff --git a/core/client/app/controllers/modals/invite-new-user.js b/core/client/app/controllers/modals/invite-new-user.js index 5866d32a0b..8a8b8b9592 100644 --- a/core/client/app/controllers/modals/invite-new-user.js +++ b/core/client/app/controllers/modals/invite-new-user.js @@ -1,29 +1,29 @@ import Ember from 'ember'; import ValidationEngine from 'ghost/mixins/validation-engine'; -export default Ember.Controller.extend(ValidationEngine, { - notifications: Ember.inject.service(), +const {Controller, computed, inject, observer} = Ember; + +export default Controller.extend(ValidationEngine, { + notifications: inject.service(), validationType: 'signup', role: null, authorRole: null, - roles: Ember.computed(function () { + roles: computed(function () { return this.store.query('role', {permissions: 'assign'}); }), // Used to set the initial value for the dropdown - authorRoleObserver: Ember.observer('roles.@each.role', function () { - var self = this; + authorRoleObserver: observer('roles.@each.role', function () { + this.get('roles').then((roles) => { + let authorRole = roles.findBy('name', 'Author'); - this.get('roles').then(function (roles) { - var authorRole = roles.findBy('name', 'Author'); + this.set('authorRole', authorRole); - self.set('authorRole', authorRole); - - if (!self.get('role')) { - self.set('role', authorRole); + if (!this.get('role')) { + this.set('role', authorRole); } }); }), @@ -37,69 +37,68 @@ export default Ember.Controller.extend(ValidationEngine, { } }, + confirmReject() { + return false; + }, + actions: { - setRole: function (role) { + setRole(role) { this.set('role', role); }, - confirmAccept: function () { - var email = this.get('email'), - role = this.get('role'), - validationErrors = this.get('errors.messages'), - self = this, - newUser; + confirmAccept() { + let email = this.get('email'); + let role = this.get('role'); + let validationErrors = this.get('errors.messages'); + let newUser; // reset the form and close the modal this.set('email', ''); - this.set('role', self.get('authorRole')); + this.set('role', this.get('authorRole')); - this.store.findAll('user', {reload: true}).then(function (result) { - var invitedUser = result.findBy('email', email); + this.store.findAll('user', {reload: true}).then((result) => { + let invitedUser = result.findBy('email', email); if (invitedUser) { if (invitedUser.get('status') === 'invited' || invitedUser.get('status') === 'invited-pending') { - self.get('notifications').showAlert('A user with that email address was already invited.', {type: 'warn', key: 'invite.send.already-invited'}); + this.get('notifications').showAlert('A user with that email address was already invited.', {type: 'warn', key: 'invite.send.already-invited'}); } else { - self.get('notifications').showAlert('A user with that email address already exists.', {type: 'warn', key: 'invite.send.user-exists'}); + this.get('notifications').showAlert('A user with that email address already exists.', {type: 'warn', key: 'invite.send.user-exists'}); } } else { - newUser = self.store.createRecord('user', { - email: email, - status: 'invited', - role: role + newUser = this.store.createRecord('user', { + email, + role, + status: 'invited' }); - newUser.save().then(function () { - var notificationText = 'Invitation sent! (' + email + ')'; + newUser.save().then(() => { + let notificationText = `Invitation sent! (${email})`; // 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').showAlert('Invitation email was not sent. Please try resending.', {type: 'error', key: 'invite.send.failed'}); + this.get('notifications').showAlert('Invitation email was not sent. Please try resending.', {type: 'error', key: 'invite.send.failed'}); } else { - self.get('notifications').closeAlerts('invite.send'); - self.get('notifications').showNotification(notificationText); + this.get('notifications').closeAlerts('invite.send'); + this.get('notifications').showNotification(notificationText); } - }).catch(function (errors) { + }).catch((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 if (errors) { - self.get('notifications').showErrors(errors, {key: 'invite.send'}); + this.get('notifications').showErrors(errors, {key: 'invite.send'}); } else if (validationErrors) { - self.get('notifications').showAlert(validationErrors.toString(), {type: 'error', key: 'invite.send.validation-error'}); + this.get('notifications').showAlert(validationErrors.toString(), {type: 'error', key: 'invite.send.validation-error'}); } - }).finally(function () { - self.get('errors').clear(); + }).finally(() => { + this.get('errors').clear(); }); } }); - }, - - confirmReject: function () { - return false; } } }); diff --git a/core/client/app/controllers/modals/leave-editor.js b/core/client/app/controllers/modals/leave-editor.js index 2712236c4a..5d0ff8b840 100644 --- a/core/client/app/controllers/modals/leave-editor.js +++ b/core/client/app/controllers/modals/leave-editor.js @@ -1,18 +1,32 @@ import Ember from 'ember'; -export default Ember.Controller.extend({ - notifications: Ember.inject.service(), +const {Controller, computed, inject, isArray} = Ember; +const {alias} = computed; - args: Ember.computed.alias('model'), +export default Controller.extend({ + notifications: inject.service(), + + args: alias('model'), + + confirm: { + accept: { + text: 'Leave', + buttonClass: 'btn btn-red' + }, + reject: { + text: 'Stay', + buttonClass: 'btn btn-default btn-minor' + } + }, actions: { - confirmAccept: function () { - var args = this.get('args'), - editorController, + confirmAccept() { + let args = this.get('args'); + let editorController, model, transition; - if (Ember.isArray(args)) { + if (isArray(args)) { editorController = args[0]; transition = args[1]; model = editorController.get('model'); @@ -44,18 +58,7 @@ export default Ember.Controller.extend({ transition.retry(); }, - confirmReject: function () { - } - }, - - confirm: { - accept: { - text: 'Leave', - buttonClass: 'btn btn-red' - }, - reject: { - text: 'Stay', - buttonClass: 'btn btn-default btn-minor' + confirmReject() { } } }); diff --git a/core/client/app/controllers/modals/signin.js b/core/client/app/controllers/modals/signin.js index c3b2c627c3..e38d26d3af 100644 --- a/core/client/app/controllers/modals/signin.js +++ b/core/client/app/controllers/modals/signin.js @@ -1,57 +1,56 @@ import Ember from 'ember'; import ValidationEngine from 'ghost/mixins/validation-engine'; -export default Ember.Controller.extend(ValidationEngine, { +const {Controller, computed, inject} = Ember; + +export default Controller.extend(ValidationEngine, { validationType: 'signin', submitting: false, - application: Ember.inject.controller(), - notifications: Ember.inject.service(), - session: Ember.inject.service(), + application: inject.controller(), + notifications: inject.service(), + session: inject.service(), - identification: Ember.computed('session.user.email', function () { + identification: computed('session.user.email', function () { return this.get('session.user.email'); }), actions: { - authenticate: function () { - var appController = this.get('application'), - authStrategy = 'authenticator:oauth2', - self = this; + authenticate() { + let appController = this.get('application'); + let authStrategy = 'authenticator:oauth2'; appController.set('skipAuthSuccessHandler', true); - this.get('session').authenticate(authStrategy, this.get('identification'), this.get('password')).then(function () { - self.send('closeModal'); - self.set('password', ''); - self.get('notifications').closeAlerts('post.save'); - }).catch(function () { + this.get('session').authenticate(authStrategy, this.get('identification'), this.get('password')).then(() => { + this.send('closeModal'); + this.set('password', ''); + this.get('notifications').closeAlerts('post.save'); + }).catch(() => { // if authentication fails a rejected promise will be returned. // it needs to be caught so it doesn't generate an exception in the console, // but it's actually "handled" by the sessionAuthenticationFailed action handler. - }).finally(function () { - self.toggleProperty('submitting'); + }).finally(() => { + this.toggleProperty('submitting'); appController.set('skipAuthSuccessHandler', undefined); }); }, - validateAndAuthenticate: function () { - var self = this; - + validateAndAuthenticate() { this.toggleProperty('submitting'); // Manually trigger events for input fields, ensuring legacy compatibility with // browsers and password managers that don't send proper events on autofill $('#login').find('input').trigger('change'); - this.validate({format: false}).then(function () { - self.send('authenticate'); - }).catch(function (errors) { - self.get('notifications').showErrors(errors); + this.validate({format: false}).then(() => { + this.send('authenticate'); + }).catch((errors) => { + this.get('notifications').showErrors(errors); }); }, - confirmAccept: function () { + confirmAccept() { this.send('validateAndAuthenticate'); } } diff --git a/core/client/app/controllers/modals/transfer-owner.js b/core/client/app/controllers/modals/transfer-owner.js index cdd98bc744..ada0bc1601 100644 --- a/core/client/app/controllers/modals/transfer-owner.js +++ b/core/client/app/controllers/modals/transfer-owner.js @@ -1,48 +1,12 @@ 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(), +const {Controller, inject, isArray} = Ember; - actions: { - confirmAccept: function () { - var user = this.get('model'), - url = this.get('ghostPaths.url').api('users', 'owner'), - self = this; - - self.get('dropdown').closeDropdowns(); - - ajax(url, { - type: 'PUT', - data: { - owner: [{ - id: user.get('id') - }] - } - }).then(function (response) { - // manually update the roles for the users that just changed roles - // because store.pushPayload is not working with embedded relations - if (response && Ember.isArray(response.users)) { - response.users.forEach(function (userJSON) { - var user = self.store.peekRecord('user', userJSON.id), - role = self.store.peekRecord('role', userJSON.roles[0].id); - - user.set('role', role); - }); - } - - self.get('notifications').showAlert('Ownership successfully transferred to ' + user.get('name'), {type: 'success', key: 'owner.transfer.success'}); - }).catch(function (error) { - self.get('notifications').showAPIError(error, {key: 'owner.transfer'}); - }); - }, - - confirmReject: function () { - return false; - } - }, +export default Controller.extend({ + dropdown: inject.service(), + ghostPaths: inject.service('ghost-paths'), + notifications: inject.service(), confirm: { accept: { @@ -53,5 +17,42 @@ export default Ember.Controller.extend({ text: 'Cancel', buttonClass: 'btn btn-default btn-minor' } + }, + + actions: { + confirmAccept() { + let user = this.get('model'); + let url = this.get('ghostPaths.url').api('users', 'owner'); + + this.get('dropdown').closeDropdowns(); + + ajax(url, { + type: 'PUT', + data: { + owner: [{ + id: user.get('id') + }] + } + }).then((response) => { + // manually update the roles for the users that just changed roles + // because store.pushPayload is not working with embedded relations + if (response && isArray(response.users)) { + response.users.forEach((userJSON) => { + let user = this.store.peekRecord('user', userJSON.id); + let role = this.store.peekRecord('role', userJSON.roles[0].id); + + user.set('role', role); + }); + } + + this.get('notifications').showAlert(`Ownership successfully transferred to ${user.get('name')}`, {type: 'success', key: 'owner.transfer.success'}); + }).catch((error) => { + this.get('notifications').showAPIError(error, {key: 'owner.transfer'}); + }); + }, + + confirmReject() { + return false; + } } }); diff --git a/core/client/app/controllers/modals/upload.js b/core/client/app/controllers/modals/upload.js index 2e423f46a7..880a86e8ec 100644 --- a/core/client/app/controllers/modals/upload.js +++ b/core/client/app/controllers/modals/upload.js @@ -1,22 +1,24 @@ import Ember from 'ember'; -export default Ember.Controller.extend({ - notifications: Ember.inject.service(), +const {Controller, inject} = Ember; + +export default Controller.extend({ + notifications: inject.service(), acceptEncoding: 'image/*', actions: { - confirmAccept: function () { - var notifications = this.get('notifications'); + confirmAccept() { + let notifications = this.get('notifications'); - this.get('model').save().then(function (model) { + this.get('model').save().then((model) => { return model; - }).catch(function (err) { + }).catch((err) => { notifications.showAPIError(err, {key: 'image.upload'}); }); }, - confirmReject: function () { + confirmReject() { return false; } } diff --git a/core/client/app/controllers/post-settings-menu.js b/core/client/app/controllers/post-settings-menu.js index d4c1db1a2e..9a4f799690 100644 --- a/core/client/app/controllers/post-settings-menu.js +++ b/core/client/app/controllers/post-settings-menu.js @@ -5,48 +5,48 @@ import SlugGenerator from 'ghost/models/slug-generator'; import boundOneWay from 'ghost/utils/bound-one-way'; import isNumber from 'ghost/utils/isNumber'; -export default Ember.Controller.extend(SettingsMenuMixin, { +const {ArrayProxy, Controller, Handlebars, PromiseProxyMixin, RSVP, computed, guidFor, inject, isArray, observer, run} = Ember; + +export default 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(), - session: Ember.inject.service(), + application: inject.controller(), + config: inject.service(), + ghostPaths: inject.service('ghost-paths'), + notifications: inject.service(), + session: inject.service(), - initializeSelectedAuthor: Ember.observer('model', function () { - var self = this; - - return this.get('model.author').then(function (author) { - self.set('selectedAuthor', author); + initializeSelectedAuthor: observer('model', function () { + return this.get('model.author').then((author) => { + this.set('selectedAuthor', author); return author; }); }), - authors: Ember.computed(function () { + authors: computed(function () { // Loaded asynchronously, so must use promise proxies. - var deferred = {}; + let deferred = {}; - deferred.promise = this.store.query('user', {limit: 'all'}).then(function (users) { + deferred.promise = this.store.query('user', {limit: 'all'}).then((users) => { return users.rejectBy('id', 'me').sortBy('name'); - }).then(function (users) { - return users.filter(function (user) { + }).then((users) => { + return users.filter((user) => { return user.get('active'); }); }); - return Ember.ArrayProxy - .extend(Ember.PromiseProxyMixin) + return ArrayProxy + .extend(PromiseProxyMixin) .create(deferred); }), /*jshint unused:false */ - publishedAtValue: Ember.computed('model.published_at', { - get: function () { - var pubDate = this.get('model.published_at'); + publishedAtValue: computed('model.published_at', { + get() { + let pubDate = this.get('model.published_at'); if (pubDate) { return formatDate(pubDate); @@ -54,7 +54,7 @@ export default Ember.Controller.extend(SettingsMenuMixin, { return formatDate(moment()); }, - set: function (key, value) { + set(key, value) { // We're using a fake setter to reset // the cache for this property return formatDate(moment()); @@ -65,7 +65,7 @@ export default Ember.Controller.extend(SettingsMenuMixin, { slugValue: boundOneWay('model.slug'), // Lazy load the slug generator - slugGenerator: Ember.computed(function () { + slugGenerator: computed(function () { return SlugGenerator.create({ ghostPaths: this.get('ghostPaths'), slugType: 'post' @@ -73,21 +73,20 @@ export default Ember.Controller.extend(SettingsMenuMixin, { }), // Requests slug from title - generateAndSetSlug: function (destination) { - var self = this, - title = this.get('model.titleScratch'), - afterSave = this.get('lastPromise'), - promise; + generateAndSetSlug(destination) { + let title = this.get('model.titleScratch'); + let afterSave = this.get('lastPromise'); + let promise; // Only set an "untitled" slug once per post if (title === '(Untitled)' && this.get('model.slug')) { return; } - promise = Ember.RSVP.resolve(afterSave).then(function () { - return self.get('slugGenerator').generateSlug(title).then(function (slug) { - self.set(destination, slug); - }).catch(function () { + promise = RSVP.resolve(afterSave).then(() => { + return this.get('slugGenerator').generateSlug(title).then((slug) => { + this.set(destination, slug); + }).catch(() => { // Nothing to do (would be nice to log this somewhere though), // but a rejected promise needs to be handled here so that a resolved // promise is returned. @@ -100,25 +99,24 @@ export default Ember.Controller.extend(SettingsMenuMixin, { metaTitleScratch: boundOneWay('model.meta_title'), metaDescriptionScratch: boundOneWay('model.meta_description'), - seoTitle: Ember.computed('model.titleScratch', 'metaTitleScratch', function () { - var metaTitle = this.get('metaTitleScratch') || ''; + seoTitle: computed('model.titleScratch', 'metaTitleScratch', function () { + let metaTitle = this.get('metaTitleScratch') || ''; metaTitle = metaTitle.length > 0 ? metaTitle : this.get('model.titleScratch'); if (metaTitle.length > 70) { metaTitle = metaTitle.substring(0, 70).trim(); - metaTitle = Ember.Handlebars.Utils.escapeExpression(metaTitle); - metaTitle = Ember.String.htmlSafe(metaTitle + '…'); + metaTitle = Handlebars.Utils.escapeExpression(metaTitle); + metaTitle = Ember.String.htmlSafe(`${metaTitle}…`); } return metaTitle; }), - seoDescription: Ember.computed('model.scratch', 'metaDescriptionScratch', function () { - var metaDescription = this.get('metaDescriptionScratch') || '', - el, - html = '', - placeholder; + seoDescription: computed('model.scratch', 'metaDescriptionScratch', function () { + let metaDescription = this.get('metaDescriptionScratch') || ''; + let html = ''; + let el, placeholder; if (metaDescription.length > 0) { placeholder = metaDescription; @@ -133,27 +131,25 @@ export default Ember.Controller.extend(SettingsMenuMixin, { } // Strip HTML - placeholder = $('
', {html: html}).text(); + placeholder = $('
', {html}).text(); // Replace new lines and trim - // jscs: disable placeholder = placeholder.replace(/\n+/g, ' ').trim(); - // jscs: enable } if (placeholder.length > 156) { // Limit to 156 characters placeholder = placeholder.substring(0, 156).trim(); - placeholder = Ember.Handlebars.Utils.escapeExpression(placeholder); - placeholder = Ember.String.htmlSafe(placeholder + '…'); + placeholder = Handlebars.Utils.escapeExpression(placeholder); + placeholder = Ember.String.htmlSafe(`${placeholder}…`); } return placeholder; }), - seoURL: Ember.computed('model.slug', 'config.blogUrl', function () { - var blogUrl = this.get('config.blogUrl'), - seoSlug = this.get('model.slug') ? this.get('model.slug') : '', - seoURL = blogUrl + '/' + seoSlug; + seoURL: computed('model.slug', 'config.blogUrl', function () { + let blogUrl = this.get('config.blogUrl'); + let seoSlug = this.get('model.slug') ? this.get('model.slug') : ''; + let seoURL = `${blogUrl}/${seoSlug}`; // only append a slash to the URL if the slug exists if (seoSlug) { @@ -162,7 +158,7 @@ export default Ember.Controller.extend(SettingsMenuMixin, { if (seoURL.length > 70) { seoURL = seoURL.substring(0, 70).trim(); - seoURL = Ember.String.htmlSafe(seoURL + '…'); + seoURL = Ember.String.htmlSafe(`${seoURL}…`); } return seoURL; @@ -170,61 +166,58 @@ export default Ember.Controller.extend(SettingsMenuMixin, { // observe titleScratch, keeping the post's slug in sync // with it until saved for the first time. - addTitleObserver: Ember.observer('model', function () { + addTitleObserver: observer('model', function () { if (this.get('model.isNew') || this.get('model.title') === '(Untitled)') { this.addObserver('model.titleScratch', this, 'titleObserver'); } }), - titleObserver: function () { - var debounceId, - title = this.get('model.title'); + titleObserver() { + let title = this.get('model.title'); + let debounceId; // generate a slug if a post is new and doesn't have a title yet or // if the title is still '(Untitled)' and the slug is unaltered. if ((this.get('model.isNew') && !title) || title === '(Untitled)') { - debounceId = Ember.run.debounce(this, 'generateAndSetSlug', 'model.slug', 700); + debounceId = run.debounce(this, 'generateAndSetSlug', 'model.slug', 700); } this.set('debounceId', debounceId); }, // live-query of all tags for tag input autocomplete - availableTags: Ember.computed(function () { - return this.get('store').filter('tag', {limit: 'all'}, function () { + availableTags: computed(function () { + return this.get('store').filter('tag', {limit: 'all'}, () => { return true; }); }), - showErrors: function (errors) { - errors = Ember.isArray(errors) ? errors : [errors]; + showErrors(errors) { + errors = isArray(errors) ? errors : [errors]; this.get('notifications').showErrors(errors); }, actions: { - discardEnter: function () { + discardEnter() { return false; }, - togglePage: function () { - var self = this; - + togglePage() { this.toggleProperty('model.page'); + // If this is a new post. Don't save the model. Defer the save // to the user pressing the save button if (this.get('model.isNew')) { return; } - this.get('model').save().catch(function (errors) { - self.showErrors(errors); - self.get('model').rollbackAttributes(); + this.get('model').save().catch((errors) => { + this.showErrors(errors); + this.get('model').rollbackAttributes(); }); }, - toggleFeatured: function () { - var self = this; - + toggleFeatured() { this.toggleProperty('model.featured'); // If this is a new post. Don't save the model. Defer the save @@ -233,21 +226,19 @@ export default Ember.Controller.extend(SettingsMenuMixin, { return; } - this.get('model').save(this.get('saveOptions')).catch(function (errors) { - self.showErrors(errors); - self.get('model').rollbackAttributes(); + this.get('model').save(this.get('saveOptions')).catch((errors) => { + this.showErrors(errors); + this.get('model').rollbackAttributes(); }); }, /** * triggered by user manually changing slug */ - updateSlug: function (newSlug) { - var slug = this.get('model.slug'), - self = this; + updateSlug(newSlug) { + let slug = this.get('model.slug'); newSlug = newSlug || slug; - newSlug = newSlug && newSlug.trim(); // Ignore unchanged slugs or candidate slugs that are empty @@ -258,7 +249,7 @@ export default Ember.Controller.extend(SettingsMenuMixin, { return; } - this.get('slugGenerator').generateSlug(newSlug).then(function (serverSlug) { + this.get('slugGenerator').generateSlug(newSlug).then((serverSlug) => { // If after getting the sanitized and unique slug back from the API // we end up with a slug that matches the existing slug, abort the change if (serverSlug === slug) { @@ -272,35 +263,35 @@ export default Ember.Controller.extend(SettingsMenuMixin, { // the trailing incrementor (e.g., this-is-a-slug and this-is-a-slug-2) // get the last token out of the slug candidate and see if it's a number - var slugTokens = serverSlug.split('-'), - check = Number(slugTokens.pop()); + let slugTokens = serverSlug.split('-'); + let check = Number(slugTokens.pop()); // if the candidate slug is the same as the existing slug except // for the incrementor then the existing slug should be used if (isNumber(check) && check > 0) { if (slug === slugTokens.join('-') && serverSlug !== newSlug) { - self.set('slugValue', slug); + this.set('slugValue', slug); return; } } - self.set('model.slug', serverSlug); + this.set('model.slug', serverSlug); - if (self.hasObserverFor('model.titleScratch')) { - self.removeObserver('model.titleScratch', self, 'titleObserver'); + if (this.hasObserverFor('model.titleScratch')) { + this.removeObserver('model.titleScratch', this, 'titleObserver'); } // If this is a new post. Don't save the model. Defer the save // to the user pressing the save button - if (self.get('model.isNew')) { + if (this.get('model.isNew')) { return; } - return self.get('model').save(); - }).catch(function (errors) { - self.showErrors(errors); - self.get('model').rollbackAttributes(); + return this.get('model').save(); + }).catch((errors) => { + this.showErrors(errors); + this.get('model').rollbackAttributes(); }); }, @@ -309,11 +300,10 @@ export default Ember.Controller.extend(SettingsMenuMixin, { * Action sent by post settings menu view. * (#1351) */ - setPublishedAt: function (userInput) { - var errMessage = '', - newPublishedAt = parseDateString(userInput), - publishedAt = this.get('model.published_at'), - self = this; + setPublishedAt(userInput) { + let newPublishedAt = parseDateString(userInput); + let publishedAt = this.get('model.published_at'); + let errMessage = ''; if (!userInput) { // Clear out the published_at field for a draft @@ -354,16 +344,16 @@ export default Ember.Controller.extend(SettingsMenuMixin, { return; } - this.get('model').save().catch(function (errors) { - self.showErrors(errors); - self.get('model').rollbackAttributes(); + this.get('model').save().catch((errors) => { + this.showErrors(errors); + this.get('model').rollbackAttributes(); }); }, - setMetaTitle: function (metaTitle) { - var property = 'meta_title', - model = this.get('model'), - currentTitle = model.get(property) || ''; + setMetaTitle(metaTitle) { + let property = 'meta_title'; + let model = this.get('model'); + let currentTitle = model.get(property) || ''; // Only update if the title has changed if (currentTitle === metaTitle) { @@ -381,10 +371,10 @@ export default Ember.Controller.extend(SettingsMenuMixin, { model.save(); }, - setMetaDescription: function (metaDescription) { - var property = 'meta_description', - model = this.get('model'), - currentDescription = model.get(property) || ''; + setMetaDescription(metaDescription) { + let property = 'meta_description'; + let model = this.get('model'); + let currentDescription = model.get(property) || ''; // Only update if the description has changed if (currentDescription === metaDescription) { @@ -402,56 +392,51 @@ export default Ember.Controller.extend(SettingsMenuMixin, { model.save(); }, - setCoverImage: function (image) { - var self = this; - + setCoverImage(image) { this.set('model.image', image); if (this.get('model.isNew')) { return; } - this.get('model').save().catch(function (errors) { - self.showErrors(errors); - self.get('model').rollbackAttributes(); + this.get('model').save().catch((errors) => { + this.showErrors(errors); + this.get('model').rollbackAttributes(); }); }, - clearCoverImage: function () { - var self = this; - + clearCoverImage() { this.set('model.image', ''); if (this.get('model.isNew')) { return; } - this.get('model').save().catch(function (errors) { - self.showErrors(errors); - self.get('model').rollbackAttributes(); + this.get('model').save().catch((errors) => { + this.showErrors(errors); + this.get('model').rollbackAttributes(); }); }, - resetUploader: function () { - var uploader = this.get('uploaderReference'); + resetUploader() { + let uploader = this.get('uploaderReference'); if (uploader && uploader[0]) { uploader[0].uploaderUi.reset(); } }, - resetPubDate: function () { + resetPubDate() { this.set('publishedAtValue', ''); }, - closeNavMenu: function () { + closeNavMenu() { this.get('application').send('closeNavMenu'); }, - changeAuthor: function (newAuthor) { - var author = this.get('model.author'), - model = this.get('model'), - self = this; + changeAuthor(newAuthor) { + let author = this.get('model.author'); + let model = this.get('model'); // return if nothing changed if (newAuthor.get('id') === author.get('id')) { @@ -465,19 +450,20 @@ export default Ember.Controller.extend(SettingsMenuMixin, { return; } - model.save().catch(function (errors) { - self.showErrors(errors); - self.set('selectedAuthor', author); + model.save().catch((errors) => { + this.showErrors(errors); + this.set('selectedAuthor', author); model.rollbackAttributes(); }); }, - addTag: function (tagName, index) { - var self = this, - currentTags = this.get('model.tags'), - currentTagNames = currentTags.map(function (tag) { return tag.get('name').toLowerCase(); }), - availableTagNames = null, - tagToAdd = null; + addTag(tagName, index) { + let currentTags = this.get('model.tags'); + let currentTagNames = currentTags.map((tag) => { + return tag.get('name').toLowerCase(); + }); + let availableTagNames, + tagToAdd; tagName = tagName.trim(); @@ -486,30 +472,34 @@ export default Ember.Controller.extend(SettingsMenuMixin, { return; } - this.get('availableTags').then(function (availableTags) { - availableTagNames = availableTags.map(function (tag) { return tag.get('name').toLowerCase(); }); + this.get('availableTags').then((availableTags) => { + availableTagNames = availableTags.map((tag) => { + return tag.get('name').toLowerCase(); + }); // find existing tag or create new if (availableTagNames.contains(tagName.toLowerCase())) { - tagToAdd = availableTags.find(function (tag) { + tagToAdd = availableTags.find((tag) => { return tag.get('name').toLowerCase() === tagName.toLowerCase(); }); } else { - tagToAdd = self.get('store').createRecord('tag', { + tagToAdd = this.get('store').createRecord('tag', { name: tagName }); // we need to set a UUID so that selectize has a unique value // it will be ignored when sent to the server - tagToAdd.set('uuid', Ember.guidFor(tagToAdd)); + tagToAdd.set('uuid', guidFor(tagToAdd)); } // push tag onto post relationship - if (tagToAdd) { self.get('model.tags').insertAt(index, tagToAdd); } + if (tagToAdd) { + this.get('model.tags').insertAt(index, tagToAdd); + } }); }, - removeTag: function (tag) { + removeTag(tag) { this.get('model.tags').removeObject(tag); if (tag.get('isNew')) { diff --git a/core/client/app/controllers/posts.js b/core/client/app/controllers/posts.js index c6276768b7..852eeb5e69 100644 --- a/core/client/app/controllers/posts.js +++ b/core/client/app/controllers/posts.js @@ -1,17 +1,20 @@ import Ember from 'ember'; +const {Controller, compare, computed} = Ember; +const {equal} = computed; + // a custom sort function is needed in order to sort the posts list the same way the server would: // status: ASC // published_at: DESC // updated_at: DESC // id: DESC function comparator(item1, item2) { - var updated1 = item1.get('updated_at'), - updated2 = item2.get('updated_at'), - idResult, + let updated1 = item1.get('updated_at'); + let updated2 = item2.get('updated_at'); + let idResult, + publishedAtResult, statusResult, - updatedAtResult, - publishedAtResult; + updatedAtResult; // when `updated_at` is undefined, the model is still // being written to with the results from the server @@ -23,9 +26,9 @@ function comparator(item1, item2) { return 1; } - idResult = Ember.compare(parseInt(item1.get('id')), parseInt(item2.get('id'))); - statusResult = Ember.compare(item1.get('status'), item2.get('status')); - updatedAtResult = Ember.compare(updated1.valueOf(), updated2.valueOf()); + idResult = compare(parseInt(item1.get('id')), parseInt(item2.get('id'))); + statusResult = compare(item1.get('status'), item2.get('status')); + updatedAtResult = compare(updated1.valueOf(), updated2.valueOf()); publishedAtResult = publishedAtCompare(item1, item2); if (statusResult === 0) { @@ -45,8 +48,8 @@ function comparator(item1, item2) { } function publishedAtCompare(item1, item2) { - var published1 = item1.get('published_at'), - published2 = item2.get('published_at'); + let published1 = item1.get('published_at'); + let published2 = item2.get('published_at'); if (!published1 && !published2) { return 0; @@ -60,23 +63,23 @@ function publishedAtCompare(item1, item2) { return 1; } - return Ember.compare(published1.valueOf(), published2.valueOf()); + return compare(published1.valueOf(), published2.valueOf()); } -export default Ember.Controller.extend({ +export default Controller.extend({ // See PostsRoute's shortcuts - postListFocused: Ember.computed.equal('keyboardFocus', 'postList'), - postContentFocused: Ember.computed.equal('keyboardFocus', 'postContent'), + postListFocused: equal('keyboardFocus', 'postList'), + postContentFocused: equal('keyboardFocus', 'postContent'), - sortedPosts: Ember.computed('model.@each.status', 'model.@each.published_at', 'model.@each.isNew', 'model.@each.updated_at', function () { - var postsArray = this.get('model').toArray(); + sortedPosts: computed('model.@each.status', 'model.@each.published_at', 'model.@each.isNew', 'model.@each.updated_at', function () { + let postsArray = this.get('model').toArray(); return postsArray.sort(comparator); }), actions: { - showPostContent: function (post) { + showPostContent(post) { if (!post) { return; } diff --git a/core/client/app/controllers/reset.js b/core/client/app/controllers/reset.js index bb6f3fb9e6..5f997e780c 100644 --- a/core/client/app/controllers/reset.js +++ b/core/client/app/controllers/reset.js @@ -2,7 +2,9 @@ import Ember from 'ember'; import {request as ajax} from 'ic-ajax'; import ValidationEngine from 'ghost/mixins/validation-engine'; -export default Ember.Controller.extend(ValidationEngine, { +const {Controller, computed, inject} = Ember; + +export default Controller.extend(ValidationEngine, { newPassword: '', ne2Password: '', token: '', @@ -11,18 +13,18 @@ export default Ember.Controller.extend(ValidationEngine, { validationType: 'reset', - ghostPaths: Ember.inject.service('ghost-paths'), - notifications: Ember.inject.service(), - session: Ember.inject.service(), + ghostPaths: inject.service('ghost-paths'), + notifications: inject.service(), + session: inject.service(), - email: Ember.computed('token', function () { + email: computed('token', function () { // The token base64 encodes the email (and some other stuff), // each section is divided by a '|'. Email comes second. return atob(this.get('token')).split('|')[1]; }), // Used to clear sensitive information - clearData: function () { + clearData() { this.setProperties({ newPassword: '', ne2Password: '', @@ -31,34 +33,34 @@ export default Ember.Controller.extend(ValidationEngine, { }, actions: { - submit: function () { - var credentials = this.getProperties('newPassword', 'ne2Password', 'token'), - self = this; + submit() { + let credentials = this.getProperties('newPassword', 'ne2Password', 'token'); + this.set('flowErrors', ''); this.get('hasValidated').addObjects((['newPassword', 'ne2Password'])); - this.validate().then(function () { - self.toggleProperty('submitting'); + this.validate().then(() => { + this.toggleProperty('submitting'); ajax({ - url: self.get('ghostPaths.url').api('authentication', 'passwordreset'), + url: this.get('ghostPaths.url').api('authentication', 'passwordreset'), type: 'PUT', data: { passwordreset: [credentials] } - }).then(function (resp) { - self.toggleProperty('submitting'); - self.get('notifications').showAlert(resp.passwordreset[0].message, {type: 'warn', delayed: true, key: 'password.reset'}); - self.get('session').authenticate('authenticator:oauth2', self.get('email'), credentials.newPassword); - }).catch(function (response) { - self.get('notifications').showAPIError(response, {key: 'password.reset'}); - self.toggleProperty('submitting'); + }).then((resp) => { + this.toggleProperty('submitting'); + this.get('notifications').showAlert(resp.passwordreset[0].message, {type: 'warn', delayed: true, key: 'password.reset'}); + this.get('session').authenticate('authenticator:oauth2', this.get('email'), credentials.newPassword); + }).catch((response) => { + this.get('notifications').showAPIError(response, {key: 'password.reset'}); + this.toggleProperty('submitting'); }); - }).catch(function () { - if (self.get('errors.newPassword')) { - self.set('flowErrors', self.get('errors.newPassword')[0].message); + }).catch(() => { + if (this.get('errors.newPassword')) { + this.set('flowErrors', this.get('errors.newPassword')[0].message); } - if (self.get('errors.ne2Password')) { - self.set('flowErrors', self.get('errors.ne2Password')[0].message); + if (this.get('errors.ne2Password')) { + this.set('flowErrors', this.get('errors.ne2Password')[0].message); } }); } diff --git a/core/client/app/controllers/settings/code-injection.js b/core/client/app/controllers/settings/code-injection.js index beebb21531..6b72ea6e22 100644 --- a/core/client/app/controllers/settings/code-injection.js +++ b/core/client/app/controllers/settings/code-injection.js @@ -1,13 +1,15 @@ import Ember from 'ember'; import SettingsSaveMixin from 'ghost/mixins/settings-save'; -export default Ember.Controller.extend(SettingsSaveMixin, { - notifications: Ember.inject.service(), +const {Controller, inject} = Ember; - save: function () { - var notifications = this.get('notifications'); +export default Controller.extend(SettingsSaveMixin, { + notifications: inject.service(), - return this.get('model').save().catch(function (error) { + save() { + let notifications = this.get('notifications'); + + return this.get('model').save().catch((error) => { notifications.showAPIError(error, {key: 'code-injection.save'}); }); } diff --git a/core/client/app/controllers/settings/general.js b/core/client/app/controllers/settings/general.js index 37b0e3c9b4..304c76bdc9 100644 --- a/core/client/app/controllers/settings/general.js +++ b/core/client/app/controllers/settings/general.js @@ -2,16 +2,18 @@ import Ember from 'ember'; import SettingsSaveMixin from 'ghost/mixins/settings-save'; import randomPassword from 'ghost/utils/random-password'; -export default Ember.Controller.extend(SettingsSaveMixin, { - notifications: Ember.inject.service(), - config: Ember.inject.service(), +const {Controller, computed, inject, observer} = Ember; - selectedTheme: Ember.computed('model.activeTheme', 'themes', function () { - var activeTheme = this.get('model.activeTheme'), - themes = this.get('themes'), - selectedTheme; +export default Controller.extend(SettingsSaveMixin, { + notifications: inject.service(), + config: inject.service(), - themes.forEach(function (theme) { + selectedTheme: computed('model.activeTheme', 'themes', function () { + let activeTheme = this.get('model.activeTheme'); + let themes = this.get('themes'); + let selectedTheme; + + themes.forEach((theme) => { if (theme.name === activeTheme) { selectedTheme = theme; } @@ -20,34 +22,35 @@ export default Ember.Controller.extend(SettingsSaveMixin, { return selectedTheme; }), - logoImageSource: Ember.computed('model.logo', function () { + logoImageSource: computed('model.logo', function () { return this.get('model.logo') || ''; }), - coverImageSource: Ember.computed('model.cover', function () { + coverImageSource: computed('model.cover', function () { return this.get('model.cover') || ''; }), - isDatedPermalinks: Ember.computed('model.permalinks', { - set: function (key, value) { + isDatedPermalinks: computed('model.permalinks', { + set(key, value) { this.set('model.permalinks', value ? '/:year/:month/:day/:slug/' : '/:slug/'); - var slugForm = this.get('model.permalinks'); + let slugForm = this.get('model.permalinks'); return slugForm !== '/:slug/'; }, - get: function () { - var slugForm = this.get('model.permalinks'); + + get() { + let slugForm = this.get('model.permalinks'); return slugForm !== '/:slug/'; } }), - themes: Ember.computed(function () { + themes: computed(function () { return this.get('model.availableThemes').reduce(function (themes, t) { - var theme = {}; + let theme = {}; theme.name = t.name; - theme.label = t.package ? t.package.name + ' - ' + t.package.version : t.name; + theme.label = t.package ? `${t.package.name} - ${t.package.version}` : t.name; theme.package = t.package; theme.active = !!t.active; @@ -57,22 +60,22 @@ export default Ember.Controller.extend(SettingsSaveMixin, { }, []); }).readOnly(), - generatePassword: Ember.observer('model.isPrivate', function () { + generatePassword: observer('model.isPrivate', function () { this.get('model.errors').remove('password'); if (this.get('model.isPrivate') && this.get('model.hasDirtyAttributes')) { this.get('model').set('password', randomPassword()); } }), - save: function () { - var notifications = this.get('notifications'), - config = this.get('config'); + save() { + let notifications = this.get('notifications'); + let config = this.get('config'); - return this.get('model').save().then(function (model) { + return this.get('model').save().then((model) => { config.set('blogTitle', model.get('title')); return model; - }).catch(function (error) { + }).catch((error) => { if (error) { notifications.showAPIError(error, {key: 'settings.save'}); } @@ -80,19 +83,19 @@ export default Ember.Controller.extend(SettingsSaveMixin, { }, actions: { - validate: function (property) { - this.get('model').validate({property: property}); + validate(property) { + this.get('model').validate({property}); }, - checkPostsPerPage: function () { - var postsPerPage = this.get('model.postsPerPage'); + checkPostsPerPage() { + let postsPerPage = this.get('model.postsPerPage'); if (postsPerPage < 1 || postsPerPage > 1000 || isNaN(postsPerPage)) { this.set('model.postsPerPage', 5); } }, - setTheme: function (theme) { + setTheme(theme) { this.set('model.activeTheme', theme.name); } } diff --git a/core/client/app/controllers/settings/labs.js b/core/client/app/controllers/settings/labs.js index 2a70142721..a4ae97c2b9 100644 --- a/core/client/app/controllers/settings/labs.js +++ b/core/client/app/controllers/settings/labs.js @@ -1,51 +1,51 @@ import Ember from 'ember'; import {request as ajax} from 'ic-ajax'; -export default Ember.Controller.extend({ +const {Controller, computed, inject} = Ember; + +export default Controller.extend({ uploadButtonText: 'Import', importErrors: '', submitting: false, - ghostPaths: Ember.inject.service('ghost-paths'), - notifications: Ember.inject.service(), - session: Ember.inject.service(), - feature: Ember.inject.controller(), + ghostPaths: inject.service('ghost-paths'), + notifications: inject.service(), + session: inject.service(), + feature: inject.controller(), - labsJSON: Ember.computed('model.labs', function () { + labsJSON: computed('model.labs', function () { return JSON.parse(this.get('model.labs') || {}); }), - saveLabs: function (optionName, optionValue) { - var self = this, - labsJSON = this.get('labsJSON'); + saveLabs(optionName, optionValue) { + let labsJSON = this.get('labsJSON'); // Set new value in the JSON object labsJSON[optionName] = optionValue; this.set('model.labs', JSON.stringify(labsJSON)); - this.get('model').save().catch(function (errors) { - self.showErrors(errors); - self.get('model').rollbackAttributes(); + this.get('model').save().catch((errors) => { + this.showErrors(errors); + this.get('model').rollbackAttributes(); }); }, - usePublicAPI: Ember.computed('feature.publicAPI', { - get: function () { + usePublicAPI: computed('feature.publicAPI', { + get() { return this.get('feature.publicAPI'); }, - set: function (key, value) { + set(key, value) { this.saveLabs('publicAPI', value); return value; } }), actions: { - onUpload: function (file) { - var self = this, - formData = new FormData(), - notifications = this.get('notifications'), - currentUserId = this.get('session.user.id'); + onUpload(file) { + let formData = new FormData(); + let notifications = this.get('notifications'); + let currentUserId = this.get('session.user.id'); this.set('uploadButtonText', 'Importing'); this.set('importErrors', ''); @@ -59,29 +59,30 @@ export default Ember.Controller.extend({ cache: false, contentType: false, processData: false - }).then(function () { + }).then(() => { // Clear the store, so that all the new data gets fetched correctly. - self.store.unloadAll(); + this.store.unloadAll(); // Reload currentUser and set session - self.set('session.user', self.store.findRecord('user', currentUserId)); + this.set('session.user', this.store.findRecord('user', currentUserId)); // TODO: keep as notification, add link to view content notifications.showNotification('Import successful.'); notifications.closeAlerts('import.upload'); - }).catch(function (response) { + }).catch((response) => { if (response && response.jqXHR && response.jqXHR.responseJSON && response.jqXHR.responseJSON.errors) { - self.set('importErrors', response.jqXHR.responseJSON.errors); + this.set('importErrors', response.jqXHR.responseJSON.errors); } notifications.showAlert('Import Failed', {type: 'error', key: 'import.upload.failed'}); - }).finally(function () { - self.set('uploadButtonText', 'Import'); + }).finally(() => { + this.set('uploadButtonText', 'Import'); }); }, - exportData: function () { - var iframe = $('#iframeDownload'), - downloadURL = this.get('ghostPaths.url').api('db') + - '?access_token=' + this.get('session.data.authenticated.access_token'); + exportData() { + let dbUrl = this.get('ghostPaths.url').api('db'); + let accessToken = this.get('session.data.authenticated.access_token'); + let downloadURL = `${dbUrl}?access_token=${accessToken}`; + let iframe = $('#iframeDownload'); if (iframe.length === 0) { iframe = $('