From 1b85d67e0e52cade30a3c9d5592308626b1e838b Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Wed, 18 May 2016 10:29:59 +0100 Subject: [PATCH 1/2] Remove split Ghost-Admin code --- core/client/.ember-cli | 9 - core/client/.gitignore | 20 - core/client/.jscsrc | 14 - core/client/.jshintrc | 36 - core/client/.watchmanconfig | 3 - core/client/app/README.md | 30 - core/client/app/_config.yml | 15 - core/client/app/adapters/application.js | 9 - core/client/app/adapters/base.js | 54 -- .../app/adapters/embedded-relation-adapter.js | 132 ---- core/client/app/adapters/setting.js | 19 - core/client/app/adapters/tag.js | 4 - core/client/app/adapters/user.js | 23 - core/client/app/app.js | 20 - core/client/app/authenticators/oauth2.js | 28 - core/client/app/authorizers/oauth2.js | 3 - .../app/components/gh-activating-list-item.js | 22 - core/client/app/components/gh-alert.js | 40 - core/client/app/components/gh-alerts.js | 22 - core/client/app/components/gh-app.js | 15 - core/client/app/components/gh-blog-url.js | 12 - core/client/app/components/gh-cm-editor.js | 47 -- .../client/app/components/gh-content-cover.js | 30 - .../components/gh-content-preview-content.js | 22 - .../components/gh-content-view-container.js | 15 - .../app/components/gh-datetime-input.js | 33 - .../app/components/gh-dropdown-button.js | 24 - core/client/app/components/gh-dropdown.js | 99 --- core/client/app/components/gh-ed-editor.js | 52 -- core/client/app/components/gh-ed-preview.js | 110 --- .../app/components/gh-editor-save-button.js | 50 -- core/client/app/components/gh-editor.js | 123 --- .../client/app/components/gh-error-message.js | 36 - core/client/app/components/gh-feature-flag.js | 45 -- core/client/app/components/gh-file-upload.js | 37 - .../client/app/components/gh-file-uploader.js | 161 ---- core/client/app/components/gh-form-group.js | 5 - .../app/components/gh-fullscreen-modal.js | 85 --- .../gh-image-uploader-with-preview.js | 39 - .../app/components/gh-image-uploader.js | 226 ------ .../app/components/gh-infinite-scroll.js | 12 - core/client/app/components/gh-input.js | 8 - core/client/app/components/gh-light-table.js | 27 - core/client/app/components/gh-main.js | 13 - core/client/app/components/gh-menu-toggle.js | 42 -- core/client/app/components/gh-nav-menu.js | 48 -- core/client/app/components/gh-navigation.js | 39 - .../app/components/gh-navitem-url-input.js | 145 ---- core/client/app/components/gh-navitem.js | 55 -- core/client/app/components/gh-notification.js | 56 -- .../client/app/components/gh-notifications.js | 17 - .../app/components/gh-popover-button.js | 26 - core/client/app/components/gh-popover.js | 11 - .../app/components/gh-posts-list-item.js | 85 --- .../client/app/components/gh-profile-image.js | 142 ---- core/client/app/components/gh-search-input.js | 206 ----- .../app/components/gh-search-input/trigger.js | 43 -- .../client/app/components/gh-select-native.js | 42 -- core/client/app/components/gh-selectize.js | 124 --- core/client/app/components/gh-skip-link.js | 36 - core/client/app/components/gh-spin-button.js | 59 -- .../app/components/gh-subscribers-table.js | 17 - core/client/app/components/gh-tab-pane.js | 34 - core/client/app/components/gh-tab.js | 35 - core/client/app/components/gh-tabs-manager.js | 85 --- .../app/components/gh-tag-settings-form.js | 136 ---- core/client/app/components/gh-tag.js | 12 - .../gh-tags-management-container.js | 54 -- core/client/app/components/gh-textarea.js | 8 - .../app/components/gh-trim-focus-input.js | 41 - core/client/app/components/gh-url-preview.js | 35 - core/client/app/components/gh-user-active.js | 31 - core/client/app/components/gh-user-invited.js | 69 -- .../gh-validation-status-container.js | 26 - core/client/app/components/gh-view-title.js | 14 - core/client/app/components/modals/base.js | 56 -- .../client/app/components/modals/copy-html.js | 9 - .../app/components/modals/delete-all.js | 49 -- .../app/components/modals/delete-post.js | 55 -- .../components/modals/delete-subscriber.js | 23 - .../app/components/modals/delete-tag.js | 27 - .../app/components/modals/delete-user.js | 19 - .../components/modals/import-subscribers.js | 43 -- .../app/components/modals/invite-new-user.js | 125 --- .../app/components/modals/leave-editor.js | 12 - .../app/components/modals/markdown-help.js | 4 - .../app/components/modals/new-subscriber.js | 32 - .../app/components/modals/re-authenticate.js | 68 -- .../app/components/modals/transfer-owner.js | 17 - .../app/components/modals/upload-image.js | 101 --- core/client/app/controllers/about.js | 21 - core/client/app/controllers/application.js | 57 -- core/client/app/controllers/editor/edit.js | 14 - core/client/app/controllers/editor/new.js | 25 - core/client/app/controllers/error.js | 20 - .../app/controllers/post-settings-menu.js | 491 ------------ core/client/app/controllers/posts.js | 88 --- core/client/app/controllers/reset.js | 75 -- .../app/controllers/settings/apps/index.js | 14 - .../app/controllers/settings/apps/slack.js | 75 -- .../controllers/settings/code-injection.js | 19 - .../app/controllers/settings/general.js | 253 ------- core/client/app/controllers/settings/labs.js | 89 --- .../app/controllers/settings/navigation.js | 101 --- core/client/app/controllers/settings/tags.js | 43 -- .../app/controllers/settings/tags/tag.js | 81 -- core/client/app/controllers/setup.js | 23 - core/client/app/controllers/setup/three.js | 236 ------ core/client/app/controllers/setup/two.js | 152 ---- core/client/app/controllers/signin.js | 114 --- core/client/app/controllers/signup.js | 96 --- core/client/app/controllers/subscribers.js | 162 ---- core/client/app/controllers/team/index.js | 33 - core/client/app/controllers/team/user.js | 425 ----------- .../client/app/helpers/gh-count-characters.js | 25 - .../app/helpers/gh-count-down-characters.js | 28 - core/client/app/helpers/gh-count-words.js | 20 - core/client/app/helpers/gh-format-html.js | 26 - core/client/app/helpers/gh-format-markdown.js | 34 - core/client/app/helpers/gh-format-timeago.js | 16 - core/client/app/helpers/gh-path.js | 55 -- core/client/app/helpers/gh-user-can-admin.js | 16 - core/client/app/helpers/highlighted-text.js | 7 - core/client/app/helpers/is-equal.js | 13 - core/client/app/helpers/is-not.js | 11 - core/client/app/html/apps.html | 305 -------- core/client/app/html/permalinks.html | 250 ------ core/client/app/html/themes.html | 273 ------- core/client/app/index.html | 56 -- .../app/initializers/ember-simple-auth.js | 17 - .../app/initializers/trailing-history.js | 23 - .../jquery-ajax-oauth-prefilter.js | 21 - core/client/app/mirage/config.js | 436 ----------- .../app/mirage/factories/notification.js | 9 - core/client/app/mirage/factories/post.js | 23 - core/client/app/mirage/factories/role.js | 12 - core/client/app/mirage/factories/setting.js | 13 - .../client/app/mirage/factories/subscriber.js | 21 - core/client/app/mirage/factories/tag.js | 23 - core/client/app/mirage/factories/user.js | 27 - core/client/app/mirage/fixtures/roles.js | 43 -- core/client/app/mirage/fixtures/settings.js | 218 ------ core/client/app/mirage/scenarios/default.js | 8 - core/client/app/mixins/404-handler.js | 23 - core/client/app/mixins/active-link-wrapper.js | 32 - core/client/app/mixins/body-event-listener.js | 49 -- .../app/mixins/current-user-settings.js | 25 - core/client/app/mixins/dropdown-mixin.js | 17 - core/client/app/mixins/ed-editor-api.js | 142 ---- core/client/app/mixins/ed-editor-scroll.js | 100 --- core/client/app/mixins/ed-editor-shortcuts.js | 173 ----- .../app/mixins/editor-base-controller.js | 424 ----------- core/client/app/mixins/editor-base-route.js | 144 ---- core/client/app/mixins/infinite-scroll.js | 43 -- core/client/app/mixins/pagination.js | 118 --- .../app/mixins/settings-menu-controller.js | 34 - core/client/app/mixins/settings-save.js | 17 - core/client/app/mixins/shortcuts-route.js | 49 -- core/client/app/mixins/shortcuts.js | 79 -- core/client/app/mixins/slug-url.js | 16 - core/client/app/mixins/style-body.js | 32 - core/client/app/mixins/text-input.js | 25 - core/client/app/mixins/validation-engine.js | 162 ---- core/client/app/mixins/validation-state.js | 34 - core/client/app/models/navigation-item.js | 27 - core/client/app/models/notification.js | 9 - core/client/app/models/post.js | 87 --- core/client/app/models/role.js | 20 - core/client/app/models/setting.js | 28 - core/client/app/models/slack-integration.js | 19 - core/client/app/models/subscriber.js | 23 - core/client/app/models/tag.js | 23 - core/client/app/models/user.js | 121 --- core/client/app/resolver.js | 3 - core/client/app/router.js | 70 -- core/client/app/routes/about.js | 36 - core/client/app/routes/application.js | 132 ---- core/client/app/routes/authenticated.js | 6 - core/client/app/routes/editor/edit.js | 65 -- core/client/app/routes/editor/index.js | 10 - core/client/app/routes/editor/new.js | 50 -- core/client/app/routes/error404.js | 15 - core/client/app/routes/mobile-index-route.js | 37 - core/client/app/routes/posts.js | 100 --- core/client/app/routes/posts/index.js | 57 -- core/client/app/routes/posts/post.js | 77 -- core/client/app/routes/reset.js | 33 - core/client/app/routes/settings/apps.js | 20 - core/client/app/routes/settings/apps/slack.js | 19 - .../app/routes/settings/code-injection.js | 27 - core/client/app/routes/settings/general.js | 26 - core/client/app/routes/settings/labs.js | 22 - core/client/app/routes/settings/navigation.js | 46 -- core/client/app/routes/settings/tags.js | 104 --- core/client/app/routes/settings/tags/index.js | 20 - core/client/app/routes/settings/tags/new.js | 21 - core/client/app/routes/settings/tags/tag.js | 20 - core/client/app/routes/setup.js | 59 -- core/client/app/routes/setup/index.js | 10 - core/client/app/routes/setup/one.js | 64 -- core/client/app/routes/setup/three.js | 12 - core/client/app/routes/signin.js | 45 -- core/client/app/routes/signout.js | 25 - core/client/app/routes/signup.js | 77 -- core/client/app/routes/subscribers.js | 54 -- core/client/app/routes/subscribers/import.js | 9 - core/client/app/routes/subscribers/new.js | 37 - core/client/app/routes/team/index.js | 32 - core/client/app/routes/team/user.js | 58 -- core/client/app/serializers/application.js | 27 - core/client/app/serializers/post.js | 56 -- core/client/app/serializers/setting.js | 46 -- core/client/app/serializers/tag.js | 20 - core/client/app/serializers/user.js | 29 - core/client/app/services/ajax.js | 72 -- core/client/app/services/config.js | 44 -- core/client/app/services/dropdown.js | 20 - core/client/app/services/feature.js | 84 --- core/client/app/services/ghost-paths.js | 8 - core/client/app/services/media-queries.js | 48 -- core/client/app/services/notifications.js | 187 ----- core/client/app/services/session.js | 24 - core/client/app/services/slug-generator.js | 29 - core/client/app/session-stores/application.js | 10 - core/client/app/styles/app.css | 48 -- core/client/app/styles/components/badges.css | 74 -- .../app/styles/components/dropdowns.css | 149 ---- core/client/app/styles/components/modals.css | 183 ----- .../app/styles/components/notifications.css | 219 ------ .../app/styles/components/pagination.css | 111 --- .../client/app/styles/components/popovers.css | 59 -- .../app/styles/components/power-select.css | 123 --- .../app/styles/components/selectize.css | 405 ---------- .../app/styles/components/settings-menu.css | 180 ----- .../app/styles/components/splitbuttons.css | 62 -- .../client/app/styles/components/uploader.css | 162 ---- core/client/app/styles/csscomb.json | 235 ------ core/client/app/styles/layouts/about.css | 123 --- core/client/app/styles/layouts/apps.css | 252 ------- core/client/app/styles/layouts/auth.css | 56 -- core/client/app/styles/layouts/content.css | 289 ------- core/client/app/styles/layouts/editor.css | 403 ---------- core/client/app/styles/layouts/error.css | 88 --- core/client/app/styles/layouts/flow.css | 464 ------------ core/client/app/styles/layouts/main.css | 560 -------------- core/client/app/styles/layouts/packages.css | 376 --------- core/client/app/styles/layouts/settings.css | 163 ---- .../client/app/styles/layouts/subscribers.css | 69 -- core/client/app/styles/layouts/tags.css | 129 ---- core/client/app/styles/layouts/user.css | 217 ------ core/client/app/styles/layouts/users.css | 202 ----- core/client/app/styles/patterns/_shame.css | 7 - core/client/app/styles/patterns/buttons.css | 237 ------ core/client/app/styles/patterns/forms.css | 343 --------- core/client/app/styles/patterns/global.css | 441 ----------- core/client/app/styles/patterns/icons.css | 266 ------- core/client/app/styles/patterns/labels.css | 119 --- core/client/app/styles/patterns/navlist.css | 71 -- core/client/app/styles/patterns/tables.css | 79 -- core/client/app/templates/-import-errors.hbs | 7 - core/client/app/templates/-user-list-item.hbs | 18 - core/client/app/templates/about.hbs | 44 -- core/client/app/templates/application.hbs | 28 - .../components/gh-activating-list-item.hbs | 1 - .../app/templates/components/gh-alert.hbs | 4 - .../app/templates/components/gh-alerts.hbs | 3 - .../app/templates/components/gh-app.hbs | 1 - .../app/templates/components/gh-blog-url.hbs | 1 - .../components/gh-content-preview-content.hbs | 1 - .../components/gh-content-view-container.hbs | 1 - .../components/gh-datetime-input.hbs | 5 - .../templates/components/gh-ed-preview.hbs | 14 - .../components/gh-editor-save-button.hbs | 22 - .../app/templates/components/gh-editor.hbs | 50 -- .../templates/components/gh-error-message.hbs | 1 - .../templates/components/gh-feature-flag.hbs | 3 - .../templates/components/gh-file-upload.hbs | 4 - .../templates/components/gh-file-uploader.hbs | 20 - .../components/gh-fullscreen-modal.hbs | 11 - .../gh-image-uploader-with-preview.hbs | 16 - .../components/gh-image-uploader.hbs | 43 -- .../components/gh-infinite-scroll.hbs | 1 - .../templates/components/gh-menu-toggle.hbs | 1 - .../templates/components/gh-modal-dialog.hbs | 18 - .../app/templates/components/gh-nav-menu.hbs | 69 -- .../templates/components/gh-navigation.hbs | 1 - .../app/templates/components/gh-navitem.hbs | 26 - .../templates/components/gh-notification.hbs | 4 - .../templates/components/gh-notifications.hbs | 3 - .../components/gh-posts-list-item.hbs | 1 - .../templates/components/gh-profile-image.hbs | 17 - .../templates/components/gh-search-input.hbs | 13 - .../components/gh-search-input/trigger.hbs | 12 - .../templates/components/gh-select-native.hbs | 14 - .../templates/components/gh-spin-button.hbs | 9 - .../gh-subscribers-table-delete-cell.hbs | 1 - .../components/gh-subscribers-table.hbs | 17 - .../components/gh-tag-settings-form.hbs | 88 --- .../app/templates/components/gh-tag.hbs | 8 - .../gh-tags-management-container.hbs | 1 - .../templates/components/gh-url-preview.hbs | 1 - .../templates/components/gh-user-active.hbs | 1 - .../templates/components/gh-user-invited.hbs | 1 - .../templates/components/gh-view-title.hbs | 2 - .../templates/components/modals/copy-html.hbs | 8 - .../components/modals/delete-all.hbs | 13 - .../components/modals/delete-post.hbs | 17 - .../components/modals/delete-subscriber.hbs | 13 - .../components/modals/delete-tag.hbs | 17 - .../components/modals/delete-user.hbs | 17 - .../components/modals/import-subscribers.hbs | 47 -- .../components/modals/invite-new-user.hbs | 41 - .../components/modals/leave-editor.hbs | 18 - .../components/modals/markdown-help.hbs | 81 -- .../components/modals/new-subscriber.hbs | 29 - .../components/modals/re-authenticate.hbs | 16 - .../components/modals/transfer-owner.hbs | 16 - .../components/modals/upload-image.hbs | 20 - core/client/app/templates/editor/edit.hbs | 48 -- core/client/app/templates/error.hbs | 27 - .../app/templates/post-settings-menu.hbs | 144 ---- core/client/app/templates/posts.hbs | 53 -- core/client/app/templates/posts/index.hbs | 8 - core/client/app/templates/posts/post.hbs | 14 - core/client/app/templates/reset.hbs | 18 - core/client/app/templates/settings/apps.hbs | 3 - .../app/templates/settings/apps/index.hbs | 33 - .../app/templates/settings/apps/slack.hbs | 42 -- .../app/templates/settings/code-injection.hbs | 30 - .../client/app/templates/settings/general.hbs | 137 ---- core/client/app/templates/settings/labs.hbs | 66 -- .../app/templates/settings/navigation.hbs | 19 - core/client/app/templates/settings/tags.hbs | 27 - .../app/templates/settings/tags/index.hbs | 6 - .../app/templates/settings/tags/tag.hbs | 11 - core/client/app/templates/setup.hbs | 27 - core/client/app/templates/setup/one.hbs | 12 - core/client/app/templates/setup/three.hbs | 19 - core/client/app/templates/setup/two.hbs | 44 -- core/client/app/templates/signin.hbs | 22 - core/client/app/templates/signup.hbs | 43 -- core/client/app/templates/subscribers.hbs | 49 -- .../app/templates/subscribers/import.hbs | 3 - core/client/app/templates/subscribers/new.hbs | 4 - core/client/app/templates/team/index.hbs | 82 -- core/client/app/templates/team/user.hbs | 197 ----- .../app/transforms/facebook-url-user.js | 21 - core/client/app/transforms/moment-date.js | 18 - .../app/transforms/navigation-settings.js | 41 - core/client/app/transforms/raw.js | 11 - core/client/app/transforms/slack-settings.js | 37 - .../client/app/transforms/twitter-url-user.js | 21 - core/client/app/transitions.js | 16 - core/client/app/utils/ajax.js | 49 -- core/client/app/utils/bound-one-way.js | 31 - core/client/app/utils/caja-sanitizers.js | 26 - core/client/app/utils/ctrl-or-cmd.js | 1 - core/client/app/utils/date-formatting.js | 39 - core/client/app/utils/document-title.js | 58 -- core/client/app/utils/ed-image-manager.js | 40 - core/client/app/utils/editor-shortcuts.js | 31 - core/client/app/utils/ghost-paths.js | 55 -- core/client/app/utils/isFinite.js | 7 - core/client/app/utils/isNumber.js | 8 - core/client/app/utils/link-component.js | 20 - core/client/app/utils/random-password.js | 8 - core/client/app/utils/text-field.js | 7 - core/client/app/utils/titleize.js | 16 - core/client/app/utils/validator-extensions.js | 19 - core/client/app/utils/window-proxy.js | 9 - core/client/app/utils/word-count.js | 18 - core/client/app/validators/base.js | 37 - core/client/app/validators/invite-user.js | 17 - core/client/app/validators/nav-item.js | 36 - core/client/app/validators/new-user.js | 35 - core/client/app/validators/post.js | 37 - core/client/app/validators/reset.js | 21 - core/client/app/validators/setting.js | 47 -- core/client/app/validators/setup.js | 14 - core/client/app/validators/signin.js | 48 -- core/client/app/validators/signup.js | 3 - .../app/validators/slack-integration.js | 24 - core/client/app/validators/subscriber.js | 19 - core/client/app/validators/tag-settings.js | 56 -- core/client/app/validators/user.js | 81 -- core/client/bower.json | 30 - core/client/config/deprecation-workflow.js | 6 - core/client/config/environment.js | 54 -- core/client/ember-cli-build.js | 85 --- core/client/lib/.jshintrc | 4 - core/client/lib/asset-delivery/index.js | 22 - core/client/lib/asset-delivery/package.json | 6 - core/client/package.json | 71 -- .../client/public/assets/fonts/ghosticons.eot | Bin 22584 -> 0 bytes .../client/public/assets/fonts/ghosticons.svg | 83 -- .../client/public/assets/fonts/ghosticons.ttf | Bin 22420 -> 0 bytes .../public/assets/fonts/ghosticons.woff | Bin 22496 -> 0 bytes core/client/public/assets/img/404-ghost.png | Bin 3681 -> 0 bytes .../client/public/assets/img/404-ghost@2x.png | Bin 7958 -> 0 bytes core/client/public/assets/img/ghost-logo.png | Bin 8638 -> 0 bytes core/client/public/assets/img/ghosticon.jpg | Bin 2499 -> 0 bytes .../public/assets/img/install-welcome.png | Bin 240204 -> 0 bytes .../public/assets/img/invite-placeholder.png | Bin 7860 -> 0 bytes core/client/public/assets/img/large.png | Bin 1912 -> 0 bytes core/client/public/assets/img/loadingcat.gif | Bin 20207 -> 0 bytes core/client/public/assets/img/medium.png | Bin 400 -> 0 bytes core/client/public/assets/img/slackicon.png | Bin 18136 -> 0 bytes core/client/public/assets/img/small.png | Bin 426 -> 0 bytes .../public/assets/img/touch-icon-ipad.png | Bin 494 -> 0 bytes .../public/assets/img/touch-icon-iphone.png | Bin 640 -> 0 bytes core/client/public/assets/img/user-cover.png | Bin 23563 -> 0 bytes core/client/public/assets/img/user-image.png | Bin 2421 -> 0 bytes core/client/public/assets/img/users.png | Bin 49253 -> 0 bytes core/client/testem.js | 14 - core/client/tests/.jscsrc | 4 - core/client/tests/.jshintrc | 58 -- .../tests/acceptance/authentication-test.js | 195 ----- .../tests/acceptance/password-reset-test.js | 107 --- .../tests/acceptance/posts/post-test.js | 71 -- .../tests/acceptance/settings/apps-test.js | 89 --- .../settings/code-injection-test.js | 91 --- .../tests/acceptance/settings/general-test.js | 325 -------- .../tests/acceptance/settings/labs-test.js | 95 --- .../acceptance/settings/navigation-test.js | 224 ------ .../tests/acceptance/settings/slack-test.js | 121 --- .../tests/acceptance/settings/tags-test.js | 305 -------- core/client/tests/acceptance/setup-test.js | 402 ---------- core/client/tests/acceptance/signin-test.js | 131 ---- .../tests/acceptance/subscribers-test.js | 256 ------- core/client/tests/acceptance/team-test.js | 479 ------------ core/client/tests/helpers/adapter-error.js | 19 - core/client/tests/helpers/destroy-app.js | 5 - .../tests/helpers/module-for-acceptance.js | 23 - core/client/tests/helpers/resolver.js | 11 - core/client/tests/helpers/start-app.js | 21 - core/client/tests/index.html | 60 -- .../tests/integration/adapters/tag-test.js | 63 -- .../tests/integration/adapters/user-test.js | 86 --- .../integration/components/gh-alert-test.js | 46 -- .../integration/components/gh-alerts-test.js | 58 -- .../components/gh-cm-editor-test.js | 53 -- .../components/gh-feature-flag-test.js | 60 -- .../components/gh-file-uploader-test.js | 98 --- .../components/gh-image-uploader-test.js | 244 ------ .../gh-image-uploader-with-preview-test.js | 48 -- .../components/gh-navigation-test.js | 75 -- .../integration/components/gh-navitem-test.js | 114 --- .../components/gh-navitem-url-input-test.js | 481 ------------ .../components/gh-notification-test.js | 44 -- .../components/gh-notifications-test.js | 44 -- .../components/gh-profile-image-test.js | 152 ---- .../components/gh-search-input-test.js | 26 - .../components/gh-subscribers-table-test.js | 26 - .../components/gh-tag-settings-form-test.js | 322 -------- .../gh-tags-management-container-test.js | 33 - .../gh-validation-status-container-test.js | 72 -- .../modals/delete-subscriber-test.js | 30 - .../modals/import-subscribers-test.js | 30 - .../components/modals/new-subscriber-test.js | 30 - .../components/transfer-owner-test.js | 39 - .../tests/integration/services/ajax-test.js | 131 ---- .../integration/services/feature-test.js | 207 ----- .../services/slug-generator-test.js | 62 -- core/client/tests/test-helper.js | 11 - core/client/tests/unit/.gitkeep | 0 .../tests/unit/components/gh-alert-test.js | 33 - .../tests/unit/components/gh-app-test.js | 28 - .../gh-content-preview-content-test.js | 28 - .../components/gh-editor-save-button-test.js | 32 - .../tests/unit/components/gh-editor-test.js | 34 - .../unit/components/gh-file-uploader-test.js | 313 -------- .../unit/components/gh-image-uploader-test.js | 344 --------- .../components/gh-infinite-scroll-test.js | 28 - .../components/gh-navitem-url-input-test.js | 39 - .../unit/components/gh-notification-test.js | 50 -- .../components/gh-posts-list-item-test.js | 28 - .../unit/components/gh-select-native-test.js | 28 - .../unit/components/gh-selectize-test.js | 40 - .../unit/components/gh-spin-button-test.js | 28 - .../components/gh-trim-focus-input_test.js | 47 -- .../unit/components/gh-url-preview_test.js | 42 -- .../unit/components/gh-user-active-test.js | 28 - .../unit/components/gh-user-invited-test.js | 28 - .../controllers/post-settings-menu-test.js | 712 ------------------ .../unit/controllers/settings/general-test.js | 93 --- .../controllers/settings/navigation-test.js | 181 ----- .../unit/controllers/subscribers-test.js | 21 - .../unit/helpers/gh-user-can-admin-test.js | 59 -- .../unit/helpers/highlighted-text-test.js | 18 - .../tests/unit/helpers/is-equal-test.js | 18 - core/client/tests/unit/helpers/is-not-test.js | 18 - .../tests/unit/mixins/infinite-scroll-test.js | 18 - .../unit/mixins/validation-engine-test.js | 42 -- .../tests/unit/models/navigation-item-test.js | 67 -- core/client/tests/unit/models/post-test.js | 84 --- core/client/tests/unit/models/role-test.js | 25 - core/client/tests/unit/models/setting-test.js | 12 - .../tests/unit/models/subscriber-test.js | 20 - core/client/tests/unit/models/tag-test.js | 12 - core/client/tests/unit/models/user-test.js | 141 ---- .../tests/unit/routes/subscribers-test.js | 20 - .../unit/routes/subscribers/import-test.js | 21 - .../tests/unit/routes/subscribers/new-test.js | 20 - .../client/tests/unit/services/config-test.js | 34 - .../tests/unit/services/notifications-test.js | 419 ----------- .../unit/transforms/facebook-url-user-test.js | 32 - .../transforms/navigation-settings-test.js | 42 -- .../unit/transforms/slack-settings-test.js | 37 - .../unit/transforms/twitter-url-user-test.js | 32 - .../tests/unit/utils/ghost-paths-test.js | 58 -- .../tests/unit/validators/nav-item-test.js | 101 --- .../unit/validators/slack-integration-test.js | 66 -- .../tests/unit/validators/subscriber-test.js | 77 -- .../unit/validators/tag-settings-test.js | 306 -------- 514 files changed, 33625 deletions(-) delete mode 100644 core/client/.ember-cli delete mode 100644 core/client/.gitignore delete mode 100644 core/client/.jscsrc delete mode 100644 core/client/.jshintrc delete mode 100644 core/client/.watchmanconfig delete mode 100644 core/client/app/README.md delete mode 100644 core/client/app/_config.yml delete mode 100644 core/client/app/adapters/application.js delete mode 100644 core/client/app/adapters/base.js delete mode 100644 core/client/app/adapters/embedded-relation-adapter.js delete mode 100644 core/client/app/adapters/setting.js delete mode 100644 core/client/app/adapters/tag.js delete mode 100644 core/client/app/adapters/user.js delete mode 100755 core/client/app/app.js delete mode 100644 core/client/app/authenticators/oauth2.js delete mode 100644 core/client/app/authorizers/oauth2.js delete mode 100644 core/client/app/components/gh-activating-list-item.js delete mode 100644 core/client/app/components/gh-alert.js delete mode 100644 core/client/app/components/gh-alerts.js delete mode 100644 core/client/app/components/gh-app.js delete mode 100644 core/client/app/components/gh-blog-url.js delete mode 100644 core/client/app/components/gh-cm-editor.js delete mode 100644 core/client/app/components/gh-content-cover.js delete mode 100644 core/client/app/components/gh-content-preview-content.js delete mode 100644 core/client/app/components/gh-content-view-container.js delete mode 100644 core/client/app/components/gh-datetime-input.js delete mode 100644 core/client/app/components/gh-dropdown-button.js delete mode 100644 core/client/app/components/gh-dropdown.js delete mode 100644 core/client/app/components/gh-ed-editor.js delete mode 100644 core/client/app/components/gh-ed-preview.js delete mode 100644 core/client/app/components/gh-editor-save-button.js delete mode 100644 core/client/app/components/gh-editor.js delete mode 100644 core/client/app/components/gh-error-message.js delete mode 100644 core/client/app/components/gh-feature-flag.js delete mode 100644 core/client/app/components/gh-file-upload.js delete mode 100644 core/client/app/components/gh-file-uploader.js delete mode 100644 core/client/app/components/gh-form-group.js delete mode 100644 core/client/app/components/gh-fullscreen-modal.js delete mode 100644 core/client/app/components/gh-image-uploader-with-preview.js delete mode 100644 core/client/app/components/gh-image-uploader.js delete mode 100644 core/client/app/components/gh-infinite-scroll.js delete mode 100644 core/client/app/components/gh-input.js delete mode 100644 core/client/app/components/gh-light-table.js delete mode 100644 core/client/app/components/gh-main.js delete mode 100644 core/client/app/components/gh-menu-toggle.js delete mode 100644 core/client/app/components/gh-nav-menu.js delete mode 100644 core/client/app/components/gh-navigation.js delete mode 100644 core/client/app/components/gh-navitem-url-input.js delete mode 100644 core/client/app/components/gh-navitem.js delete mode 100644 core/client/app/components/gh-notification.js delete mode 100644 core/client/app/components/gh-notifications.js delete mode 100644 core/client/app/components/gh-popover-button.js delete mode 100644 core/client/app/components/gh-popover.js delete mode 100644 core/client/app/components/gh-posts-list-item.js delete mode 100644 core/client/app/components/gh-profile-image.js delete mode 100644 core/client/app/components/gh-search-input.js delete mode 100644 core/client/app/components/gh-search-input/trigger.js delete mode 100644 core/client/app/components/gh-select-native.js delete mode 100644 core/client/app/components/gh-selectize.js delete mode 100644 core/client/app/components/gh-skip-link.js delete mode 100644 core/client/app/components/gh-spin-button.js delete mode 100644 core/client/app/components/gh-subscribers-table.js delete mode 100644 core/client/app/components/gh-tab-pane.js delete mode 100644 core/client/app/components/gh-tab.js delete mode 100644 core/client/app/components/gh-tabs-manager.js delete mode 100644 core/client/app/components/gh-tag-settings-form.js delete mode 100644 core/client/app/components/gh-tag.js delete mode 100644 core/client/app/components/gh-tags-management-container.js delete mode 100644 core/client/app/components/gh-textarea.js delete mode 100644 core/client/app/components/gh-trim-focus-input.js delete mode 100644 core/client/app/components/gh-url-preview.js delete mode 100644 core/client/app/components/gh-user-active.js delete mode 100644 core/client/app/components/gh-user-invited.js delete mode 100644 core/client/app/components/gh-validation-status-container.js delete mode 100644 core/client/app/components/gh-view-title.js delete mode 100644 core/client/app/components/modals/base.js delete mode 100644 core/client/app/components/modals/copy-html.js delete mode 100644 core/client/app/components/modals/delete-all.js delete mode 100644 core/client/app/components/modals/delete-post.js delete mode 100644 core/client/app/components/modals/delete-subscriber.js delete mode 100644 core/client/app/components/modals/delete-tag.js delete mode 100644 core/client/app/components/modals/delete-user.js delete mode 100644 core/client/app/components/modals/import-subscribers.js delete mode 100644 core/client/app/components/modals/invite-new-user.js delete mode 100644 core/client/app/components/modals/leave-editor.js delete mode 100644 core/client/app/components/modals/markdown-help.js delete mode 100644 core/client/app/components/modals/new-subscriber.js delete mode 100644 core/client/app/components/modals/re-authenticate.js delete mode 100644 core/client/app/components/modals/transfer-owner.js delete mode 100644 core/client/app/components/modals/upload-image.js delete mode 100644 core/client/app/controllers/about.js delete mode 100644 core/client/app/controllers/application.js delete mode 100644 core/client/app/controllers/editor/edit.js delete mode 100644 core/client/app/controllers/editor/new.js delete mode 100644 core/client/app/controllers/error.js delete mode 100644 core/client/app/controllers/post-settings-menu.js delete mode 100644 core/client/app/controllers/posts.js delete mode 100644 core/client/app/controllers/reset.js delete mode 100644 core/client/app/controllers/settings/apps/index.js delete mode 100644 core/client/app/controllers/settings/apps/slack.js delete mode 100644 core/client/app/controllers/settings/code-injection.js delete mode 100644 core/client/app/controllers/settings/general.js delete mode 100644 core/client/app/controllers/settings/labs.js delete mode 100644 core/client/app/controllers/settings/navigation.js delete mode 100644 core/client/app/controllers/settings/tags.js delete mode 100644 core/client/app/controllers/settings/tags/tag.js delete mode 100644 core/client/app/controllers/setup.js delete mode 100644 core/client/app/controllers/setup/three.js delete mode 100644 core/client/app/controllers/setup/two.js delete mode 100644 core/client/app/controllers/signin.js delete mode 100644 core/client/app/controllers/signup.js delete mode 100644 core/client/app/controllers/subscribers.js delete mode 100644 core/client/app/controllers/team/index.js delete mode 100644 core/client/app/controllers/team/user.js delete mode 100644 core/client/app/helpers/gh-count-characters.js delete mode 100644 core/client/app/helpers/gh-count-down-characters.js delete mode 100644 core/client/app/helpers/gh-count-words.js delete mode 100644 core/client/app/helpers/gh-format-html.js delete mode 100644 core/client/app/helpers/gh-format-markdown.js delete mode 100644 core/client/app/helpers/gh-format-timeago.js delete mode 100644 core/client/app/helpers/gh-path.js delete mode 100644 core/client/app/helpers/gh-user-can-admin.js delete mode 100644 core/client/app/helpers/highlighted-text.js delete mode 100644 core/client/app/helpers/is-equal.js delete mode 100644 core/client/app/helpers/is-not.js delete mode 100644 core/client/app/html/apps.html delete mode 100644 core/client/app/html/permalinks.html delete mode 100644 core/client/app/html/themes.html delete mode 100644 core/client/app/index.html delete mode 100644 core/client/app/initializers/ember-simple-auth.js delete mode 100644 core/client/app/initializers/trailing-history.js delete mode 100644 core/client/app/instance-initializers/jquery-ajax-oauth-prefilter.js delete mode 100644 core/client/app/mirage/config.js delete mode 100644 core/client/app/mirage/factories/notification.js delete mode 100644 core/client/app/mirage/factories/post.js delete mode 100644 core/client/app/mirage/factories/role.js delete mode 100644 core/client/app/mirage/factories/setting.js delete mode 100644 core/client/app/mirage/factories/subscriber.js delete mode 100644 core/client/app/mirage/factories/tag.js delete mode 100644 core/client/app/mirage/factories/user.js delete mode 100644 core/client/app/mirage/fixtures/roles.js delete mode 100644 core/client/app/mirage/fixtures/settings.js delete mode 100644 core/client/app/mirage/scenarios/default.js delete mode 100644 core/client/app/mixins/404-handler.js delete mode 100644 core/client/app/mixins/active-link-wrapper.js delete mode 100644 core/client/app/mixins/body-event-listener.js delete mode 100644 core/client/app/mixins/current-user-settings.js delete mode 100644 core/client/app/mixins/dropdown-mixin.js delete mode 100644 core/client/app/mixins/ed-editor-api.js delete mode 100644 core/client/app/mixins/ed-editor-scroll.js delete mode 100644 core/client/app/mixins/ed-editor-shortcuts.js delete mode 100644 core/client/app/mixins/editor-base-controller.js delete mode 100644 core/client/app/mixins/editor-base-route.js delete mode 100644 core/client/app/mixins/infinite-scroll.js delete mode 100644 core/client/app/mixins/pagination.js delete mode 100644 core/client/app/mixins/settings-menu-controller.js delete mode 100644 core/client/app/mixins/settings-save.js delete mode 100644 core/client/app/mixins/shortcuts-route.js delete mode 100644 core/client/app/mixins/shortcuts.js delete mode 100644 core/client/app/mixins/slug-url.js delete mode 100644 core/client/app/mixins/style-body.js delete mode 100644 core/client/app/mixins/text-input.js delete mode 100644 core/client/app/mixins/validation-engine.js delete mode 100644 core/client/app/mixins/validation-state.js delete mode 100644 core/client/app/models/navigation-item.js delete mode 100644 core/client/app/models/notification.js delete mode 100644 core/client/app/models/post.js delete mode 100644 core/client/app/models/role.js delete mode 100644 core/client/app/models/setting.js delete mode 100644 core/client/app/models/slack-integration.js delete mode 100644 core/client/app/models/subscriber.js delete mode 100644 core/client/app/models/tag.js delete mode 100644 core/client/app/models/user.js delete mode 100644 core/client/app/resolver.js delete mode 100644 core/client/app/router.js delete mode 100644 core/client/app/routes/about.js delete mode 100644 core/client/app/routes/application.js delete mode 100644 core/client/app/routes/authenticated.js delete mode 100644 core/client/app/routes/editor/edit.js delete mode 100644 core/client/app/routes/editor/index.js delete mode 100644 core/client/app/routes/editor/new.js delete mode 100644 core/client/app/routes/error404.js delete mode 100644 core/client/app/routes/mobile-index-route.js delete mode 100644 core/client/app/routes/posts.js delete mode 100644 core/client/app/routes/posts/index.js delete mode 100644 core/client/app/routes/posts/post.js delete mode 100644 core/client/app/routes/reset.js delete mode 100644 core/client/app/routes/settings/apps.js delete mode 100644 core/client/app/routes/settings/apps/slack.js delete mode 100644 core/client/app/routes/settings/code-injection.js delete mode 100644 core/client/app/routes/settings/general.js delete mode 100644 core/client/app/routes/settings/labs.js delete mode 100644 core/client/app/routes/settings/navigation.js delete mode 100644 core/client/app/routes/settings/tags.js delete mode 100644 core/client/app/routes/settings/tags/index.js delete mode 100644 core/client/app/routes/settings/tags/new.js delete mode 100644 core/client/app/routes/settings/tags/tag.js delete mode 100644 core/client/app/routes/setup.js delete mode 100644 core/client/app/routes/setup/index.js delete mode 100644 core/client/app/routes/setup/one.js delete mode 100644 core/client/app/routes/setup/three.js delete mode 100644 core/client/app/routes/signin.js delete mode 100644 core/client/app/routes/signout.js delete mode 100644 core/client/app/routes/signup.js delete mode 100644 core/client/app/routes/subscribers.js delete mode 100644 core/client/app/routes/subscribers/import.js delete mode 100644 core/client/app/routes/subscribers/new.js delete mode 100644 core/client/app/routes/team/index.js delete mode 100644 core/client/app/routes/team/user.js delete mode 100644 core/client/app/serializers/application.js delete mode 100644 core/client/app/serializers/post.js delete mode 100644 core/client/app/serializers/setting.js delete mode 100644 core/client/app/serializers/tag.js delete mode 100644 core/client/app/serializers/user.js delete mode 100644 core/client/app/services/ajax.js delete mode 100644 core/client/app/services/config.js delete mode 100644 core/client/app/services/dropdown.js delete mode 100644 core/client/app/services/feature.js delete mode 100644 core/client/app/services/ghost-paths.js delete mode 100644 core/client/app/services/media-queries.js delete mode 100644 core/client/app/services/notifications.js delete mode 100644 core/client/app/services/session.js delete mode 100644 core/client/app/services/slug-generator.js delete mode 100644 core/client/app/session-stores/application.js delete mode 100644 core/client/app/styles/app.css delete mode 100644 core/client/app/styles/components/badges.css delete mode 100644 core/client/app/styles/components/dropdowns.css delete mode 100644 core/client/app/styles/components/modals.css delete mode 100644 core/client/app/styles/components/notifications.css delete mode 100644 core/client/app/styles/components/pagination.css delete mode 100644 core/client/app/styles/components/popovers.css delete mode 100644 core/client/app/styles/components/power-select.css delete mode 100644 core/client/app/styles/components/selectize.css delete mode 100644 core/client/app/styles/components/settings-menu.css delete mode 100644 core/client/app/styles/components/splitbuttons.css delete mode 100644 core/client/app/styles/components/uploader.css delete mode 100644 core/client/app/styles/csscomb.json delete mode 100644 core/client/app/styles/layouts/about.css delete mode 100644 core/client/app/styles/layouts/apps.css delete mode 100644 core/client/app/styles/layouts/auth.css delete mode 100644 core/client/app/styles/layouts/content.css delete mode 100644 core/client/app/styles/layouts/editor.css delete mode 100644 core/client/app/styles/layouts/error.css delete mode 100644 core/client/app/styles/layouts/flow.css delete mode 100644 core/client/app/styles/layouts/main.css delete mode 100644 core/client/app/styles/layouts/packages.css delete mode 100644 core/client/app/styles/layouts/settings.css delete mode 100644 core/client/app/styles/layouts/subscribers.css delete mode 100644 core/client/app/styles/layouts/tags.css delete mode 100644 core/client/app/styles/layouts/user.css delete mode 100644 core/client/app/styles/layouts/users.css delete mode 100644 core/client/app/styles/patterns/_shame.css delete mode 100644 core/client/app/styles/patterns/buttons.css delete mode 100644 core/client/app/styles/patterns/forms.css delete mode 100644 core/client/app/styles/patterns/global.css delete mode 100755 core/client/app/styles/patterns/icons.css delete mode 100644 core/client/app/styles/patterns/labels.css delete mode 100644 core/client/app/styles/patterns/navlist.css delete mode 100644 core/client/app/styles/patterns/tables.css delete mode 100644 core/client/app/templates/-import-errors.hbs delete mode 100644 core/client/app/templates/-user-list-item.hbs delete mode 100644 core/client/app/templates/about.hbs delete mode 100644 core/client/app/templates/application.hbs delete mode 100644 core/client/app/templates/components/gh-activating-list-item.hbs delete mode 100644 core/client/app/templates/components/gh-alert.hbs delete mode 100644 core/client/app/templates/components/gh-alerts.hbs delete mode 100644 core/client/app/templates/components/gh-app.hbs delete mode 100644 core/client/app/templates/components/gh-blog-url.hbs delete mode 100644 core/client/app/templates/components/gh-content-preview-content.hbs delete mode 100644 core/client/app/templates/components/gh-content-view-container.hbs delete mode 100644 core/client/app/templates/components/gh-datetime-input.hbs delete mode 100644 core/client/app/templates/components/gh-ed-preview.hbs delete mode 100644 core/client/app/templates/components/gh-editor-save-button.hbs delete mode 100644 core/client/app/templates/components/gh-editor.hbs delete mode 100644 core/client/app/templates/components/gh-error-message.hbs delete mode 100644 core/client/app/templates/components/gh-feature-flag.hbs delete mode 100644 core/client/app/templates/components/gh-file-upload.hbs delete mode 100644 core/client/app/templates/components/gh-file-uploader.hbs delete mode 100644 core/client/app/templates/components/gh-fullscreen-modal.hbs delete mode 100644 core/client/app/templates/components/gh-image-uploader-with-preview.hbs delete mode 100644 core/client/app/templates/components/gh-image-uploader.hbs delete mode 100644 core/client/app/templates/components/gh-infinite-scroll.hbs delete mode 100644 core/client/app/templates/components/gh-menu-toggle.hbs delete mode 100644 core/client/app/templates/components/gh-modal-dialog.hbs delete mode 100644 core/client/app/templates/components/gh-nav-menu.hbs delete mode 100644 core/client/app/templates/components/gh-navigation.hbs delete mode 100644 core/client/app/templates/components/gh-navitem.hbs delete mode 100644 core/client/app/templates/components/gh-notification.hbs delete mode 100644 core/client/app/templates/components/gh-notifications.hbs delete mode 100644 core/client/app/templates/components/gh-posts-list-item.hbs delete mode 100644 core/client/app/templates/components/gh-profile-image.hbs delete mode 100644 core/client/app/templates/components/gh-search-input.hbs delete mode 100644 core/client/app/templates/components/gh-search-input/trigger.hbs delete mode 100644 core/client/app/templates/components/gh-select-native.hbs delete mode 100644 core/client/app/templates/components/gh-spin-button.hbs delete mode 100644 core/client/app/templates/components/gh-subscribers-table-delete-cell.hbs delete mode 100644 core/client/app/templates/components/gh-subscribers-table.hbs delete mode 100644 core/client/app/templates/components/gh-tag-settings-form.hbs delete mode 100644 core/client/app/templates/components/gh-tag.hbs delete mode 100644 core/client/app/templates/components/gh-tags-management-container.hbs delete mode 100644 core/client/app/templates/components/gh-url-preview.hbs delete mode 100644 core/client/app/templates/components/gh-user-active.hbs delete mode 100644 core/client/app/templates/components/gh-user-invited.hbs delete mode 100644 core/client/app/templates/components/gh-view-title.hbs delete mode 100644 core/client/app/templates/components/modals/copy-html.hbs delete mode 100644 core/client/app/templates/components/modals/delete-all.hbs delete mode 100644 core/client/app/templates/components/modals/delete-post.hbs delete mode 100644 core/client/app/templates/components/modals/delete-subscriber.hbs delete mode 100644 core/client/app/templates/components/modals/delete-tag.hbs delete mode 100644 core/client/app/templates/components/modals/delete-user.hbs delete mode 100644 core/client/app/templates/components/modals/import-subscribers.hbs delete mode 100644 core/client/app/templates/components/modals/invite-new-user.hbs delete mode 100644 core/client/app/templates/components/modals/leave-editor.hbs delete mode 100644 core/client/app/templates/components/modals/markdown-help.hbs delete mode 100644 core/client/app/templates/components/modals/new-subscriber.hbs delete mode 100644 core/client/app/templates/components/modals/re-authenticate.hbs delete mode 100644 core/client/app/templates/components/modals/transfer-owner.hbs delete mode 100644 core/client/app/templates/components/modals/upload-image.hbs delete mode 100644 core/client/app/templates/editor/edit.hbs delete mode 100644 core/client/app/templates/error.hbs delete mode 100644 core/client/app/templates/post-settings-menu.hbs delete mode 100644 core/client/app/templates/posts.hbs delete mode 100644 core/client/app/templates/posts/index.hbs delete mode 100644 core/client/app/templates/posts/post.hbs delete mode 100644 core/client/app/templates/reset.hbs delete mode 100644 core/client/app/templates/settings/apps.hbs delete mode 100644 core/client/app/templates/settings/apps/index.hbs delete mode 100644 core/client/app/templates/settings/apps/slack.hbs delete mode 100644 core/client/app/templates/settings/code-injection.hbs delete mode 100644 core/client/app/templates/settings/general.hbs delete mode 100644 core/client/app/templates/settings/labs.hbs delete mode 100644 core/client/app/templates/settings/navigation.hbs delete mode 100644 core/client/app/templates/settings/tags.hbs delete mode 100644 core/client/app/templates/settings/tags/index.hbs delete mode 100644 core/client/app/templates/settings/tags/tag.hbs delete mode 100644 core/client/app/templates/setup.hbs delete mode 100644 core/client/app/templates/setup/one.hbs delete mode 100644 core/client/app/templates/setup/three.hbs delete mode 100644 core/client/app/templates/setup/two.hbs delete mode 100644 core/client/app/templates/signin.hbs delete mode 100644 core/client/app/templates/signup.hbs delete mode 100644 core/client/app/templates/subscribers.hbs delete mode 100644 core/client/app/templates/subscribers/import.hbs delete mode 100644 core/client/app/templates/subscribers/new.hbs delete mode 100644 core/client/app/templates/team/index.hbs delete mode 100644 core/client/app/templates/team/user.hbs delete mode 100644 core/client/app/transforms/facebook-url-user.js delete mode 100644 core/client/app/transforms/moment-date.js delete mode 100644 core/client/app/transforms/navigation-settings.js delete mode 100644 core/client/app/transforms/raw.js delete mode 100644 core/client/app/transforms/slack-settings.js delete mode 100644 core/client/app/transforms/twitter-url-user.js delete mode 100644 core/client/app/transitions.js delete mode 100644 core/client/app/utils/ajax.js delete mode 100644 core/client/app/utils/bound-one-way.js delete mode 100644 core/client/app/utils/caja-sanitizers.js delete mode 100644 core/client/app/utils/ctrl-or-cmd.js delete mode 100644 core/client/app/utils/date-formatting.js delete mode 100644 core/client/app/utils/document-title.js delete mode 100644 core/client/app/utils/ed-image-manager.js delete mode 100644 core/client/app/utils/editor-shortcuts.js delete mode 100644 core/client/app/utils/ghost-paths.js delete mode 100644 core/client/app/utils/isFinite.js delete mode 100644 core/client/app/utils/isNumber.js delete mode 100644 core/client/app/utils/link-component.js delete mode 100644 core/client/app/utils/random-password.js delete mode 100644 core/client/app/utils/text-field.js delete mode 100644 core/client/app/utils/titleize.js delete mode 100644 core/client/app/utils/validator-extensions.js delete mode 100644 core/client/app/utils/window-proxy.js delete mode 100644 core/client/app/utils/word-count.js delete mode 100644 core/client/app/validators/base.js delete mode 100644 core/client/app/validators/invite-user.js delete mode 100644 core/client/app/validators/nav-item.js delete mode 100644 core/client/app/validators/new-user.js delete mode 100644 core/client/app/validators/post.js delete mode 100644 core/client/app/validators/reset.js delete mode 100644 core/client/app/validators/setting.js delete mode 100644 core/client/app/validators/setup.js delete mode 100644 core/client/app/validators/signin.js delete mode 100644 core/client/app/validators/signup.js delete mode 100644 core/client/app/validators/slack-integration.js delete mode 100644 core/client/app/validators/subscriber.js delete mode 100644 core/client/app/validators/tag-settings.js delete mode 100644 core/client/app/validators/user.js delete mode 100644 core/client/bower.json delete mode 100644 core/client/config/deprecation-workflow.js delete mode 100644 core/client/config/environment.js delete mode 100644 core/client/ember-cli-build.js delete mode 100644 core/client/lib/.jshintrc delete mode 100644 core/client/lib/asset-delivery/index.js delete mode 100644 core/client/lib/asset-delivery/package.json delete mode 100644 core/client/package.json delete mode 100755 core/client/public/assets/fonts/ghosticons.eot delete mode 100755 core/client/public/assets/fonts/ghosticons.svg delete mode 100755 core/client/public/assets/fonts/ghosticons.ttf delete mode 100755 core/client/public/assets/fonts/ghosticons.woff delete mode 100644 core/client/public/assets/img/404-ghost.png delete mode 100644 core/client/public/assets/img/404-ghost@2x.png delete mode 100644 core/client/public/assets/img/ghost-logo.png delete mode 100644 core/client/public/assets/img/ghosticon.jpg delete mode 100644 core/client/public/assets/img/install-welcome.png delete mode 100644 core/client/public/assets/img/invite-placeholder.png delete mode 100644 core/client/public/assets/img/large.png delete mode 100644 core/client/public/assets/img/loadingcat.gif delete mode 100644 core/client/public/assets/img/medium.png delete mode 100644 core/client/public/assets/img/slackicon.png delete mode 100644 core/client/public/assets/img/small.png delete mode 100644 core/client/public/assets/img/touch-icon-ipad.png delete mode 100644 core/client/public/assets/img/touch-icon-iphone.png delete mode 100644 core/client/public/assets/img/user-cover.png delete mode 100644 core/client/public/assets/img/user-image.png delete mode 100644 core/client/public/assets/img/users.png delete mode 100644 core/client/testem.js delete mode 100644 core/client/tests/.jscsrc delete mode 100644 core/client/tests/.jshintrc delete mode 100644 core/client/tests/acceptance/authentication-test.js delete mode 100644 core/client/tests/acceptance/password-reset-test.js delete mode 100644 core/client/tests/acceptance/posts/post-test.js delete mode 100644 core/client/tests/acceptance/settings/apps-test.js delete mode 100644 core/client/tests/acceptance/settings/code-injection-test.js delete mode 100644 core/client/tests/acceptance/settings/general-test.js delete mode 100644 core/client/tests/acceptance/settings/labs-test.js delete mode 100644 core/client/tests/acceptance/settings/navigation-test.js delete mode 100644 core/client/tests/acceptance/settings/slack-test.js delete mode 100644 core/client/tests/acceptance/settings/tags-test.js delete mode 100644 core/client/tests/acceptance/setup-test.js delete mode 100644 core/client/tests/acceptance/signin-test.js delete mode 100644 core/client/tests/acceptance/subscribers-test.js delete mode 100644 core/client/tests/acceptance/team-test.js delete mode 100644 core/client/tests/helpers/adapter-error.js delete mode 100644 core/client/tests/helpers/destroy-app.js delete mode 100644 core/client/tests/helpers/module-for-acceptance.js delete mode 100644 core/client/tests/helpers/resolver.js delete mode 100644 core/client/tests/helpers/start-app.js delete mode 100644 core/client/tests/index.html delete mode 100644 core/client/tests/integration/adapters/tag-test.js delete mode 100644 core/client/tests/integration/adapters/user-test.js delete mode 100644 core/client/tests/integration/components/gh-alert-test.js delete mode 100644 core/client/tests/integration/components/gh-alerts-test.js delete mode 100644 core/client/tests/integration/components/gh-cm-editor-test.js delete mode 100644 core/client/tests/integration/components/gh-feature-flag-test.js delete mode 100644 core/client/tests/integration/components/gh-file-uploader-test.js delete mode 100644 core/client/tests/integration/components/gh-image-uploader-test.js delete mode 100644 core/client/tests/integration/components/gh-image-uploader-with-preview-test.js delete mode 100644 core/client/tests/integration/components/gh-navigation-test.js delete mode 100644 core/client/tests/integration/components/gh-navitem-test.js delete mode 100644 core/client/tests/integration/components/gh-navitem-url-input-test.js delete mode 100644 core/client/tests/integration/components/gh-notification-test.js delete mode 100644 core/client/tests/integration/components/gh-notifications-test.js delete mode 100644 core/client/tests/integration/components/gh-profile-image-test.js delete mode 100644 core/client/tests/integration/components/gh-search-input-test.js delete mode 100644 core/client/tests/integration/components/gh-subscribers-table-test.js delete mode 100644 core/client/tests/integration/components/gh-tag-settings-form-test.js delete mode 100644 core/client/tests/integration/components/gh-tags-management-container-test.js delete mode 100644 core/client/tests/integration/components/gh-validation-status-container-test.js delete mode 100644 core/client/tests/integration/components/modals/delete-subscriber-test.js delete mode 100644 core/client/tests/integration/components/modals/import-subscribers-test.js delete mode 100644 core/client/tests/integration/components/modals/new-subscriber-test.js delete mode 100644 core/client/tests/integration/components/transfer-owner-test.js delete mode 100644 core/client/tests/integration/services/ajax-test.js delete mode 100644 core/client/tests/integration/services/feature-test.js delete mode 100644 core/client/tests/integration/services/slug-generator-test.js delete mode 100644 core/client/tests/test-helper.js delete mode 100644 core/client/tests/unit/.gitkeep delete mode 100644 core/client/tests/unit/components/gh-alert-test.js delete mode 100644 core/client/tests/unit/components/gh-app-test.js delete mode 100644 core/client/tests/unit/components/gh-content-preview-content-test.js delete mode 100644 core/client/tests/unit/components/gh-editor-save-button-test.js delete mode 100644 core/client/tests/unit/components/gh-editor-test.js delete mode 100644 core/client/tests/unit/components/gh-file-uploader-test.js delete mode 100644 core/client/tests/unit/components/gh-image-uploader-test.js delete mode 100644 core/client/tests/unit/components/gh-infinite-scroll-test.js delete mode 100644 core/client/tests/unit/components/gh-navitem-url-input-test.js delete mode 100644 core/client/tests/unit/components/gh-notification-test.js delete mode 100644 core/client/tests/unit/components/gh-posts-list-item-test.js delete mode 100644 core/client/tests/unit/components/gh-select-native-test.js delete mode 100644 core/client/tests/unit/components/gh-selectize-test.js delete mode 100644 core/client/tests/unit/components/gh-spin-button-test.js delete mode 100644 core/client/tests/unit/components/gh-trim-focus-input_test.js delete mode 100644 core/client/tests/unit/components/gh-url-preview_test.js delete mode 100644 core/client/tests/unit/components/gh-user-active-test.js delete mode 100644 core/client/tests/unit/components/gh-user-invited-test.js delete mode 100644 core/client/tests/unit/controllers/post-settings-menu-test.js delete mode 100644 core/client/tests/unit/controllers/settings/general-test.js delete mode 100644 core/client/tests/unit/controllers/settings/navigation-test.js delete mode 100644 core/client/tests/unit/controllers/subscribers-test.js delete mode 100644 core/client/tests/unit/helpers/gh-user-can-admin-test.js delete mode 100644 core/client/tests/unit/helpers/highlighted-text-test.js delete mode 100644 core/client/tests/unit/helpers/is-equal-test.js delete mode 100644 core/client/tests/unit/helpers/is-not-test.js delete mode 100644 core/client/tests/unit/mixins/infinite-scroll-test.js delete mode 100644 core/client/tests/unit/mixins/validation-engine-test.js delete mode 100644 core/client/tests/unit/models/navigation-item-test.js delete mode 100644 core/client/tests/unit/models/post-test.js delete mode 100644 core/client/tests/unit/models/role-test.js delete mode 100644 core/client/tests/unit/models/setting-test.js delete mode 100644 core/client/tests/unit/models/subscriber-test.js delete mode 100644 core/client/tests/unit/models/tag-test.js delete mode 100644 core/client/tests/unit/models/user-test.js delete mode 100644 core/client/tests/unit/routes/subscribers-test.js delete mode 100644 core/client/tests/unit/routes/subscribers/import-test.js delete mode 100644 core/client/tests/unit/routes/subscribers/new-test.js delete mode 100644 core/client/tests/unit/services/config-test.js delete mode 100644 core/client/tests/unit/services/notifications-test.js delete mode 100644 core/client/tests/unit/transforms/facebook-url-user-test.js delete mode 100644 core/client/tests/unit/transforms/navigation-settings-test.js delete mode 100644 core/client/tests/unit/transforms/slack-settings-test.js delete mode 100644 core/client/tests/unit/transforms/twitter-url-user-test.js delete mode 100644 core/client/tests/unit/utils/ghost-paths-test.js delete mode 100644 core/client/tests/unit/validators/nav-item-test.js delete mode 100644 core/client/tests/unit/validators/slack-integration-test.js delete mode 100644 core/client/tests/unit/validators/subscriber-test.js delete mode 100644 core/client/tests/unit/validators/tag-settings-test.js diff --git a/core/client/.ember-cli b/core/client/.ember-cli deleted file mode 100644 index 59bb55fe90b7..000000000000 --- a/core/client/.ember-cli +++ /dev/null @@ -1,9 +0,0 @@ -{ - /** - Ember CLI sends analytics information by default. The data is completely - anonymous, but there are times when you might want to disable this behavior. - - Setting `disableAnalytics` to true will prevent any data from being sent. - */ - "disableAnalytics": true -} diff --git a/core/client/.gitignore b/core/client/.gitignore deleted file mode 100644 index 7fa0a2c1c335..000000000000 --- a/core/client/.gitignore +++ /dev/null @@ -1,20 +0,0 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. - -# compiled output -/dist -/tmp - -# dependencies -/node_modules -/bower_components - -# misc -/connect.lock -/coverage/* -/libpeerconnection.log -npm-debug.log -testem.log - -# built by grunt -public/assets/img/contributors/ -app/templates/-contributors.hbs diff --git a/core/client/.jscsrc b/core/client/.jscsrc deleted file mode 100644 index 15197078cef7..000000000000 --- a/core/client/.jscsrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "preset": "ember-suave", - "validateIndentation": 4, - "disallowSpacesInFunction": null, - "disallowSpacesInNamedFunctionExpression": { - "beforeOpeningRoundBrace": true - }, - "disallowSpacesInFunctionDeclaration": { - "beforeOpeningRoundBrace": true - }, - "disallowSpacesInsideObjectBrackets": "all", - "requireCommentsToIncludeAccess": null, - "requireSpacesInsideObjectBrackets": null -} diff --git a/core/client/.jshintrc b/core/client/.jshintrc deleted file mode 100644 index c753d6085b3a..000000000000 --- a/core/client/.jshintrc +++ /dev/null @@ -1,36 +0,0 @@ -{ - "predef": [ - "server", - "document", - "window", - "-Promise", - "-Notification", - "validator", - "moment" - ], - "browser": true, - "boss": true, - "curly": true, - "debug": false, - "devel": true, - "eqeqeq": true, - "evil": true, - "forin": false, - "immed": false, - "laxbreak": false, - "newcap": true, - "noarg": true, - "noempty": false, - "nonew": false, - "nomen": false, - "onevar": false, - "plusplus": false, - "regexp": false, - "undef": true, - "sub": true, - "strict": false, - "white": false, - "eqnull": true, - "esnext": true, - "unused": true -} diff --git a/core/client/.watchmanconfig b/core/client/.watchmanconfig deleted file mode 100644 index e7834e3e4f39..000000000000 --- a/core/client/.watchmanconfig +++ /dev/null @@ -1,3 +0,0 @@ -{ - "ignore_dirs": ["tmp", "dist"] -} diff --git a/core/client/app/README.md b/core/client/app/README.md deleted file mode 100644 index 125ed2f578cc..000000000000 --- a/core/client/app/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Ghost Admin Client - -Ember.js application used as a client-side admin for the [Ghost](http://ghost.org) blogging platform. This readme is a work in progress guide aimed at explaining the specific nuances of the Ghost Ember app to contributors whose main focus is on this side of things. - - -## CSS - -We use pure CSS, which is pre-processed for backwards compatibility by [Myth](http://myth.io). We do not follow any strict CSS framework, however our general style is pretty similar to BEM. - -Styles are primarily broken up into 4 main categories: - -* **Patterns** - are base level visual styles for HTML elements (eg. Buttons) -* **Components** - are groups of patterns used to create a UI component (eg. Modals) -* **Layouts** - are groups of components used to create application screens (eg. Settings) - -All of these separate files are subsequently imported and compiled in `app.css`. - - -## Front End Standards - -* 4 spaces for HTML & CSS indentation. Never tabs. -* Double quotes only, never single quotes. -* Use tags and elements appropriate for an HTML5 doctype (including self-closing tags) -* Adhere to the [Recess CSS](http://markdotto.com/2011/11/29/css-property-order/) property order. -* Always a space after a property's colon (.e.g, display: block; and not display:block;). -* End all lines with a semi-colon. -* For multiple, comma-separated selectors, place each selector on its own line. -* Use js- prefixed classes for JavaScript hooks into the DOM, and never use these in CSS as per [Slightly Obtrusive JavaSript](http://ozmm.org/posts/slightly_obtrusive_javascript.html) -* Avoid over-nesting CSS. Never nest more than 3 levels deep. -* Use comments to explain "why" not "what" (Good: This requires a z-index in order to appear above mobile navigation. Bad: This is a thing which is always on top!) diff --git a/core/client/app/_config.yml b/core/client/app/_config.yml deleted file mode 100644 index 4c390fe3be92..000000000000 --- a/core/client/app/_config.yml +++ /dev/null @@ -1,15 +0,0 @@ -# Dependencies -markdown: kramdown -highlighter: highlighter - -# Permalinks -permalink: pretty - -# Server -source: docs -destination: _gh_pages -host: 0.0.0.0 -port: 9001 -baseurl: -url: http://localhost:9001 -encoding: UTF-8 diff --git a/core/client/app/adapters/application.js b/core/client/app/adapters/application.js deleted file mode 100644 index 924f2e53c182..000000000000 --- a/core/client/app/adapters/application.js +++ /dev/null @@ -1,9 +0,0 @@ -import EmbeddedRelationAdapter from 'ghost/adapters/embedded-relation-adapter'; - -export default EmbeddedRelationAdapter.extend({ - - shouldBackgroundReloadRecord() { - return false; - } - -}); diff --git a/core/client/app/adapters/base.js b/core/client/app/adapters/base.js deleted file mode 100644 index c5d42c0ce9cc..000000000000 --- a/core/client/app/adapters/base.js +++ /dev/null @@ -1,54 +0,0 @@ -import Ember from 'ember'; -import RESTAdapter from 'ember-data/adapters/rest'; -import ghostPaths from 'ghost/utils/ghost-paths'; -import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin'; - -const { - inject: {service} -} = Ember; - -export default RESTAdapter.extend(DataAdapterMixin, { - authorizer: 'authorizer:oauth2', - - host: window.location.origin, - namespace: ghostPaths().apiRoot.slice(1), - - session: service(), - - shouldBackgroundReloadRecord() { - return false; - }, - - query(store, type, query) { - let id; - - if (query.id) { - id = query.id; - delete query.id; - } - - return this.ajax(this.buildURL(type.modelName, id), 'GET', {data: query}); - }, - - buildURL() { - // Ensure trailing slashes - let url = this._super(...arguments); - - if (url.slice(-1) !== '/') { - url += '/'; - } - - return url; - }, - - handleResponse(status) { - if (status === 401) { - if (this.get('session.isAuthenticated')) { - this.get('session').invalidate(); - return; // prevent error from bubbling because invalidate is async - } - } - - return this._super(...arguments); - } -}); diff --git a/core/client/app/adapters/embedded-relation-adapter.js b/core/client/app/adapters/embedded-relation-adapter.js deleted file mode 100644 index f971405c3d96..000000000000 --- a/core/client/app/adapters/embedded-relation-adapter.js +++ /dev/null @@ -1,132 +0,0 @@ -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. -// In cases where there is no query object (DS.Model#save, or simple finds) the URL -// that is built will be augmented with ?include=... where appropriate. -// -// Example: -// If a model has an embedded hasMany relation, the related type will be included: -// roles: DS.hasMany('role', { embedded: 'always' }) => ?include=roles - -export default BaseAdapter.extend({ - find(store, type, id, snapshot) { - return this.ajax(this.buildIncludeURL(store, type.modelName, id, snapshot, 'find'), 'GET'); - }, - - findRecord(store, type, id, snapshot) { - return this.ajax(this.buildIncludeURL(store, type.modelName, id, snapshot, 'findRecord'), 'GET'); - }, - - findAll(store, type, sinceToken) { - let query, url; - - if (sinceToken) { - query = {since: sinceToken}; - } - - url = this.buildIncludeURL(store, type.modelName, null, null, 'findAll'); - - return this.ajax(url, 'GET', {data: query}); - }, - - query(store, type, query) { - return this._super(store, type, this.buildQuery(store, type.modelName, query)); - }, - - queryRecord(store, type, query) { - return this._super(store, type, this.buildQuery(store, type.modelName, query)); - }, - - createRecord(store, type, snapshot) { - return this.saveRecord(store, type, snapshot, {method: 'POST'}, 'createRecord'); - }, - - updateRecord(store, type, snapshot) { - let options = { - method: 'PUT', - id: get(snapshot, 'id') - }; - - return this.saveRecord(store, type, snapshot, options, 'updateRecord'); - }, - - saveRecord(store, type, snapshot, options, requestType) { - let _options = options || {}; - let url = this.buildIncludeURL(store, type.modelName, _options.id, snapshot, requestType); - let payload = this.preparePayload(store, type, snapshot); - - return this.ajax(url, _options.method, payload); - }, - - preparePayload(store, type, snapshot) { - let serializer = store.serializerFor(type.modelName); - let payload = {}; - - serializer.serializeIntoHash(payload, type, snapshot); - - return {data: payload}; - }, - - 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(',')}`; - } - - return url; - }, - - 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 - if (typeof options === 'string' || typeof options === 'number') { - query = {}; - query.id = options; - query.include = toInclude.join(','); - } 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 - // into the existing object. Existing properties and includes are preserved. - query = query || {}; - toInclude = toInclude.concat(query.include ? query.include.split(',') : []); - - toInclude.forEach((include) => { - deDupe[include] = true; - }); - - query.include = Object.keys(deDupe).join(','); - } - } - - return query; - }, - - 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 - model.eachRelationship(function (name, meta) { - if (meta.kind === 'hasMany' && - Object.prototype.hasOwnProperty.call(meta.options, 'embedded') && - meta.options.embedded === 'always') { - ret.push(name); - } - }); - - return ret; - } -}); diff --git a/core/client/app/adapters/setting.js b/core/client/app/adapters/setting.js deleted file mode 100644 index ccc266e8858a..000000000000 --- a/core/client/app/adapters/setting.js +++ /dev/null @@ -1,19 +0,0 @@ -import ApplicationAdapter from 'ghost/adapters/application'; - -export default ApplicationAdapter.extend({ - 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; - - // use the SettingSerializer to transform the model back into - // an array of settings objects like the API expects - serializer.serializeIntoHash(data, type, record); - - // use the ApplicationAdapter's buildURL method but do not - // pass in an id. - return this.ajax(this.buildURL(type.modelName), 'PUT', {data}); - } -}); diff --git a/core/client/app/adapters/tag.js b/core/client/app/adapters/tag.js deleted file mode 100644 index 5dc33f7e8950..000000000000 --- a/core/client/app/adapters/tag.js +++ /dev/null @@ -1,4 +0,0 @@ -import ApplicationAdapter from 'ghost/adapters/application'; -import SlugUrl from 'ghost/mixins/slug-url'; - -export default ApplicationAdapter.extend(SlugUrl); diff --git a/core/client/app/adapters/user.js b/core/client/app/adapters/user.js deleted file mode 100644 index 578f737cc6ca..000000000000 --- a/core/client/app/adapters/user.js +++ /dev/null @@ -1,23 +0,0 @@ -import ApplicationAdapter from 'ghost/adapters/application'; -import SlugUrl from 'ghost/mixins/slug-url'; - -export default ApplicationAdapter.extend(SlugUrl, { - 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(store, type, id, snapshot) { - let url = this.buildIncludeURL(store, type.modelName, id, snapshot, 'findRecord'); - - url += '&status=all'; - - return this.ajax(url, 'GET'); - }, - - 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 deleted file mode 100755 index ed5c21167723..000000000000 --- a/core/client/app/app.js +++ /dev/null @@ -1,20 +0,0 @@ -import Ember from 'ember'; -import Resolver from './resolver'; -import loadInitializers from 'ember-load-initializers'; -import 'ghost/utils/link-component'; -import 'ghost/utils/text-field'; -import config from './config/environment'; - -const {Application} = Ember; - -Ember.MODEL_FACTORY_INJECTIONS = true; - -let App = Application.extend({ - Resolver, - modulePrefix: config.modulePrefix, - podModulePrefix: config.podModulePrefix -}); - -loadInitializers(App, config.modulePrefix); - -export default App; diff --git a/core/client/app/authenticators/oauth2.js b/core/client/app/authenticators/oauth2.js deleted file mode 100644 index b5380973b016..000000000000 --- a/core/client/app/authenticators/oauth2.js +++ /dev/null @@ -1,28 +0,0 @@ -import Ember from 'ember'; -import Authenticator from 'ember-simple-auth/authenticators/oauth2-password-grant'; - -const { - computed, - inject: {service} -} = Ember; - -export default Authenticator.extend({ - config: service(), - ghostPaths: service(), - - serverTokenEndpoint: computed('ghostPaths.apiRoot', function () { - return `${this.get('ghostPaths.apiRoot')}/authentication/token`; - }), - - serverTokenRevocationEndpoint: computed('ghostPaths.apiRoot', function () { - return `${this.get('ghostPaths.apiRoot')}/authentication/revoke`; - }), - - 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/authorizers/oauth2.js b/core/client/app/authorizers/oauth2.js deleted file mode 100644 index cdd9c12760eb..000000000000 --- a/core/client/app/authorizers/oauth2.js +++ /dev/null @@ -1,3 +0,0 @@ -import Oauth2Bearer from 'ember-simple-auth/authorizers/oauth2-bearer'; - -export default Oauth2Bearer; diff --git a/core/client/app/components/gh-activating-list-item.js b/core/client/app/components/gh-activating-list-item.js deleted file mode 100644 index 06fbec9b747c..000000000000 --- a/core/client/app/components/gh-activating-list-item.js +++ /dev/null @@ -1,22 +0,0 @@ -import Ember from 'ember'; - -const {Component, run} = Ember; - -export default Component.extend({ - tagName: 'li', - classNameBindings: ['active'], - active: false, - linkClasses: null, - - click() { - this.$('a').blur(); - }, - - actions: { - 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 deleted file mode 100644 index 02419bf62297..000000000000 --- a/core/client/app/components/gh-alert.js +++ /dev/null @@ -1,40 +0,0 @@ -import Ember from 'ember'; - -const { - Component, - computed, - inject: {service} -} = Ember; - -export default Component.extend({ - tagName: 'article', - classNames: ['gh-alert'], - classNameBindings: ['typeClass'], - - notifications: service(), - - typeClass: computed('message.type', function () { - let type = this.get('message.type'); - let classes = ''; - let typeMapping; - - typeMapping = { - success: 'green', - error: 'red', - warn: 'yellow', - info: 'blue' - }; - - if (typeMapping[type] !== undefined) { - classes += `gh-alert-${typeMapping[type]}`; - } - - return classes; - }), - - actions: { - 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 deleted file mode 100644 index 179318dc2114..000000000000 --- a/core/client/app/components/gh-alerts.js +++ /dev/null @@ -1,22 +0,0 @@ -import Ember from 'ember'; - -const { - Component, - computed, - inject: {service}, - observer -} = Ember; -const {alias} = computed; - -export default Component.extend({ - tagName: 'aside', - classNames: 'gh-alerts', - - notifications: service(), - - messages: alias('notifications.alerts'), - - 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 deleted file mode 100644 index 0fa2af8b833c..000000000000 --- a/core/client/app/components/gh-app.js +++ /dev/null @@ -1,15 +0,0 @@ -import Ember from 'ember'; - -const {Component, observer} = Ember; - -export default Component.extend({ - classNames: ['gh-app'], - - showSettingsMenu: false, - - 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 deleted file mode 100644 index 285d2cb398dc..000000000000 --- a/core/client/app/components/gh-blog-url.js +++ /dev/null @@ -1,12 +0,0 @@ -import Ember from 'ember'; - -const { - Component, - inject: {service} -} = Ember; - -export default Component.extend({ - tagName: '', - - config: service() -}); diff --git a/core/client/app/components/gh-cm-editor.js b/core/client/app/components/gh-cm-editor.js deleted file mode 100644 index 785ba4aa1363..000000000000 --- a/core/client/app/components/gh-cm-editor.js +++ /dev/null @@ -1,47 +0,0 @@ -/* global CodeMirror */ -import Ember from 'ember'; - -const {Component} = Ember; - -export default Component.extend({ - classNameBindings: ['isFocused:focused'], - - value: '', // make sure a value exists - isFocused: false, - - // options for the editor - lineNumbers: true, - indentUnit: 4, - mode: 'htmlmixed', - theme: 'xq-light', - - _editor: null, // reference to CodeMirror editor - - didInsertElement() { - this._super(...arguments); - - let options = this.getProperties('lineNumbers', 'indentUnit', 'mode', 'theme'); - let editor = new CodeMirror(this.get('element'), options); - - editor.getDoc().setValue(this.get('value')); - - // events - editor.on('focus', Ember.run.bind(this, 'set', 'isFocused', true)); - editor.on('blur', Ember.run.bind(this, 'set', 'isFocused', false)); - editor.on('change', () => { - Ember.run(this, function () { - this.set('value', editor.getDoc().getValue()); - }); - }); - - this._editor = editor; - }, - - willDestroyElement() { - this._super(...arguments); - - 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 deleted file mode 100644 index 05ec3b4a1aae..000000000000 --- a/core/client/app/components/gh-content-cover.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - -Implements a div for covering the page content -when in a menu context that, for example, -should be closed when the user clicks elsewhere. - -Example: -``` -{{gh-content-cover onClick="closeMenus" onMouseEnter="closeAutoNav"}} -``` -**/ - -import Ember from 'ember'; - -const {Component} = Ember; - -export default Component.extend({ - classNames: ['content-cover'], - - onClick: null, - onMouseEnter: null, - - click() { - this.sendAction('onClick'); - }, - - 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 deleted file mode 100644 index 873a78ec62c0..000000000000 --- a/core/client/app/components/gh-content-preview-content.js +++ /dev/null @@ -1,22 +0,0 @@ -import Ember from 'ember'; - -const {Component} = Ember; - -export default Component.extend({ - classNames: ['content-preview-content'], - - content: null, - - didReceiveAttrs(options) { - this._super(...arguments); - - // adjust when didReceiveAttrs gets both newAttrs and oldAttrs - if (options.newAttrs.content && this.get('content') !== options.newAttrs.content.value) { - let el = this.$(); - - if (el) { - el.closest('.content-preview').scrollTop(0); - } - } - } -}); diff --git a/core/client/app/components/gh-content-view-container.js b/core/client/app/components/gh-content-view-container.js deleted file mode 100644 index eff63ba31bf4..000000000000 --- a/core/client/app/components/gh-content-view-container.js +++ /dev/null @@ -1,15 +0,0 @@ -import Ember from 'ember'; - -const { - Component, - computed, - inject: {service} -} = Ember; - -export default Component.extend({ - tagName: 'section', - classNames: ['gh-view', 'content-view-container'], - - mediaQueries: service(), - previewIsHidden: computed.reads('mediaQueries.maxWidth900') -}); diff --git a/core/client/app/components/gh-datetime-input.js b/core/client/app/components/gh-datetime-input.js deleted file mode 100644 index de4e34d502a7..000000000000 --- a/core/client/app/components/gh-datetime-input.js +++ /dev/null @@ -1,33 +0,0 @@ -import Ember from 'ember'; -import TextInputMixin from 'ghost/mixins/text-input'; -import boundOneWay from 'ghost/utils/bound-one-way'; -import {formatDate} from 'ghost/utils/date-formatting'; -import {invokeAction} from 'ember-invoke-action'; - -const {Component} = Ember; - -export default Component.extend(TextInputMixin, { - tagName: 'span', - classNames: 'input-icon icon-calendar', - - datetime: boundOneWay('value'), - inputClass: null, - inputId: null, - inputName: null, - - didReceiveAttrs() { - let datetime = this.get('datetime') || moment(); - - if (!this.get('update')) { - throw new Error(`You must provide an \`update\` action to \`{{${this.templateName}}}\`.`); - } - - this.set('datetime', formatDate(datetime)); - }, - - focusOut() { - let datetime = this.get('datetime'); - - invokeAction(this, 'update', datetime); - } -}); diff --git a/core/client/app/components/gh-dropdown-button.js b/core/client/app/components/gh-dropdown-button.js deleted file mode 100644 index a9429be827af..000000000000 --- a/core/client/app/components/gh-dropdown-button.js +++ /dev/null @@ -1,24 +0,0 @@ -import Ember from 'ember'; -import DropdownMixin from 'ghost/mixins/dropdown-mixin'; - -const { - Component, - inject: {service} -} = Ember; - -export default Component.extend(DropdownMixin, { - tagName: 'button', - attributeBindings: 'role', - role: 'button', - - // matches with the dropdown this button toggles - dropdownName: null, - - dropdown: service(), - - // Notify dropdown service this dropdown should be toggled - 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 deleted file mode 100644 index b7f01a589fdf..000000000000 --- a/core/client/app/components/gh-dropdown.js +++ /dev/null @@ -1,99 +0,0 @@ -import Ember from 'ember'; -import DropdownMixin from 'ghost/mixins/dropdown-mixin'; - -const { - Component, - computed, - inject: {service} -} = Ember; - -export default Component.extend(DropdownMixin, { - classNames: 'dropdown', - classNameBindings: ['fadeIn:fade-in-scale:fade-out', 'isOpen:open:closed'], - - name: null, - closeOnClick: false, - - // Helps track the user re-opening the menu while it's fading out. - closing: false, - - // Helps track whether the dropdown is open or closes, or in a transition to either - isOpen: false, - - // Managed the toggle between the fade-in and fade-out classes - fadeIn: computed('isOpen', 'closing', function () { - return this.get('isOpen') && !this.get('closing'); - }), - - dropdown: service(), - - open() { - this.set('isOpen', true); - this.set('closing', false); - this.set('button.isOpen', true); - }, - - close() { - this.set('closing', true); - - if (this.get('button')) { - this.set('button.isOpen', false); - } - - this.$().on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', (event) => { - if (event.originalEvent.animationName === 'fade-out') { - Ember.run(this, function () { - if (this.get('closing')) { - this.set('isOpen', false); - this.set('closing', false); - } - }); - } - }); - }, - - // Called by the dropdown service when any dropdown button is clicked. - 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) { - button = options.button; - this.set('button', button); - } - this.open(); - } else if (isOpen) { - this.close(); - } - }, - - click(event) { - this._super(event); - - if (this.get('closeOnClick')) { - return this.close(); - } - }, - - didInsertElement() { - let dropdownService = this.get('dropdown'); - - this._super(...arguments); - - dropdownService.on('close', this, this.close); - dropdownService.on('toggle', this, this.toggle); - }, - - willDestroyElement() { - let 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 deleted file mode 100644 index a927458bc1fd..000000000000 --- a/core/client/app/components/gh-ed-editor.js +++ /dev/null @@ -1,52 +0,0 @@ -import Ember from 'ember'; -import EditorAPI from 'ghost/mixins/ed-editor-api'; -import EditorShortcuts from 'ghost/mixins/ed-editor-shortcuts'; -import EditorScroll from 'ghost/mixins/ed-editor-scroll'; -import {invokeAction} from 'ember-invoke-action'; - -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() { - this.sendAction('onFocusIn'); - }, - - /** - * Sets the focus of the textarea if needed - */ - setFocus() { - if (this.get('focus')) { - this.$().val(this.$().val()).focus(); - } - }, - - /** - * Sets up properties at render time - */ - didInsertElement() { - this._super(...arguments); - - this.setFocus(); - - invokeAction(this, 'setEditor', this); - - run.scheduleOnce('afterRender', this, this.afterRenderEvent); - }, - - afterRenderEvent() { - if (this.get('focus') && this.get('focusCursorAtEnd')) { - this.setSelection('end'); - } - }, - - actions: { - toggleCopyHTMLModal(generatedHTML) { - invokeAction(this, 'toggleCopyHTMLModal', generatedHTML); - } - } -}); diff --git a/core/client/app/components/gh-ed-preview.js b/core/client/app/components/gh-ed-preview.js deleted file mode 100644 index f28d47d198a6..000000000000 --- a/core/client/app/components/gh-ed-preview.js +++ /dev/null @@ -1,110 +0,0 @@ -import Ember from 'ember'; -import {formatMarkdown} from 'ghost/helpers/gh-format-markdown'; - -const { - Component, - run, - uuid -} = Ember; - -export default Component.extend({ - _scrollWrapper: null, - - previewHTML: '', - - init() { - this._super(...arguments); - this.set('imageUploadComponents', Ember.A([])); - this.buildPreviewHTML(); - }, - - didInsertElement() { - this._super(...arguments); - this._scrollWrapper = this.$().closest('.entry-preview-content'); - this.adjustScrollPosition(this.get('scrollPosition')); - }, - - didReceiveAttrs(attrs) { - this._super(...arguments); - - 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) { - run.throttle(this, this.buildPreviewHTML, 30, false); - } - }, - - adjustScrollPosition(scrollPosition) { - let scrollWrapper = this._scrollWrapper; - - if (scrollWrapper) { - scrollWrapper.scrollTop(scrollPosition); - } - }, - - buildPreviewHTML() { - let markdown = this.get('markdown'); - let html = formatMarkdown([markdown]).string; - let template = document.createElement('template'); - template.innerHTML = html; - let fragment = template.content; - let dropzones = fragment.querySelectorAll('.js-drop-zone'); - let components = this.get('imageUploadComponents'); - - if (dropzones.length !== components.length) { - components = Ember.A([]); - this.set('imageUploadComponents', components); - } - - [...dropzones].forEach((oldEl, i) => { - let el = oldEl.cloneNode(true); - let component = components[i]; - let uploadTarget = el.querySelector('.js-upload-target'); - let id = uuid(); - let destinationElementId = `image-uploader-${id}`; - let src; - - if (uploadTarget) { - src = uploadTarget.getAttribute('src'); - } - - if (component) { - component.set('destinationElementId', destinationElementId); - component.set('src', src); - } else { - let imageUpload = Ember.Object.create({ - destinationElementId, - id, - src, - index: i - }); - - this.get('imageUploadComponents').pushObject(imageUpload); - } - - el.id = destinationElementId; - el.innerHTML = ''; - el.classList.remove('image-uploader'); - - oldEl.parentNode.replaceChild(el, oldEl); - }); - - this.set('previewHTML', fragment); - }, - - actions: { - updateImageSrc(index, url) { - this.attrs.updateImageSrc(index, url); - }, - - updateHeight() { - this.attrs.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 deleted file mode 100644 index 9b04b131ebe2..000000000000 --- a/core/client/app/components/gh-editor-save-button.js +++ /dev/null @@ -1,50 +0,0 @@ -import Ember from 'ember'; - -const {Component, computed} = Ember; - -export default Component.extend({ - tagName: 'section', - classNames: ['splitbtn', 'js-publish-splitbutton'], - classNameBindings: ['isNew:unsaved'], - - isNew: null, - isPublished: null, - willPublish: null, - postOrPage: null, - submitting: false, - - // Tracks whether we're going to change the state of the post on save - isDangerous: computed('isPublished', 'willPublish', function () { - return this.get('isPublished') !== this.get('willPublish'); - }), - - publishText: computed('isPublished', 'postOrPage', function () { - return this.get('isPublished') ? `Update ${this.get('postOrPage')}` : 'Publish Now'; - }), - - draftText: computed('isPublished', function () { - return this.get('isPublished') ? 'Unpublish' : 'Save Draft'; - }), - - deleteText: computed('postOrPage', function () { - return `Delete ${this.get('postOrPage')}`; - }), - - saveText: computed('willPublish', 'publishText', 'draftText', function () { - return this.get('willPublish') ? this.get('publishText') : this.get('draftText'); - }), - - actions: { - save() { - this.sendAction('save'); - }, - - setSaveType(saveType) { - this.sendAction('setSaveType', saveType); - }, - - delete() { - this.sendAction('delete'); - } - } -}); diff --git a/core/client/app/components/gh-editor.js b/core/client/app/components/gh-editor.js deleted file mode 100644 index 5b0fec6988ff..000000000000 --- a/core/client/app/components/gh-editor.js +++ /dev/null @@ -1,123 +0,0 @@ -import Ember from 'ember'; -import ShortcutsMixin from 'ghost/mixins/shortcuts'; -import imageManager from 'ghost/utils/ed-image-manager'; -import editorShortcuts from 'ghost/utils/editor-shortcuts'; -import {invokeAction} from 'ember-invoke-action'; - -const {Component, computed, run} = Ember; -const {equal} = computed; - -export default Component.extend(ShortcutsMixin, { - tagName: 'section', - classNames: ['view-container', 'view-editor'], - - activeTab: 'markdown', - editor: null, - editorDisabled: undefined, - editorScrollInfo: null, // updated when gh-ed-editor component scrolls - height: null, // updated when markdown is rendered - shouldFocusEditor: false, - showCopyHTMLModal: false, - copyHTMLModalContent: null, - - shortcuts: editorShortcuts, - - 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: computed('editorScrollInfo', 'height', function () { - let scrollInfo = this.get('editorScrollInfo'); - let {$previewContent, $previewViewPort} = this; - - if (!scrollInfo || !$previewContent || !$previewViewPort) { - return 0; - } - - let previewHeight = $previewContent.height() - $previewViewPort.height(); - let previewPosition, ratio; - - ratio = previewHeight / scrollInfo.diff; - previewPosition = scrollInfo.top * ratio; - - return previewPosition; - }), - - didInsertElement() { - this._super(...arguments); - this.registerShortcuts(); - run.scheduleOnce('afterRender', this, this._cacheElements); - }, - - willDestroyElement() { - invokeAction(this, 'onTeardown'); - - this.removeShortcuts(); - }, - - _cacheElements() { - // cache these elements for use in other methods - this.$previewViewPort = this.$('.js-entry-preview-content'); - this.$previewContent = this.$('.js-rendered-markdown'); - }, - - actions: { - selectTab(tab) { - this.set('activeTab', tab); - }, - - updateScrollInfo(scrollInfo) { - this.set('editorScrollInfo', scrollInfo); - }, - - updateHeight(height) { - this.set('height', height); - }, - - // set from a `sendAction` on the gh-ed-editor component, - // so that we get a reference for handling uploads. - setEditor(editor) { - this.set('editor', editor); - }, - - disableEditor() { - this.set('editorDisabled', true); - }, - - enableEditor() { - this.set('editorDisabled', undefined); - }, - - // The actual functionality is implemented in utils/ed-editor-shortcuts - editorShortcut(options) { - if (this.editor.$().is(':focus')) { - this.editor.shortcut(options.type); - } - }, - - // Match the uploaded file to a line in the editor, and update that line with a path reference - // ensuring that everything ends up in the correct place and format. - handleImgUpload(imageIndex, newSrc) { - let editor = this.get('editor'); - let editorValue = editor.getValue(); - let replacement = imageManager.getSrcRange(editorValue, imageIndex); - let cursorPosition; - - if (replacement) { - cursorPosition = replacement.start + newSrc.length + 1; - if (replacement.needsParens) { - newSrc = `(${newSrc})`; - } - editor.replaceSelection(newSrc, replacement.start, replacement.end, cursorPosition); - } - }, - - toggleCopyHTMLModal(generatedHTML) { - this.set('copyHTMLModalContent', generatedHTML); - this.toggleProperty('showCopyHTMLModal'); - } - } -}); diff --git a/core/client/app/components/gh-error-message.js b/core/client/app/components/gh-error-message.js deleted file mode 100644 index 281fe3c226b1..000000000000 --- a/core/client/app/components/gh-error-message.js +++ /dev/null @@ -1,36 +0,0 @@ -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 - * that specific property. If there are no errors associated with the property, - * nothing will be rendered. - * @param {DS.Errors} errors The DS.Errors object - * @param {string} property The property name - */ -export default Component.extend({ - tagName: 'p', - classNames: ['response'], - - errors: null, - property: '', - - isVisible: computed.notEmpty('errors'), - - message: computed('errors.[]', 'property', function () { - let property = this.get('property'); - let errors = this.get('errors'); - let messages = []; - let index; - - if (!isEmpty(errors) && errors.get(property)) { - errors.get(property).forEach((error) => { - messages.push(error); - }); - index = Math.floor(Math.random() * messages.length); - return messages[index].message; - } - }) -}); diff --git a/core/client/app/components/gh-feature-flag.js b/core/client/app/components/gh-feature-flag.js deleted file mode 100644 index e744e0f943cb..000000000000 --- a/core/client/app/components/gh-feature-flag.js +++ /dev/null @@ -1,45 +0,0 @@ -import Ember from 'ember'; - -const { - computed, - inject: {service}, - Component -} = Ember; - -const FeatureFlagComponent = Component.extend({ - tagName: 'label', - classNames: 'checkbox', - attributeBindings: ['for'], - _flagValue: null, - - feature: service(), - - init() { - this._super(...arguments); - - this.set('_flagValue', this.get(`feature.${this.get('flag')}`)); - }, - - value: computed('_flagValue', { - get() { - return this.get('_flagValue'); - }, - set(key, value) { - return this.set(`feature.${this.get('flag')}`, value); - } - }), - - for: computed('flag', function () { - return `labs-${this.get('flag')}`; - }), - - name: computed('flag', function () { - return `labs[${this.get('flag')}]`; - }) -}); - -FeatureFlagComponent.reopenClass({ - positionalParams: ['flag'] -}); - -export default FeatureFlagComponent; diff --git a/core/client/app/components/gh-file-upload.js b/core/client/app/components/gh-file-upload.js deleted file mode 100644 index e65b8817fdd1..000000000000 --- a/core/client/app/components/gh-file-upload.js +++ /dev/null @@ -1,37 +0,0 @@ -import Ember from 'ember'; - -const {Component} = Ember; - -export default Component.extend({ - _file: null, - - uploadButtonText: 'Text', - uploadButtonDisabled: true, - - onUpload: null, - onAdd: null, - - shouldResetForm: true, - - change(event) { - this.set('uploadButtonDisabled', false); - this.sendAction('onAdd'); - this._file = event.target.files[0]; - }, - - actions: { - upload() { - if (!this.get('uploadButtonDisabled') && this._file) { - this.sendAction('onUpload', this._file); - } - - // Prevent double post by disabling the button. - this.set('uploadButtonDisabled', true); - - // Reset form - if (this.get('shouldResetForm')) { - this.$().closest('form')[0].reset(); - } - } - } -}); diff --git a/core/client/app/components/gh-file-uploader.js b/core/client/app/components/gh-file-uploader.js deleted file mode 100644 index f17feb3e5b46..000000000000 --- a/core/client/app/components/gh-file-uploader.js +++ /dev/null @@ -1,161 +0,0 @@ -import Ember from 'ember'; -import { invoke, invokeAction } from 'ember-invoke-action'; -import { - RequestEntityTooLargeError, - UnsupportedMediaTypeError -} from 'ghost/services/ajax'; - -const { - Component, - computed, - inject: {service}, - isBlank, - run -} = Ember; - -export default Component.extend({ - tagName: 'section', - classNames: ['gh-image-uploader'], - classNameBindings: ['dragClass'], - - labelText: 'Select or drag-and-drop a file', - url: null, - paramName: 'file', - - file: null, - response: null, - - dragClass: null, - failureMessage: null, - uploadPercentage: 0, - - ajax: service(), - - formData: computed('file', function () { - let paramName = this.get('paramName'); - let file = this.get('file'); - let formData = new FormData(); - - formData.append(paramName, file); - - return formData; - }), - - progressStyle: computed('uploadPercentage', function () { - let percentage = this.get('uploadPercentage'); - let width = ''; - - if (percentage > 0) { - width = `${percentage}%`; - } else { - width = '0'; - } - - return Ember.String.htmlSafe(`width: ${width}`); - }), - - dragOver(event) { - if (!event.dataTransfer) { - return; - } - - // this is needed to work around inconsistencies with dropping files - // from Chrome's downloads bar - let eA = event.dataTransfer.effectAllowed; - event.dataTransfer.dropEffect = (eA === 'move' || eA === 'linkMove') ? 'move' : 'copy'; - - event.stopPropagation(); - event.preventDefault(); - - this.set('dragClass', '--drag-over'); - }, - - dragLeave(event) { - event.preventDefault(); - this.set('dragClass', null); - }, - - drop(event) { - event.preventDefault(); - this.set('dragClass', null); - if (event.dataTransfer.files) { - invoke(this, 'fileSelected', event.dataTransfer.files); - } - }, - - generateRequest() { - let ajax = this.get('ajax'); - let formData = this.get('formData'); - let url = this.get('url'); - - invokeAction(this, 'uploadStarted'); - - ajax.post(url, { - data: formData, - processData: false, - contentType: false, - dataType: 'text', - xhr: () => { - let xhr = new window.XMLHttpRequest(); - - xhr.upload.addEventListener('progress', (event) => { - this._uploadProgress(event); - }, false); - - return xhr; - } - }).then((response) => { - this._uploadSuccess(JSON.parse(response)); - }).catch((error) => { - this._uploadFailed(error); - }).finally(() => { - invokeAction(this, 'uploadFinished'); - }); - }, - - _uploadProgress(event) { - if (event.lengthComputable) { - run(() => { - let percentage = Math.round((event.loaded / event.total) * 100); - this.set('uploadPercentage', percentage); - }); - } - }, - - _uploadSuccess(response) { - invokeAction(this, 'uploadSuccess', response); - invoke(this, 'reset'); - }, - - _uploadFailed(error) { - let message; - - if (error instanceof UnsupportedMediaTypeError) { - message = 'The file type you uploaded is not supported.'; - } else if (error instanceof RequestEntityTooLargeError) { - message = 'The file you uploaded was larger than the maximum file size your server allows.'; - } else if (error.errors && !isBlank(error.errors[0].message)) { - message = error.errors[0].message; - } else { - message = 'Something went wrong :('; - } - - this.set('failureMessage', message); - invokeAction(this, 'uploadFailed', error); - }, - - actions: { - fileSelected(fileList) { - this.set('file', fileList[0]); - run.schedule('actions', this, function () { - this.generateRequest(); - }); - }, - - reset() { - this.set('file', null); - this.set('uploadPercentage', 0); - this.set('failureMessage', null); - } - } -}); diff --git a/core/client/app/components/gh-form-group.js b/core/client/app/components/gh-form-group.js deleted file mode 100644 index 5445314cc583..000000000000 --- a/core/client/app/components/gh-form-group.js +++ /dev/null @@ -1,5 +0,0 @@ -import ValidationStatusContainer from 'ghost/components/gh-validation-status-container'; - -export default ValidationStatusContainer.extend({ - classNames: 'form-group' -}); diff --git a/core/client/app/components/gh-fullscreen-modal.js b/core/client/app/components/gh-fullscreen-modal.js deleted file mode 100644 index 21740a1538d3..000000000000 --- a/core/client/app/components/gh-fullscreen-modal.js +++ /dev/null @@ -1,85 +0,0 @@ -import Ember from 'ember'; -import LiquidTether from 'liquid-tether/components/liquid-tether'; -import {invokeAction} from 'ember-invoke-action'; - -const { - RSVP: {Promise}, - inject: {service}, - isBlank, - on, - run -} = Ember; -const emberA = Ember.A; - -const FullScreenModalComponent = LiquidTether.extend({ - to: 'fullscreen-modal', - target: 'document.body', - targetModifier: 'visible', - targetAttachment: 'top center', - attachment: 'top center', - tetherClass: 'fullscreen-modal', - overlayClass: 'fullscreen-modal-background', - modalPath: 'unknown', - - dropdown: service(), - - init() { - this._super(...arguments); - this.modalPath = `modals/${this.get('modal')}`; - }, - - setTetherClass: on('init', function () { - let tetherClass = this.get('tetherClass'); - let modifiers = (this.get('modifier') || '').split(' '); - let tetherClasses = emberA([tetherClass]); - - modifiers.forEach((modifier) => { - if (!isBlank(modifier)) { - let className = `${tetherClass}-${modifier}`; - tetherClasses.push(className); - } - }); - - this.set('tetherClass', tetherClasses.join(' ')); - }), - - closeDropdowns: on('didInsertElement', function () { - run.schedule('afterRender', this, function () { - this.get('dropdown').closeDropdowns(); - }); - }), - - actions: { - close() { - // Because we return the promise from invokeAction, we have - // to check if "close" exists first - if (this.get('close')) { - return invokeAction(this, 'close'); - } - - return new Promise((resolve) => { - resolve(); - }); - }, - - confirm() { - if (this.get('confirm')) { - return invokeAction(this, 'confirm'); - } - - return new Promise((resolve) => { - resolve(); - }); - }, - - clickOverlay() { - this.send('close'); - } - } -}); - -FullScreenModalComponent.reopenClass({ - positionalParams: ['modal'] -}); - -export default FullScreenModalComponent; diff --git a/core/client/app/components/gh-image-uploader-with-preview.js b/core/client/app/components/gh-image-uploader-with-preview.js deleted file mode 100644 index aba65c3261ad..000000000000 --- a/core/client/app/components/gh-image-uploader-with-preview.js +++ /dev/null @@ -1,39 +0,0 @@ -import Ember from 'ember'; - -const { - Component -} = Ember; - -export default Component.extend({ - actions: { - update() { - if (typeof this.attrs.update === 'function') { - this.attrs.update(...arguments); - } - }, - - onInput() { - if (typeof this.attrs.onInput === 'function') { - this.attrs.onInput(...arguments); - } - }, - - uploadStarted() { - if (typeof this.attrs.uploadStarted === 'function') { - this.attrs.uploadStarted(...arguments); - } - }, - - uploadFinished() { - if (typeof this.attrs.uploadFinished === 'function') { - this.attrs.uploadFinished(...arguments); - } - }, - - formChanged() { - if (typeof this.attrs.formChanged === 'function') { - this.attrs.formChanged(...arguments); - } - } - } -}); diff --git a/core/client/app/components/gh-image-uploader.js b/core/client/app/components/gh-image-uploader.js deleted file mode 100644 index 1141b38fc484..000000000000 --- a/core/client/app/components/gh-image-uploader.js +++ /dev/null @@ -1,226 +0,0 @@ -import Ember from 'ember'; -import ghostPaths from 'ghost/utils/ghost-paths'; -import {RequestEntityTooLargeError, UnsupportedMediaTypeError} from 'ghost/services/ajax'; - -const { - Component, - computed, - inject: {service}, - isBlank, - run -} = Ember; - -export default Component.extend({ - tagName: 'section', - classNames: ['gh-image-uploader'], - classNameBindings: ['dragClass'], - - image: null, - text: 'Upload an image', - saveButton: true, - - dragClass: null, - failureMessage: null, - file: null, - formType: 'upload', - url: null, - uploadPercentage: 0, - - ajax: service(), - config: service(), - - // TODO: this wouldn't be necessary if the server could accept direct - // file uploads - formData: computed('file', function () { - let file = this.get('file'); - let formData = new FormData(); - - formData.append('uploadimage', file); - - return formData; - }), - - progressStyle: computed('uploadPercentage', function () { - let percentage = this.get('uploadPercentage'); - let width = ''; - - if (percentage > 0) { - width = `${percentage}%`; - } else { - width = '0'; - } - - return Ember.String.htmlSafe(`width: ${width}`); - }), - - canShowUploadForm: computed('config.fileStorage', function () { - return this.get('config.fileStorage') !== false; - }), - - showUploadForm: computed('formType', function () { - let canShowUploadForm = this.get('canShowUploadForm'); - let formType = this.get('formType'); - - return formType === 'upload' && canShowUploadForm; - }), - - didReceiveAttrs() { - let image = this.get('image'); - this.set('url', image); - }, - - dragOver(event) { - let showUploadForm = this.get('showUploadForm'); - - if (!event.dataTransfer) { - return; - } - - // this is needed to work around inconsistencies with dropping files - // from Chrome's downloads bar - let eA = event.dataTransfer.effectAllowed; - event.dataTransfer.dropEffect = (eA === 'move' || eA === 'linkMove') ? 'move' : 'copy'; - - event.stopPropagation(); - event.preventDefault(); - - if (showUploadForm) { - this.set('dragClass', '--drag-over'); - } - }, - - dragLeave(event) { - let showUploadForm = this.get('showUploadForm'); - - event.preventDefault(); - - if (showUploadForm) { - this.set('dragClass', null); - } - }, - - drop(event) { - let showUploadForm = this.get('showUploadForm'); - - event.preventDefault(); - - this.set('dragClass', null); - - if (showUploadForm) { - if (event.dataTransfer.files) { - this.send('fileSelected', event.dataTransfer.files); - } - } - }, - - uploadStarted() { - if (typeof this.attrs.uploadStarted === 'function') { - this.attrs.uploadStarted(); - } - }, - - uploadProgress(event) { - if (event.lengthComputable) { - run(() => { - let percentage = Math.round((event.loaded / event.total) * 100); - this.set('uploadPercentage', percentage); - }); - } - }, - - uploadFinished() { - if (typeof this.attrs.uploadFinished === 'function') { - this.attrs.uploadFinished(); - } - }, - - uploadSuccess(response) { - this.set('url', response); - this.send('saveUrl'); - this.send('reset'); - }, - - uploadFailed(error) { - let message; - - if (error instanceof UnsupportedMediaTypeError) { - message = 'The image type you uploaded is not supported. Please use .PNG, .JPG, .GIF, .SVG.'; - } else if (error instanceof RequestEntityTooLargeError) { - message = 'The image you uploaded was larger than the maximum file size your server allows.'; - } else if (error.errors && !isBlank(error.errors[0].message)) { - message = error.errors[0].message; - } else { - message = 'Something went wrong :('; - } - - this.set('failureMessage', message); - }, - - generateRequest() { - let ajax = this.get('ajax'); - let formData = this.get('formData'); - let url = `${ghostPaths().apiRoot}/uploads/`; - - this.uploadStarted(); - - ajax.post(url, { - data: formData, - processData: false, - contentType: false, - dataType: 'text', - xhr: () => { - let xhr = new window.XMLHttpRequest(); - - xhr.upload.addEventListener('progress', (event) => { - this.uploadProgress(event); - }, false); - - return xhr; - } - }).then((response) => { - let url = JSON.parse(response); - this.uploadSuccess(url); - }).catch((error) => { - this.uploadFailed(error); - }).finally(() => { - this.uploadFinished(); - }); - }, - - actions: { - fileSelected(fileList) { - this.set('file', fileList[0]); - run.schedule('actions', this, function () { - this.generateRequest(); - }); - }, - - onInput(url) { - this.set('url', url); - - if (typeof this.attrs.onInput === 'function') { - this.attrs.onInput(url); - } - }, - - reset() { - this.set('file', null); - this.set('uploadPercentage', 0); - }, - - switchForm(formType) { - this.set('formType', formType); - - if (typeof this.attrs.formChanged === 'function') { - run.scheduleOnce('afterRender', this, function () { - this.attrs.formChanged(formType); - }); - } - }, - - saveUrl() { - let url = this.get('url'); - this.attrs.update(url); - } - } -}); diff --git a/core/client/app/components/gh-infinite-scroll.js b/core/client/app/components/gh-infinite-scroll.js deleted file mode 100644 index 56746ba1f3f8..000000000000 --- a/core/client/app/components/gh-infinite-scroll.js +++ /dev/null @@ -1,12 +0,0 @@ -import Ember from 'ember'; -import InfiniteScrollMixin from 'ghost/mixins/infinite-scroll'; - -const {Component} = Ember; - -export default Component.extend(InfiniteScrollMixin, { - actions: { - checkScroll() { - this._checkScroll(); - } - } -}); diff --git a/core/client/app/components/gh-input.js b/core/client/app/components/gh-input.js deleted file mode 100644 index 43d8ef131e25..000000000000 --- a/core/client/app/components/gh-input.js +++ /dev/null @@ -1,8 +0,0 @@ -import Ember from 'ember'; -import TextInputMixin from 'ghost/mixins/text-input'; - -const {TextField} = Ember; - -export default TextField.extend(TextInputMixin, { - classNames: 'gh-input' -}); diff --git a/core/client/app/components/gh-light-table.js b/core/client/app/components/gh-light-table.js deleted file mode 100644 index 010f311ae265..000000000000 --- a/core/client/app/components/gh-light-table.js +++ /dev/null @@ -1,27 +0,0 @@ -import Ember from 'ember'; -import LightTable from 'ember-light-table/components/light-table'; - -const {$, run} = Ember; - -export default LightTable.extend({ - - // HACK: infinite pagination was not triggering when scrolling very fast - // as the throttle triggers before scrolling into the buffer area but - // the scroll finishes before the throttle timeout. Adding a debounce that - // does the same thing means that we are guaranteed a final trigger when - // scrolling stops - // - // An issue has been opened upstream, this can be removed if it gets fixed - // https://github.com/offirgolan/ember-light-table/issues/15 - - _setupScrollEvents() { - $(this.get('touchMoveContainer')).on('touchmove.light-table', run.bind(this, this._scrollHandler, '_touchmoveTimer')); - $(this.get('scrollContainer')).on('scroll.light-table', run.bind(this, this._scrollHandler, '_scrollTimer')); - $(this.get('scrollContainer')).on('scroll.light-table', run.bind(this, this._scrollHandler, '_scrollDebounce')); - }, - - _scrollHandler(timer) { - this.set(timer, run.debounce(this, this._onScroll, 100)); - this.set(timer, run.throttle(this, this._onScroll, 100)); - } -}); diff --git a/core/client/app/components/gh-main.js b/core/client/app/components/gh-main.js deleted file mode 100644 index e4bd8916df7f..000000000000 --- a/core/client/app/components/gh-main.js +++ /dev/null @@ -1,13 +0,0 @@ -import Ember from 'ember'; - -const {Component} = Ember; - -export default Component.extend({ - tagName: 'main', - classNames: ['gh-main'], - ariaRole: 'main', - - mouseEnter() { - this.sendAction('onMouseEnter'); - } -}); diff --git a/core/client/app/components/gh-menu-toggle.js b/core/client/app/components/gh-menu-toggle.js deleted file mode 100644 index 8e5ba3ad9eb7..000000000000 --- a/core/client/app/components/gh-menu-toggle.js +++ /dev/null @@ -1,42 +0,0 @@ -import Ember from 'ember'; - -const { - Component, - computed, - inject: {service} -} = 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: service(), - isMobile: computed.reads('mediaQueries.isMobile'), - maximise: false, - - iconClass: computed('maximise', 'isMobile', function () { - if (this.get('maximise') && !this.get('isMobile')) { - return 'icon-maximise'; - } else { - return 'icon-minimise'; - } - }), - - click() { - if (this.get('isMobile')) { - this.sendAction('mobileAction'); - } else { - this.toggleProperty('maximise'); - this.sendAction('desktopAction'); - } - } -}); diff --git a/core/client/app/components/gh-nav-menu.js b/core/client/app/components/gh-nav-menu.js deleted file mode 100644 index b3098d167f25..000000000000 --- a/core/client/app/components/gh-nav-menu.js +++ /dev/null @@ -1,48 +0,0 @@ -import Ember from 'ember'; - -const { - Component, - inject: {service}, - computed -} = Ember; - -export default Component.extend({ - tagName: 'nav', - classNames: ['gh-nav'], - classNameBindings: ['open'], - - open: false, - - navMenuIcon: computed('ghostPaths.subdir', function () { - let url = `${this.get('ghostPaths.subdir')}/ghost/img/ghosticon.jpg`; - - return Ember.String.htmlSafe(`background-image: url(${url})`); - }), - - config: service(), - session: service(), - ghostPaths: service(), - feature: service(), - - mouseEnter() { - this.sendAction('onMouseEnter'); - }, - - actions: { - toggleAutoNav() { - this.sendAction('toggleMaximise'); - }, - - showMarkdownHelp() { - this.sendAction('showMarkdownHelp'); - }, - - closeMobileMenu() { - this.sendAction('closeMobileMenu'); - }, - - openAutoNav() { - this.sendAction('openAutoNav'); - } - } -}); diff --git a/core/client/app/components/gh-navigation.js b/core/client/app/components/gh-navigation.js deleted file mode 100644 index 53fedb3d1f86..000000000000 --- a/core/client/app/components/gh-navigation.js +++ /dev/null @@ -1,39 +0,0 @@ -import Ember from 'ember'; - -const {Component, run} = Ember; - -export default Component.extend({ - tagName: 'section', - classNames: 'gh-view', - - didInsertElement() { - let navContainer = this.$('.js-gh-blognav'); - let navElements = '.gh-blognav-item:not(.gh-blognav-item:last-child)'; - // needed because jqueryui sortable doesn't trigger babel's autoscoping - let _this = this; - - this._super(...arguments); - - navContainer.sortable({ - handle: '.gh-blognav-grab', - items: navElements, - - start(event, ui) { - run(() => { - ui.item.data('start-index', ui.item.index()); - }); - }, - - update(event, ui) { - run(() => { - _this.sendAction('moveItem', ui.item.data('start-index'), ui.item.index()); - }); - } - }); - }, - - willDestroyElement() { - this._super(...arguments); - 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 deleted file mode 100644 index df1928c6e0e0..000000000000 --- a/core/client/app/components/gh-navitem-url-input.js +++ /dev/null @@ -1,145 +0,0 @@ -import Ember from 'ember'; -import {invokeAction} from 'ember-invoke-action'; - -const {TextField, computed, run} = Ember; - -let joinUrlParts = function (url, path) { - if (path[0] !== '/' && url.slice(-1) !== '/') { - path = `/${path}`; - } else if (path[0] === '/' && url.slice(-1) === '/') { - path = path.slice(1); - } - - return url + path; -}; - -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 TextField.extend({ - classNames: 'gh-input', - - isBaseUrl: computed('baseUrl', 'value', function () { - return this.get('baseUrl') === this.get('value'); - }), - - didReceiveAttrs() { - this._super(...arguments); - - 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)) { - url = joinUrlParts(baseUrl, url); - } - - this.set('value', url); - }, - - focusIn(event) { - this.set('hasFocus', true); - - if (this.get('isBaseUrl')) { - // position the cursor at the end of the input - run.next(function (el) { - let {length} = el.value; - - el.setSelectionRange(length, length); - }, event.target); - } - }, - - keyDown(event) { - // delete the "placeholder" value all at once - if (this.get('isBaseUrl') && (event.keyCode === 8 || event.keyCode === 46)) { - this.set('value', ''); - - event.preventDefault(); - } - - // CMD-S - if (event.keyCode === 83 && event.metaKey) { - this.notifyUrlChanged(); - } - }, - - keyPress(event) { - invokeAction(this, 'clearErrors'); - - // enter key - if (event.keyCode === 13) { - this.notifyUrlChanged(); - } - - return true; - }, - - focusOut() { - this.set('hasFocus', false); - - this.notifyUrlChanged(); - }, - - notifyUrlChanged() { - let url = this.get('value').trim(); - let urlParts = document.createElement('a'); - let baseUrl = this.get('baseUrl'); - let baseUrlParts = document.createElement('a'); - - // ensure value property is trimmed - this.set('value', url); - - // leverage the browser's native URI parsing - urlParts.href = url; - baseUrlParts.href = baseUrl; - - // if we have an email address, add the mailto: - if (validator.isEmail(url)) { - url = `mailto:${url}`; - this.set('value', url); - } - - // if we have a relative url, create the absolute url to be displayed in the input - if (isRelative(url)) { - url = joinUrlParts(baseUrl, url); - this.set('value', url); - } - - // get our baseUrl relativity checks in order - let isOnSameHost = urlParts.host === baseUrlParts.host; - let isAnchorLink = url.match(/^#/); - let isRelativeToBasePath = urlParts.pathname.indexOf(baseUrlParts.pathname) === 0; - - // if our pathname is only missing a trailing / mark it as relative - if (`${urlParts.pathname}/` === baseUrlParts.pathname) { - isRelativeToBasePath = true; - } - - // if relative to baseUrl, remove the base url before sending to action - if (!isAnchorLink && isOnSameHost && isRelativeToBasePath) { - url = url.replace(/^[a-zA-Z0-9\-]+:/, ''); - url = url.replace(/^\/\//, ''); - url = url.replace(baseUrlParts.host, ''); - url = url.replace(baseUrlParts.pathname, ''); - - // handle case where url path is same as baseUrl path but missing trailing slash - if (urlParts.pathname.slice(-1) !== '/') { - url = url.replace(baseUrlParts.pathname.slice(0, -1), ''); - } - - if (!url.match(/^\//)) { - url = `/${url}`; - } - - if (!url.match(/\/$/) && !url.match(/[\.#\?]/)) { - url = `${url}/`; - } - } - - this.sendAction('change', url); - } -}); diff --git a/core/client/app/components/gh-navitem.js b/core/client/app/components/gh-navitem.js deleted file mode 100644 index 341ea1a9da98..000000000000 --- a/core/client/app/components/gh-navitem.js +++ /dev/null @@ -1,55 +0,0 @@ -import Ember from 'ember'; -import ValidationState from 'ghost/mixins/validation-state'; -import SortableItem from 'ember-sortable/mixins/sortable-item'; - -const {Component, computed, run} = Ember; -const {alias, readOnly} = computed; - -export default Component.extend(ValidationState, SortableItem, { - classNames: 'gh-blognav-item', - classNameBindings: ['errorClass', 'navItem.isNew::gh-blognav-item--sortable'], - - new: false, - handle: '.gh-blognav-grab', - - model: alias('navItem'), - errors: readOnly('navItem.errors'), - - errorClass: computed('hasError', function () { - if (this.get('hasError')) { - return 'gh-blognav-item--error'; - } - }), - - keyPress(event) { - // enter key - if (event.keyCode === 13 && this.get('navItem.isNew')) { - event.preventDefault(); - run.scheduleOnce('actions', this, function () { - this.send('addItem'); - }); - } - }, - - actions: { - addItem() { - this.sendAction('addItem'); - }, - - deleteItem(item) { - this.sendAction('deleteItem', item); - }, - - updateUrl(value) { - this.sendAction('updateUrl', value, this.get('navItem')); - }, - - clearLabelErrors() { - this.get('navItem.errors').remove('label'); - }, - - clearUrlErrors() { - this.get('navItem.errors').remove('url'); - } - } -}); diff --git a/core/client/app/components/gh-notification.js b/core/client/app/components/gh-notification.js deleted file mode 100644 index 27787e8ad9a9..000000000000 --- a/core/client/app/components/gh-notification.js +++ /dev/null @@ -1,56 +0,0 @@ -import Ember from 'ember'; - -const { - Component, - computed, - inject: {service} -} = Ember; - -export default Component.extend({ - tagName: 'article', - classNames: ['gh-notification', 'gh-notification-passive'], - classNameBindings: ['typeClass'], - - message: null, - - notifications: service(), - - typeClass: computed('message.type', function () { - let type = this.get('message.type'); - let classes = ''; - let typeMapping; - - typeMapping = { - success: 'green', - error: 'red', - warn: 'yellow' - }; - - if (typeMapping[type] !== undefined) { - classes += `gh-notification-${typeMapping[type]}`; - } - - return classes; - }), - - didInsertElement() { - this._super(...arguments); - - this.$().on('animationend webkitAnimationEnd oanimationend MSAnimationEnd', (event) => { - if (event.originalEvent.animationName === 'fade-out') { - this.get('notifications').closeNotification(this.get('message')); - } - }); - }, - - willDestroyElement() { - this._super(...arguments); - this.$().off('animationend webkitAnimationEnd oanimationend MSAnimationEnd'); - }, - - actions: { - 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 deleted file mode 100644 index a293bbcf35dd..000000000000 --- a/core/client/app/components/gh-notifications.js +++ /dev/null @@ -1,17 +0,0 @@ -import Ember from 'ember'; - -const { - Component, - computed, - inject: {service} -} = Ember; -const {alias} = computed; - -export default Component.extend({ - tagName: 'aside', - classNames: 'gh-notifications', - - notifications: service(), - - messages: alias('notifications.notifications') -}); diff --git a/core/client/app/components/gh-popover-button.js b/core/client/app/components/gh-popover-button.js deleted file mode 100644 index ac852680ffe3..000000000000 --- a/core/client/app/components/gh-popover-button.js +++ /dev/null @@ -1,26 +0,0 @@ -import Ember from 'ember'; -import DropdownButton from 'ghost/components/gh-dropdown-button'; - -const { - inject: {service} -} = Ember; - -function K() { - return this; -} - -export default DropdownButton.extend({ - dropdown: service(), - - click: K, - - mouseEnter() { - this._super(...arguments); - this.get('dropdown').toggleDropdown(this.get('popoverName'), this); - }, - - 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 deleted file mode 100644 index c724003f0bc8..000000000000 --- a/core/client/app/components/gh-popover.js +++ /dev/null @@ -1,11 +0,0 @@ -import Ember from 'ember'; -import GhostDropdown from 'ghost/components/gh-dropdown'; - -const { - inject: {service} -} = Ember; - -export default GhostDropdown.extend({ - classNames: 'ghost-popover', - dropdown: service() -}); diff --git a/core/client/app/components/gh-posts-list-item.js b/core/client/app/components/gh-posts-list-item.js deleted file mode 100644 index 51c92c81c070..000000000000 --- a/core/client/app/components/gh-posts-list-item.js +++ /dev/null @@ -1,85 +0,0 @@ -import Ember from 'ember'; -import ActiveLinkWrapper from 'ghost/mixins/active-link-wrapper'; -import {invokeAction} from 'ember-invoke-action'; - -const { - $, - Component, - computed, - inject: {service} -} = Ember; -const {alias, equal} = computed; - -export default Component.extend(ActiveLinkWrapper, { - tagName: 'li', - classNameBindings: ['isFeatured:featured', 'isPage:page'], - - post: null, - previewIsHidden: false, - - isFeatured: alias('post.featured'), - isPage: alias('post.page'), - isPublished: equal('post.status', 'published'), - - ghostPaths: service(), - - authorName: computed('post.author.name', 'post.author.email', function () { - return this.get('post.author.name') || this.get('post.author.email'); - }), - - authorAvatar: computed('post.author.image', function () { - return this.get('post.author.image') || `${this.get('ghostPaths.subdir')}/ghost/img/user-image.png`; - }), - - authorAvatarBackground: computed('authorAvatar', function () { - return Ember.String.htmlSafe(`background-image: url(${this.get('authorAvatar')})`); - }), - - click() { - this.sendAction('onClick', this.get('post')); - }, - - doubleClick() { - this.sendAction('onDoubleClick', this.get('post')); - }, - - didInsertElement() { - this._super(...arguments); - this.addObserver('active', this, this.scrollIntoView); - }, - - willDestroyElement() { - this._super(...arguments); - this.removeObserver('active', this, this.scrollIntoView); - if (this.get('post.isDeleted') && this.get('onDelete')) { - invokeAction(this, 'onDelete'); - } - }, - - scrollIntoView() { - if (!this.get('active')) { - return; - } - - 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; - - isOnScreen = isBelowTop && isAboveBottom; - - if (!isOnScreen) { - // Scroll so that element is centered in container - // 40 is the amount of padding on the container - container.clearQueue().animate({ - scrollTop: currentScroll + offset - 40 - containerHeight / 2 - }); - } - } -}); diff --git a/core/client/app/components/gh-profile-image.js b/core/client/app/components/gh-profile-image.js deleted file mode 100644 index 00f038e8bb32..000000000000 --- a/core/client/app/components/gh-profile-image.js +++ /dev/null @@ -1,142 +0,0 @@ -import Ember from 'ember'; -import AjaxService from 'ember-ajax/services/ajax'; -import {NotFoundError} from 'ghost/services/ajax'; - -const { - Component, - computed, - inject: {service}, - isBlank, - 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 - * - * Example: {{gh-profile-image email=controllerEmailProperty setImage="controllerActionName" debounce=500}} - * - * @param {int} size The size of the image to render - * @param {String} email Reference to a bound email object if gravatar image behavior is desired. - * @param {String|action} setImage The string name of the action on the controller to be called when an image is added. - * @param {int} debounce Period to wait after changes to email before attempting to load gravatar - * @property {Boolean} hasUploadedImage Whether or not the user has uploaded an image (whether or not to show the default image/gravatar image) - * @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 Component.extend({ - email: '', - size: 90, - debounce: 300, - - validEmail: '', - hasUploadedImage: false, - fileStorage: true, - ajax: AjaxService.create(), - config: service(), - - ghostPaths: service(), - displayGravatar: notEmpty('validEmail'), - - init() { - this._super(...arguments); - // Fire this immediately in case we're initialized with a valid email - this.trySetValidEmail(); - }, - - defaultImage: computed('ghostPaths', function () { - let url = `${this.get('ghostPaths.subdir')}/ghost/img/user-image.png`; - return Ember.String.htmlSafe(`background-image: url(${url})`); - }), - - trySetValidEmail() { - if (!this.get('isDestroyed')) { - let email = this.get('email'); - this.set('validEmail', validator.isEmail(email) ? email : ''); - } - }, - - didReceiveAttrs(attrs) { - this._super(...arguments); - let timeout = parseInt(attrs.newAttrs.throttle || this.get('debounce')); - run.debounce(this, 'trySetValidEmail', timeout); - }, - - imageBackground: computed('validEmail', 'size', function () { - let email = this.get('validEmail'); - let size = this.get('size'); - let style = ''; - - if (!isBlank(email)) { - let gravatarUrl = `//www.gravatar.com/avatar/${window.md5(email)}?s=${size}&d=404`; - - this.get('ajax').request(gravatarUrl) - .catch((error) => { - let defaultImageUrl = `url("${this.get('ghostPaths.subdir')}/ghost/img/user-image.png")`; - - if (error instanceof NotFoundError) { - this.$('.placeholder-img')[0].style.backgroundImage = Ember.String.htmlSafe(defaultImageUrl); - } else { - this.$('.placeholder-img')[0].style.backgroundImage = 'url()'; - } - }); - - style = `background-image: url(${gravatarUrl})`; - } - return Ember.String.htmlSafe(style); - }), - - didInsertElement() { - let size = this.get('size'); - let uploadElement = this.$('.js-file-input'); - - this._super(...arguments); - - // while theoretically the 'add' and 'processalways' functions could be - // added as properties of the hash passed to fileupload(), for some reason - // they needed to be placed in an on() call for the add method to work correctly - uploadElement.fileupload({ - url: this.get('ghostPaths.url').api('uploads'), - dropZone: this.$('.js-img-dropzone'), - previewMaxHeight: size, - previewMaxWidth: size, - previewCrop: true, - maxNumberOfFiles: 1, - autoUpload: false - }) - .on('fileuploadadd', run.bind(this, this.queueFile)) - .on('fileuploadprocessalways', run.bind(this, this.triggerPreview)); - }, - - willDestroyElement() { - let $input = this.$('.js-file-input'); - - this._super(...arguments); - - if ($input.length && $input.data()['blueimp-fileupload']) { - $input.fileupload('destroy'); - } - }, - - queueFile(e, data) { - let fileName = data.files[0].name; - - if ((/\.(gif|jpe?g|png|svg?z)$/i).test(fileName)) { - this.sendAction('setImage', data); - } - }, - - 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 - // potential todo: rename 'gravatar-img' class in the CSS to be something - // that both the gravatar and the image preview can use that's not so confusing - this.$('.js-img-preview').empty().append(this.$(file.preview).addClass('gravatar-img')); - } - } -}); diff --git a/core/client/app/components/gh-search-input.js b/core/client/app/components/gh-search-input.js deleted file mode 100644 index 93372cd32fd2..000000000000 --- a/core/client/app/components/gh-search-input.js +++ /dev/null @@ -1,206 +0,0 @@ -/* global key */ -/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */ -import Ember from 'ember'; - -const { - Component, - RSVP, - computed, - run, - inject: {service}, - isBlank, - isEmpty -} = Ember; - -export function computedGroup(category) { - return computed('content', 'currentSearch', function () { - if (!this.get('currentSearch') || !this.get('content')) { - return []; - } - - return this.get('content').filter((item) => { - let search = new RegExp(this.get('currentSearch'), 'ig'); - - return (item.category === category) && - item.title.match(search); - }); - }); -} - -export default Component.extend({ - - selection: null, - content: [], - isLoading: false, - contentExpiry: 10 * 1000, - contentExpiresAt: false, - currentSearch: '', - - posts: computedGroup('Posts'), - pages: computedGroup('Pages'), - users: computedGroup('Users'), - tags: computedGroup('Tags'), - - _store: service('store'), - _routing: service('-routing'), - ajax: service(), - - refreshContent() { - let promises = []; - let now = new Date(); - let contentExpiry = this.get('contentExpiry'); - let contentExpiresAt = this.get('contentExpiresAt'); - - if (this.get('isLoading') || contentExpiresAt > now) { - return RSVP.resolve(); - } - - this.set('isLoading', true); - this.set('content', []); - promises.pushObject(this._loadPosts()); - promises.pushObject(this._loadUsers()); - promises.pushObject(this._loadTags()); - - return RSVP.all(promises).then(() => { }).finally(() => { - this.set('isLoading', false); - this.set('contentExpiresAt', new Date(now.getTime() + contentExpiry)); - }); - }, - - groupedContent: computed('posts', 'pages', 'users', 'tags', function () { - let groups = []; - - if (!isEmpty(this.get('posts'))) { - groups.pushObject({groupName: 'Posts', options: this.get('posts')}); - } - - if (!isEmpty(this.get('pages'))) { - groups.pushObject({groupName: 'Pages', options: this.get('pages')}); - } - - if (!isEmpty(this.get('users'))) { - groups.pushObject({groupName: 'Users', options: this.get('users')}); - } - - if (!isEmpty(this.get('tags'))) { - groups.pushObject({groupName: 'Tags', options: this.get('tags')}); - } - - return groups; - }), - - _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 this.get('ajax').request(postsUrl, {data: postsQuery}).then((posts) => { - - content.pushObjects(posts.posts.map((post) => { - return { - id: `post.${post.id}`, - title: post.title, - category: post.page ? 'Pages' : 'Posts' - }; - })); - }); - }, - - _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 this.get('ajax').request(usersUrl, {data: usersQuery}).then((users) => { - content.pushObjects(users.users.map((user) => { - return { - id: `user.${user.slug}`, - title: user.name, - category: 'Users' - }; - })); - }); - }, - - _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 this.get('ajax').request(tagsUrl, {data: tagsQuery}).then((tags) => { - content.pushObjects(tags.tags.map((tag) => { - return { - id: `tag.${tag.slug}`, - title: tag.name, - category: 'Tags' - }; - })); - }); - }, - - _performSearch(term, resolve, reject) { - if (isBlank(term)) { - return resolve([]); - } - - this.refreshContent().then(() => { - this.set('currentSearch', term); - - return resolve(this.get('groupedContent')); - }).catch(reject); - }, - - _setKeymasterScope() { - key.setScope('search-input'); - }, - - _resetKeymasterScope() { - key.setScope('default'); - }, - - willDestroy() { - this._super(...arguments); - this._resetKeymasterScope(); - }, - - actions: { - openSelected(selected) { - if (!selected) { - return; - } - - if (selected.category === 'Posts' || selected.category === 'Pages') { - let id = selected.id.replace('post.', ''); - this.get('_routing.router').transitionTo('editor.edit', id); - } - - if (selected.category === 'Users') { - let id = selected.id.replace('user.', ''); - this.get('_routing.router').transitionTo('team.user', id); - } - - if (selected.category === 'Tags') { - let id = selected.id.replace('tag.', ''); - this.get('_routing.router').transitionTo('settings.tags.tag', id); - } - }, - - onFocus() { - this._setKeymasterScope(); - }, - - onBlur() { - this._resetKeymasterScope(); - }, - - search(term) { - return new RSVP.Promise((resolve, reject) => { - run.debounce(this, this._performSearch, term, resolve, reject, 200); - }); - } - } - -}); diff --git a/core/client/app/components/gh-search-input/trigger.js b/core/client/app/components/gh-search-input/trigger.js deleted file mode 100644 index 6559b36d2e57..000000000000 --- a/core/client/app/components/gh-search-input/trigger.js +++ /dev/null @@ -1,43 +0,0 @@ -import Ember from 'ember'; -import {invokeAction} from 'ember-invoke-action'; - -const {run, isBlank, Component} = Ember; - -export default Component.extend({ - open() { - this.get('select.actions').open(); - }, - - close() { - this.get('select.actions').close(); - }, - - actions: { - captureMouseDown(e) { - e.stopPropagation(); - }, - - search(term) { - if (isBlank(term) === this.get('select.isOpen')) { - run.scheduleOnce('afterRender', this, isBlank(term) ? this.close : this.open); - } - - invokeAction(this, 'select.actions.search', term); - }, - - focusInput() { - this.$('input')[0].focus(); - }, - - resetInput() { - this.$('input').val(''); - }, - - handleKeydown(e) { - let select = this.get('select'); - if (!select.isOpen) { - e.stopPropagation(); - } - } - } -}); diff --git a/core/client/app/components/gh-select-native.js b/core/client/app/components/gh-select-native.js deleted file mode 100644 index 64e1af11d601..000000000000 --- a/core/client/app/components/gh-select-native.js +++ /dev/null @@ -1,42 +0,0 @@ -import Ember from 'ember'; - -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: K, // action to fire on change - - // shadow the passed-in `selection` to avoid - // leaking changes to it via a 2-way binding - _selection: reads('selection'), - - actions: { - change() { - // jscs:disable requireArrayDestructuring - let selectEl = this.$('select')[0]; - // jscs:enable requireArrayDestructuring - let {selectedIndex} = selectEl; - - // decrement index by 1 if we have a prompt - let hasPrompt = !!this.get('prompt'); - let contentIndex = hasPrompt ? selectedIndex - 1 : selectedIndex; - - let selection = this.get('content').objectAt(contentIndex); - - // set the local, shadowed selection to avoid leaking - // changes to `selection` out via 2-way binding - this.set('_selection', selection); - - this.sendAction('action', selection); - } - } -}); diff --git a/core/client/app/components/gh-selectize.js b/core/client/app/components/gh-selectize.js deleted file mode 100644 index 3f3cfa0b5e48..000000000000 --- a/core/client/app/components/gh-selectize.js +++ /dev/null @@ -1,124 +0,0 @@ -/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */ -import Ember from 'ember'; -import EmberSelectizeComponent from 'ember-cli-selectize/components/ember-selectize'; - -const {computed, isArray, isBlank, get, run} = Ember; -const emberA = Ember.A; - -export default EmberSelectizeComponent.extend({ - - selectizeOptions: computed(function () { - let options = this._super(...arguments); - - options.onChange = run.bind(this, '_onChange'); - - return options; - }), - - /** - * Event callback that is triggered when user creates a tag - * - modified to pass the caret position to the action - */ - _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 - 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 - // in the content and selection property - callback(null); - }, - - _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 - // to the right - caret = caret - 1; - - this.get('selection').insertAt(caret, obj); - - run.schedule('actions', this, function () { - this.sendAction('add-item', obj); - this.sendAction('add-value', val); - }); - }, - - _onChange(args) { - let selection = Ember.get(this, 'selection'); - let valuePath = Ember.get(this, '_valuePath'); - let reorderedSelection = emberA([]); - - if (!args || !selection || !isArray(selection) || args.length !== get(selection, 'length')) { - return; - } - - // exit if we're not dealing with the same objects as the selection - let objectsHaveChanged = selection.any(function (obj) { - return args.indexOf(get(obj, valuePath)) === -1; - }); - - if (objectsHaveChanged) { - return; - } - - // exit if the order is still the same - let orderIsSame = selection.every(function (obj, idx) { - return get(obj, valuePath) === args[idx]; - }); - - if (orderIsSame) { - return; - } - - // we have a re-order, update the selection - args.forEach((value) => { - let obj = selection.find(function (item) { - return `${get(item, valuePath)}` === value; - }); - - if (obj) { - reorderedSelection.addObject(obj); - } - }); - - this.set('selection', reorderedSelection); - }, - - _preventOpeningWhenBlank() { - let openOnFocus = this.get('openOnFocus'); - - if (!openOnFocus) { - run.schedule('afterRender', this, function () { - let selectize = this._selectize; - if (selectize) { - selectize.on('dropdown_open', function () { - if (isBlank(selectize.$control_input.val())) { - selectize.close(); - } - }); - selectize.on('type', function (filter) { - if (isBlank(filter)) { - selectize.close(); - } - }); - } - }); - } - }, - - didInsertElement() { - this._super(...arguments); - this._preventOpeningWhenBlank(); - } - -}); diff --git a/core/client/app/components/gh-skip-link.js b/core/client/app/components/gh-skip-link.js deleted file mode 100644 index 960ba08b8155..000000000000 --- a/core/client/app/components/gh-skip-link.js +++ /dev/null @@ -1,36 +0,0 @@ -/*jshint scripturl:true*/ -import Ember from 'ember'; - -const {$, Component} = Ember; - -export default Component.extend({ - tagName: 'a', - anchor: '', - classNames: ['sr-only', 'sr-only-focusable'], - // Add attributes to component for href - // href should be set to retain anchor properties - // such as pointer cursor and text underline - attributeBindings: ['href'], - // Used so that upon clicking on the link - // anchor behaviors or ignored - href: Ember.String.htmlSafe('javascript:;'), - - click() { - let anchor = this.get('anchor'); - let $el = Ember.$(anchor); - - if ($el) { - // Scrolls to the top of main content or whatever - // is passed to the anchor attribute - Ember.$('body').scrollTop($el.offset().top); - - // This sets focus on the content which was skipped to - // upon losing focus, the tabindex should be removed - // so that normal keyboard navigation picks up from focused - // element - Ember.$($el).attr('tabindex', -1).on('blur focusout', function () { - $(this).removeAttr('tabindex'); - }).focus(); - } - } -}); diff --git a/core/client/app/components/gh-spin-button.js b/core/client/app/components/gh-spin-button.js deleted file mode 100644 index 39e203586ba9..000000000000 --- a/core/client/app/components/gh-spin-button.js +++ /dev/null @@ -1,59 +0,0 @@ -import Ember from 'ember'; - -const {Component, computed, observer, run} = Ember; -const {equal} = computed; - -export default Component.extend({ - tagName: 'button', - buttonText: '', - submitting: false, - showSpinner: false, - showSpinnerTimeout: null, - autoWidth: true, - - // Disable Button when isLoading equals true - attributeBindings: ['disabled', 'type', 'tabindex'], - - // Must be set on the controller - disabled: equal('showSpinner', true), - - click() { - if (this.get('action')) { - this.sendAction('action'); - return false; - } - return true; - }, - - toggleSpinner: observer('submitting', function () { - let submitting = this.get('submitting'); - let timeout = this.get('showSpinnerTimeout'); - - if (submitting) { - this.set('showSpinner', true); - this.set('showSpinnerTimeout', run.later(this, function () { - if (!this.get('submitting')) { - this.set('showSpinner', false); - } - this.set('showSpinnerTimeout', null); - }, 1000)); - } else if (!submitting && timeout === null) { - this.set('showSpinner', false); - } - }), - - setSize: observer('showSpinner', function () { - if (this.get('showSpinner') && this.get('autoWidth')) { - this.$().width(this.$().width()); - this.$().height(this.$().height()); - } else { - this.$().width(''); - this.$().height(''); - } - }), - - willDestroy() { - this._super(...arguments); - run.cancel(this.get('showSpinnerTimeout')); - } -}); diff --git a/core/client/app/components/gh-subscribers-table.js b/core/client/app/components/gh-subscribers-table.js deleted file mode 100644 index cde011adff25..000000000000 --- a/core/client/app/components/gh-subscribers-table.js +++ /dev/null @@ -1,17 +0,0 @@ -import Ember from 'ember'; - -export default Ember.Component.extend({ - classNames: ['subscribers-table'], - - table: null, - - actions: { - onScrolledToBottom() { - let loadNextPage = this.get('loadNextPage'); - - if (!this.get('isLoading')) { - loadNextPage(); - } - } - } -}); diff --git a/core/client/app/components/gh-tab-pane.js b/core/client/app/components/gh-tab-pane.js deleted file mode 100644 index 63faec69ac28..000000000000 --- a/core/client/app/components/gh-tab-pane.js +++ /dev/null @@ -1,34 +0,0 @@ -import Ember from 'ember'; - -const {Component, computed} = Ember; -const {alias} = computed; - -// See gh-tabs-manager.js for use -export default Component.extend({ - classNameBindings: ['active'], - - tabsManager: computed(function () { - return this.nearestWithProperty('isTabsManager'); - }), - - 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: alias('tab.active'), - - willRender() { - this._super(...arguments); - // Register with the tabs manager - this.get('tabsManager').registerTabPane(this); - }, - - willDestroyElement() { - this._super(...arguments); - // 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 deleted file mode 100644 index efd6c7927991..000000000000 --- a/core/client/app/components/gh-tab.js +++ /dev/null @@ -1,35 +0,0 @@ -import Ember from 'ember'; - -const {Component, computed} = Ember; - -// See gh-tabs-manager.js for use -export default Component.extend({ - tabsManager: computed(function () { - return this.nearestWithProperty('isTabsManager'); - }), - - active: computed('tabsManager.activeTab', function () { - return this.get('tabsManager.activeTab') === this; - }), - - index: computed('tabsManager.tabs.[]', function () { - return this.get('tabsManager.tabs').indexOf(this); - }), - - // Select on click - click() { - this.get('tabsManager').select(this); - }, - - willRender() { - this._super(...arguments); - // register the tabs with the tab manager - this.get('tabsManager').registerTab(this); - }, - - willDestroyElement() { - this._super(...arguments); - // 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 deleted file mode 100644 index 32c023cd42ed..000000000000 --- a/core/client/app/components/gh-tabs-manager.js +++ /dev/null @@ -1,85 +0,0 @@ -import Ember from 'ember'; - -const {Component} = Ember; - -/** -Heavily inspired by ic-tabs (https://github.com/instructure/ic-tabs) - -Three components work together for smooth tabbing. -1. tabs-manager (gh-tabs) -2. tab (gh-tab) -3. tab-pane (gh-tab-pane) - -## Usage: -The tabs-manager must wrap all tab and tab-pane components, -but they can be nested at any level. - -A tab and its pane are tied together via their order. -So, the second tab within a tab manager will activate -the second pane within that manager. - -```hbs -{{#gh-tabs-manager}} - {{#gh-tab}} - First tab - {{/gh-tab}} - {{#gh-tab}} - Second tab - {{/gh-tab}} - - .... - {{#gh-tab-pane}} - First pane - {{/gh-tab-pane}} - {{#gh-tab-pane}} - Second pane - {{/gh-tab-pane}} -{{/gh-tabs-manager}} -``` -## Options: - -the tabs-manager will send a "selected" action whenever one of its -tabs is clicked. -```hbs -{{#gh-tabs-manager selected="myAction"}} - .... -{{/gh-tabs-manager}} -``` - -## Styling: -Both tab and tab-pane elements have an "active" -class applied when they are active. - -*/ -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(tab) { - this.set('activeTab', tab); - this.sendAction('selected'); - }, - - // Register tabs and their panes to allow for - // interaction between components. - registerTab(tab) { - this.get('tabs').addObject(tab); - }, - - unregisterTab(tab) { - this.get('tabs').removeObject(tab); - }, - - registerTabPane(tabPane) { - this.get('tabPanes').addObject(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 deleted file mode 100644 index b892cf3140f0..000000000000 --- a/core/client/app/components/gh-tag-settings-form.js +++ /dev/null @@ -1,136 +0,0 @@ -/* global key */ -import Ember from 'ember'; -import boundOneWay from 'ghost/utils/bound-one-way'; -import {invokeAction} from 'ember-invoke-action'; - -const { - Component, - Handlebars, - computed, - get, - inject: {service} -} = Ember; -const {reads} = computed; - -export default Component.extend({ - - tag: null, - - scratchName: boundOneWay('tag.name'), - scratchSlug: boundOneWay('tag.slug'), - scratchDescription: boundOneWay('tag.description'), - scratchMetaTitle: boundOneWay('tag.metaTitle'), - scratchMetaDescription: boundOneWay('tag.metaDescription'), - - isViewingSubview: false, - - config: service(), - mediaQueries: service(), - - isMobile: reads('mediaQueries.maxWidth600'), - - title: computed('tag.isNew', function () { - if (this.get('tag.isNew')) { - return 'New Tag'; - } else { - return 'Tag Settings'; - } - }), - - 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 = Handlebars.Utils.escapeExpression(metaTitle); - metaTitle = Ember.String.htmlSafe(`${metaTitle}…`); - } - - return metaTitle; - }), - - seoURL: computed('scratchSlug', function () { - let blogUrl = this.get('config.blogUrl'); - let seoSlug = this.get('scratchSlug') || ''; - - let seoURL = `${blogUrl}/tag/${seoSlug}`; - - // only append a slash to the URL if the slug exists - if (seoSlug) { - seoURL += '/'; - } - - if (seoURL.length > 70) { - seoURL = seoURL.substring(0, 70).trim(); - seoURL = Ember.String.htmlSafe(`${seoURL}…`); - } - - return seoURL; - }), - - 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 = Handlebars.Utils.escapeExpression(metaDescription); - metaDescription = Ember.String.htmlSafe(`${metaDescription}…`); - } - - return metaDescription; - }), - - didReceiveAttrs(attrs) { - this._super(...arguments); - - if (get(attrs, 'newAttrs.tag.value.id') !== get(attrs, 'oldAttrs.tag.value.id')) { - this.reset(); - } - }, - - reset() { - this.set('isViewingSubview', false); - if (this.$()) { - this.$('.settings-menu-pane').scrollTop(0); - } - }, - - focusIn() { - key.setScope('tag-settings-form'); - }, - - focusOut() { - key.setScope('default'); - }, - - actions: { - setProperty(property, value) { - invokeAction(this, 'setProperty', property, value); - }, - - setCoverImage(image) { - invokeAction(this, 'setProperty', 'image', image); - }, - - clearCoverImage() { - invokeAction(this, 'setProperty', 'image', ''); - }, - - openMeta() { - this.set('isViewingSubview', true); - }, - - closeMeta() { - this.set('isViewingSubview', false); - }, - - deleteTag() { - invokeAction(this, 'showDeleteTagModal'); - } - } - -}); diff --git a/core/client/app/components/gh-tag.js b/core/client/app/components/gh-tag.js deleted file mode 100644 index f4692ac4a66c..000000000000 --- a/core/client/app/components/gh-tag.js +++ /dev/null @@ -1,12 +0,0 @@ -import Ember from 'ember'; -import {invokeAction} from 'ember-invoke-action'; - -export default Ember.Component.extend({ - willDestroyElement() { - this._super(...arguments); - - if (this.get('tag.isDeleted') && this.get('onDelete')) { - invokeAction(this, 'onDelete'); - } - } -}); diff --git a/core/client/app/components/gh-tags-management-container.js b/core/client/app/components/gh-tags-management-container.js deleted file mode 100644 index 4be8a86127df..000000000000 --- a/core/client/app/components/gh-tags-management-container.js +++ /dev/null @@ -1,54 +0,0 @@ -import Ember from 'ember'; - -const { - Component, - computed, - inject: {service}, - isBlank, - observer, - run -} = Ember; -const {equal, reads} = computed; - -export default Component.extend({ - classNames: ['view-container'], - classNameBindings: ['isMobile'], - - mediaQueries: service(), - - tags: null, - selectedTag: null, - - isMobile: reads('mediaQueries.maxWidth600'), - isEmpty: equal('tags.length', 0), - - init() { - this._super(...arguments); - run.schedule('actions', this, this.fireMobileChangeActions); - }, - - 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) { - return true; - } - - // display list if no tag is selected on mobile - if (isMobile && isBlank(selectedTag)) { - return false; - } - - // default to displaying settings pane - return true; - }), - - 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 deleted file mode 100644 index e668dd220f0e..000000000000 --- a/core/client/app/components/gh-textarea.js +++ /dev/null @@ -1,8 +0,0 @@ -import Ember from 'ember'; -import TextInputMixin from 'ghost/mixins/text-input'; - -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 deleted file mode 100644 index 14edd47123e4..000000000000 --- a/core/client/app/components/gh-trim-focus-input.js +++ /dev/null @@ -1,41 +0,0 @@ -/*global device*/ -import Ember from 'ember'; - -const {TextField, computed} = Ember; - -export default TextField.extend({ - focus: true, - classNames: 'gh-input', - attributeBindings: ['autofocus'], - - autofocus: computed(function () { - if (this.get('focus')) { - return (device.ios()) ? false : 'autofocus'; - } - - return false; - }), - - _focusField() { - // This fix is required until Mobile Safari has reliable - // autofocus, select() or focus() support - if (this.get('focus') && !device.ios()) { - this.$().val(this.$().val()).focus(); - } - }, - - _trimValue() { - let text = this.$().val(); - this.$().val(text.trim()); - }, - - didInsertElement() { - this._super(...arguments); - this._focusField(); - }, - - focusOut() { - this._super(...arguments); - this._trimValue(); - } -}); diff --git a/core/client/app/components/gh-url-preview.js b/core/client/app/components/gh-url-preview.js deleted file mode 100644 index 48ded92e39de..000000000000 --- a/core/client/app/components/gh-url-preview.js +++ /dev/null @@ -1,35 +0,0 @@ -import Ember from 'ember'; - -const { - Component, - computed, - inject: {service} -} = Ember; - -/* -Example usage: -{{gh-url-preview prefix="tag" slug=theSlugValue tagName="p" classNames="description"}} -*/ -export default Component.extend({ - classNames: 'ghost-url-preview', - prefix: null, - slug: null, - - config: service(), - - url: computed('slug', function () { - // Get the blog URL and strip the scheme - let blogUrl = this.get('config.blogUrl'); - // Remove `http[s]://` - let noSchemeBlogUrl = blogUrl.substr(blogUrl.indexOf('://') + 3); - - // 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 - 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 deleted file mode 100644 index 13e7735bdbd3..000000000000 --- a/core/client/app/components/gh-user-active.js +++ /dev/null @@ -1,31 +0,0 @@ -import Ember from 'ember'; - -const { - Component, - computed, - inject: {service} -} = Ember; - -export default Component.extend({ - tagName: '', - - user: null, - - ghostPaths: service(), - - userDefault: computed('ghostPaths', function () { - return `${this.get('ghostPaths.subdir')}/ghost/img/user-image.png`; - }), - - userImageBackground: computed('user.image', 'userDefault', function () { - let url = this.get('user.image') || this.get('userDefault'); - - return Ember.String.htmlSafe(`background-image: url(${url})`); - }), - - lastLogin: computed('user.lastLogin', function () { - let lastLogin = this.get('user.lastLogin'); - - 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 deleted file mode 100644 index 45a89c2ccb0f..000000000000 --- a/core/client/app/components/gh-user-invited.js +++ /dev/null @@ -1,69 +0,0 @@ -import Ember from 'ember'; - -const { - Component, - computed, - inject: {service} -} = Ember; - -export default Component.extend({ - tagName: '', - - user: null, - isSending: false, - - notifications: service(), - - createdAt: computed('user.createdAt', function () { - let createdAt = this.get('user.createdAt'); - - return createdAt ? createdAt.fromNow() : ''; - }), - - actions: { - resend() { - let user = this.get('user'); - let notifications = this.get('notifications'); - - this.set('isSending', true); - 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'. - if (result.users[0].status === 'invited-pending') { - notifications.showAlert('Invitation email was not sent. Please try resending.', {type: 'error', key: 'invite.resend.not-sent'}); - } else { - user.set('status', result.users[0].status); - notifications.showNotification(notificationText, {key: 'invite.resend.success'}); - } - }).catch((error) => { - notifications.showAPIError(error, {key: 'invite.resend'}); - }).finally(() => { - this.set('isSending', false); - }); - }, - - 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(() => { - if (user.get('invited')) { - user.destroyRecord().then(() => { - let notificationText = `Invitation revoked. (${email})`; - notifications.showNotification(notificationText, {key: 'invite.revoke.success'}); - }).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 - 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 deleted file mode 100644 index 5ddfb3ba13b5..000000000000 --- a/core/client/app/components/gh-validation-status-container.js +++ /dev/null @@ -1,26 +0,0 @@ -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 - * the specified property, it will change the CSS to reflect the error state - * @param {DS.Errors} errors The DS.Errors object - * @param {string} property Name of the property - */ -export default Component.extend(ValidationStateMixin, { - classNameBindings: ['errorClass'], - - 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'; - } else { - return ''; - } - }) -}); diff --git a/core/client/app/components/gh-view-title.js b/core/client/app/components/gh-view-title.js deleted file mode 100644 index 0c70b0c9d20a..000000000000 --- a/core/client/app/components/gh-view-title.js +++ /dev/null @@ -1,14 +0,0 @@ -import Ember from 'ember'; - -const {Component} = Ember; - -export default Component.extend({ - tagName: 'h2', - classNames: ['view-title'], - - actions: { - openMobileMenu() { - this.sendAction('openMobileMenu'); - } - } -}); diff --git a/core/client/app/components/modals/base.js b/core/client/app/components/modals/base.js deleted file mode 100644 index b77cdace4cce..000000000000 --- a/core/client/app/components/modals/base.js +++ /dev/null @@ -1,56 +0,0 @@ -/* global key */ -import Ember from 'ember'; -import {invokeAction} from 'ember-invoke-action'; - -const {Component, run} = Ember; - -export default Component.extend({ - tagName: 'section', - classNames: 'modal-content', - - _previousKeymasterScope: null, - - _setupShortcuts() { - run(function () { - document.activeElement.blur(); - }); - this._previousKeymasterScope = key.getScope(); - - key('enter', 'modal', () => { - this.send('confirm'); - }); - - key('escape', 'modal', () => { - this.send('closeModal'); - }); - - key.setScope('modal'); - }, - - _removeShortcuts() { - key.unbind('enter', 'modal'); - key.unbind('escape', 'modal'); - - key.setScope(this._previousKeymasterScope); - }, - - didInsertElement() { - this._super(...arguments); - this._setupShortcuts(); - }, - - willDestroyElement() { - this._super(...arguments); - this._removeShortcuts(); - }, - - actions: { - confirm() { - throw new Error('You must override the "confirm" action in your modal component'); - }, - - closeModal() { - invokeAction(this, 'closeModal'); - } - } -}); diff --git a/core/client/app/components/modals/copy-html.js b/core/client/app/components/modals/copy-html.js deleted file mode 100644 index 078b0b12be9f..000000000000 --- a/core/client/app/components/modals/copy-html.js +++ /dev/null @@ -1,9 +0,0 @@ -import Ember from 'ember'; -import ModalComponent from 'ghost/components/modals/base'; - -const {computed} = Ember; -const {alias} = computed; - -export default ModalComponent.extend({ - generatedHtml: alias('model') -}); diff --git a/core/client/app/components/modals/delete-all.js b/core/client/app/components/modals/delete-all.js deleted file mode 100644 index 11b1748df28e..000000000000 --- a/core/client/app/components/modals/delete-all.js +++ /dev/null @@ -1,49 +0,0 @@ -import Ember from 'ember'; -import ModalComponent from 'ghost/components/modals/base'; - -const { - inject: {service} -} = Ember; - -export default ModalComponent.extend({ - - submitting: false, - - ghostPaths: service(), - notifications: service(), - store: service(), - ajax: service(), - - _deleteAll() { - let deleteUrl = this.get('ghostPaths.url').api('db'); - return this.get('ajax').del(deleteUrl); - }, - - _unloadData() { - this.get('store').unloadAll('post'); - this.get('store').unloadAll('tag'); - }, - - _showSuccess() { - this.get('notifications').showAlert('All content deleted from database.', {type: 'success', key: 'all-content.delete.success'}); - }, - - _showFailure(error) { - this.get('notifications').showAPIError(error, {key: 'all-content.delete'}); - }, - - actions: { - confirm() { - this.set('submitting', true); - - this._deleteAll().then(() => { - this._unloadData(); - this._showSuccess(); - }).catch((error) => { - this._showFailure(error); - }).finally(() => { - this.send('closeModal'); - }); - } - } -}); diff --git a/core/client/app/components/modals/delete-post.js b/core/client/app/components/modals/delete-post.js deleted file mode 100644 index a4528058879e..000000000000 --- a/core/client/app/components/modals/delete-post.js +++ /dev/null @@ -1,55 +0,0 @@ -import Ember from 'ember'; -import ModalComponent from 'ghost/components/modals/base'; - -const { - computed, - inject: {service} -} = Ember; - -const {alias} = computed; - -export default ModalComponent.extend({ - - submitting: false, - - post: alias('model'), - - notifications: service(), - routing: service('-routing'), - - _deletePost() { - let post = this.get('post'); - - // definitely want to clear the data store and post of any unsaved, - // client-generated tags - post.updateTags(); - - return post.destroyRecord(); - }, - - _success() { - // clear any previous error messages - this.get('notifications').closeAlerts('post.delete'); - - // redirect to content screen - this.get('routing').transitionTo('posts'); - }, - - _failure() { - this.get('notifications').showAlert('Your post could not be deleted. Please try again.', {type: 'error', key: 'post.delete.failed'}); - }, - - actions: { - confirm() { - this.set('submitting', true); - - this._deletePost().then(() => { - this._success(); - }, () => { - this._failure(); - }).finally(() => { - this.send('closeModal'); - }); - } - } -}); diff --git a/core/client/app/components/modals/delete-subscriber.js b/core/client/app/components/modals/delete-subscriber.js deleted file mode 100644 index ec8b58c765f0..000000000000 --- a/core/client/app/components/modals/delete-subscriber.js +++ /dev/null @@ -1,23 +0,0 @@ -import Ember from 'ember'; -import ModalComponent from 'ghost/components/modals/base'; -import {invokeAction} from 'ember-invoke-action'; - -const {computed} = Ember; -const {alias} = computed; - -export default ModalComponent.extend({ - - submitting: false, - - subscriber: alias('model'), - - actions: { - confirm() { - this.set('submitting', true); - - invokeAction(this, 'confirm').finally(() => { - this.set('submitting', false); - }); - } - } -}); diff --git a/core/client/app/components/modals/delete-tag.js b/core/client/app/components/modals/delete-tag.js deleted file mode 100644 index 3e36d72edca2..000000000000 --- a/core/client/app/components/modals/delete-tag.js +++ /dev/null @@ -1,27 +0,0 @@ -import Ember from 'ember'; -import ModalComponent from 'ghost/components/modals/base'; -import {invokeAction} from 'ember-invoke-action'; - -const {computed} = Ember; -const {alias} = computed; - -export default ModalComponent.extend({ - - submitting: false, - - tag: alias('model'), - - postInflection: computed('tag.count.posts', function () { - return this.get('tag.count.posts') > 1 ? 'posts' : 'post'; - }), - - actions: { - confirm() { - this.set('submitting', true); - - invokeAction(this, 'confirm').finally(() => { - this.send('closeModal'); - }); - } - } -}); diff --git a/core/client/app/components/modals/delete-user.js b/core/client/app/components/modals/delete-user.js deleted file mode 100644 index 801adac23da0..000000000000 --- a/core/client/app/components/modals/delete-user.js +++ /dev/null @@ -1,19 +0,0 @@ -import ModalComponent from 'ghost/components/modals/base'; -import {invokeAction} from 'ember-invoke-action'; - -export default ModalComponent.extend({ - - submitting: false, - - user: null, - - actions: { - confirm() { - this.set('submitting', true); - - invokeAction(this, 'confirm').finally(() => { - this.send('closeModal'); - }); - } - } -}); diff --git a/core/client/app/components/modals/import-subscribers.js b/core/client/app/components/modals/import-subscribers.js deleted file mode 100644 index 272c643be870..000000000000 --- a/core/client/app/components/modals/import-subscribers.js +++ /dev/null @@ -1,43 +0,0 @@ -import Ember from 'ember'; -import { invokeAction } from 'ember-invoke-action'; -import ModalComponent from 'ghost/components/modals/base'; -import ghostPaths from 'ghost/utils/ghost-paths'; - -const {computed} = Ember; - -export default ModalComponent.extend({ - labelText: 'Select or drag-and-drop a CSV File', - - response: null, - closeDisabled: false, - - uploadUrl: computed(function () { - return `${ghostPaths().apiRoot}/subscribers/csv/`; - }), - - actions: { - uploadStarted() { - this.set('closeDisabled', true); - }, - - uploadFinished() { - this.set('closeDisabled', false); - }, - - uploadSuccess(response) { - this.set('response', response.meta.stats); - // invoke the passed in confirm action - invokeAction(this, 'confirm'); - }, - - confirm() { - // noop - we don't want the enter key doing anything - }, - - closeModal() { - if (!this.get('closeDisabled')) { - this._super(...arguments); - } - } - } -}); diff --git a/core/client/app/components/modals/invite-new-user.js b/core/client/app/components/modals/invite-new-user.js deleted file mode 100644 index 14bd9cb99a63..000000000000 --- a/core/client/app/components/modals/invite-new-user.js +++ /dev/null @@ -1,125 +0,0 @@ -import Ember from 'ember'; -import ModalComponent from 'ghost/components/modals/base'; -import ValidationEngine from 'ghost/mixins/validation-engine'; - -const { - RSVP: {Promise}, - inject: {service}, - run -} = Ember; -const emberA = Ember.A; - -export default ModalComponent.extend(ValidationEngine, { - classNames: 'modal-content invite-new-user', - - role: null, - roles: null, - authorRole: null, - submitting: false, - - validationType: 'inviteUser', - - notifications: service(), - store: service(), - - init() { - this._super(...arguments); - - // populate roles and set initial value for the dropdown - run.schedule('afterRender', this, function () { - this.get('store').query('role', {permissions: 'assign'}).then((roles) => { - let authorRole = roles.findBy('name', 'Author'); - - this.set('roles', roles); - this.set('authorRole', authorRole); - - if (!this.get('role')) { - this.set('role', authorRole); - } - }); - }); - }, - - willDestroyElement() { - this._super(...arguments); - // TODO: this should not be needed, ValidationEngine acts as a - // singleton and so it's errors and hasValidated state stick around - this.get('errors').clear(); - this.set('hasValidated', emberA()); - }, - - validate() { - let email = this.get('email'); - - // TODO: either the validator should check the email's existence or - // the API should return an appropriate error when attempting to save - return new Promise((resolve, reject) => { - return this._super().then(() => { - this.get('store').findAll('user', {reload: true}).then((result) => { - let invitedUser = result.findBy('email', email); - - if (invitedUser) { - this.get('errors').clear('email'); - if (invitedUser.get('status') === 'invited' || invitedUser.get('status') === 'invited-pending') { - this.get('errors').add('email', 'A user with that email address was already invited.'); - } else { - this.get('errors').add('email', 'A user with that email address already exists.'); - } - - // TODO: this shouldn't be needed, ValidationEngine doesn't mark - // properties as validated when validating an entire object - this.get('hasValidated').addObject('email'); - reject(); - } else { - resolve(); - } - }); - }, () => { - // TODO: this shouldn't be needed, ValidationEngine doesn't mark - // properties as validated when validating an entire object - this.get('hasValidated').addObject('email'); - reject(); - }); - }); - }, - - actions: { - setRole(role) { - this.set('role', role); - }, - - confirm() { - let email = this.get('email'); - let role = this.get('role'); - let notifications = this.get('notifications'); - let newUser; - - this.validate().then(() => { - this.set('submitting', true); - - newUser = this.get('store').createRecord('user', { - email, - role, - status: 'invited' - }); - - 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') { - notifications.showAlert('Invitation email was not sent. Please try resending.', {type: 'error', key: 'invite.send.failed'}); - } else { - notifications.showNotification(notificationText, {key: 'invite.send.success'}); - } - }).catch((errors) => { - newUser.deleteRecord(); - notifications.showErrors(errors, {key: 'invite.send'}); - }).finally(() => { - this.send('closeModal'); - }); - }); - } - } -}); diff --git a/core/client/app/components/modals/leave-editor.js b/core/client/app/components/modals/leave-editor.js deleted file mode 100644 index 98dd69064164..000000000000 --- a/core/client/app/components/modals/leave-editor.js +++ /dev/null @@ -1,12 +0,0 @@ -import ModalComponent from 'ghost/components/modals/base'; -import {invokeAction} from 'ember-invoke-action'; - -export default ModalComponent.extend({ - actions: { - confirm() { - invokeAction(this, 'confirm').finally(() => { - this.send('closeModal'); - }); - } - } -}); diff --git a/core/client/app/components/modals/markdown-help.js b/core/client/app/components/modals/markdown-help.js deleted file mode 100644 index 7827870b6bc6..000000000000 --- a/core/client/app/components/modals/markdown-help.js +++ /dev/null @@ -1,4 +0,0 @@ -import ModalComponent from 'ghost/components/modals/base'; - -export default ModalComponent.extend({ -}); diff --git a/core/client/app/components/modals/new-subscriber.js b/core/client/app/components/modals/new-subscriber.js deleted file mode 100644 index 246d24b7f752..000000000000 --- a/core/client/app/components/modals/new-subscriber.js +++ /dev/null @@ -1,32 +0,0 @@ -import Ember from 'ember'; -import ModalComponent from 'ghost/components/modals/base'; - -export default ModalComponent.extend({ - actions: { - updateEmail(newEmail) { - this.set('model.email', newEmail); - this.set('model.hasValidated', Ember.A()); - this.get('model.errors').clear(); - }, - - confirm() { - let confirmAction = this.get('confirm'); - - this.set('submitting', true); - - confirmAction().then(() => { - this.send('closeModal'); - }).catch((errors) => { - let [error] = errors; - if (error && error.match(/email/i)) { - this.get('model.errors').add('email', error); - this.get('model.hasValidated').pushObject('email'); - } - }).finally(() => { - if (!this.get('isDestroying') && !this.get('isDestroyed')) { - this.set('submitting', false); - } - }); - } - } -}); diff --git a/core/client/app/components/modals/re-authenticate.js b/core/client/app/components/modals/re-authenticate.js deleted file mode 100644 index 9c59e12ef18e..000000000000 --- a/core/client/app/components/modals/re-authenticate.js +++ /dev/null @@ -1,68 +0,0 @@ -import Ember from 'ember'; -import ModalComponent from 'ghost/components/modals/base'; -import ValidationEngine from 'ghost/mixins/validation-engine'; - -const { - $, - computed, - inject: {service} -} = Ember; - -export default ModalComponent.extend(ValidationEngine, { - validationType: 'signin', - - submitting: false, - authenticationError: null, - - notifications: service(), - session: service(), - - identification: computed('session.user.email', function () { - return this.get('session.user.email'); - }), - - _authenticate() { - let session = this.get('session'); - let authStrategy = 'authenticator:oauth2'; - let identification = this.get('identification'); - let password = this.get('password'); - - session.set('skipAuthSuccessHandler', true); - - this.toggleProperty('submitting'); - - return session.authenticate(authStrategy, identification, password).finally(() => { - this.toggleProperty('submitting'); - session.set('skipAuthSuccessHandler', undefined); - }); - }, - - actions: { - confirm() { - // 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.set('authenticationError', null); - - this.validate({property: 'signin'}).then(() => { - this._authenticate().then(() => { - this.get('notifications').closeAlerts('post.save'); - this.send('closeModal'); - }).catch((error) => { - if (error && error.errors) { - error.errors.forEach((err) => { - err.message = Ember.String.htmlSafe(err.message); - }); - - this.get('errors').add('password', 'Incorrect password'); - this.get('hasValidated').pushObject('password'); - this.set('authenticationError', error.errors[0].message); - } - }); - }, () => { - this.get('hasValidated').pushObject('password'); - }); - } - } -}); diff --git a/core/client/app/components/modals/transfer-owner.js b/core/client/app/components/modals/transfer-owner.js deleted file mode 100644 index 8753c22f7161..000000000000 --- a/core/client/app/components/modals/transfer-owner.js +++ /dev/null @@ -1,17 +0,0 @@ -import ModalComponent from 'ghost/components/modals/base'; -import {invokeAction} from 'ember-invoke-action'; - -export default ModalComponent.extend({ - user: null, - submitting: false, - - actions: { - confirm() { - this.set('submitting', true); - - invokeAction(this, 'confirm').finally(() => { - this.send('closeModal'); - }); - } - } -}); diff --git a/core/client/app/components/modals/upload-image.js b/core/client/app/components/modals/upload-image.js deleted file mode 100644 index 8c063cc5b88f..000000000000 --- a/core/client/app/components/modals/upload-image.js +++ /dev/null @@ -1,101 +0,0 @@ -import Ember from 'ember'; -import ModalComponent from 'ghost/components/modals/base'; -import cajaSanitizers from 'ghost/utils/caja-sanitizers'; - -const { - computed, - inject: {service}, - isEmpty -} = Ember; - -export default ModalComponent.extend({ - model: null, - submitting: false, - - url: '', - newUrl: '', - - config: service(), - notifications: service(), - - image: computed('model.model', 'model.imageProperty', { - get() { - let imageProperty = this.get('model.imageProperty'); - - return this.get(`model.model.${imageProperty}`); - }, - - set(key, value) { - let model = this.get('model.model'); - let imageProperty = this.get('model.imageProperty'); - - return model.set(imageProperty, value); - } - }), - - didReceiveAttrs() { - let image = this.get('image'); - this.set('url', image); - this.set('newUrl', image); - }, - - // TODO: should validation be handled in the gh-image-uploader component? - // pro - consistency everywhere, simplification here - // con - difficult if the "save" is happening externally as it does here - // - // maybe it should be handled at the model level? - // - automatically present everywhere - // - file uploads should always result in valid urls so it should only - // affect the url input form - keyDown() { - this._setErrorState(false); - }, - - _setErrorState(state) { - if (state) { - this.$('.url').addClass('error'); - } else { - this.$('.url').removeClass('error'); - } - }, - - _validateUrl(url) { - if (!isEmpty(url) && !cajaSanitizers.url(url)) { - this._setErrorState(true); - return {message: 'Image URI is not valid'}; - } - - return true; - }, - // end validation - - actions: { - fileUploaded(url) { - this.set('url', url); - this.set('newUrl', url); - }, - - removeImage() { - this.set('url', ''); - this.set('newUrl', ''); - }, - - confirm() { - let model = this.get('model.model'); - let newUrl = this.get('newUrl'); - let result = this._validateUrl(newUrl); - let notifications = this.get('notifications'); - - if (result === true) { - this.set('submitting', true); - this.set('image', newUrl); - - model.save().catch((err) => { - notifications.showAPIError(err, {key: 'image.upload'}); - }).finally(() => { - this.send('closeModal'); - }); - } - } - } -}); diff --git a/core/client/app/controllers/about.js b/core/client/app/controllers/about.js deleted file mode 100644 index 5f6c869462fb..000000000000 --- a/core/client/app/controllers/about.js +++ /dev/null @@ -1,21 +0,0 @@ -import Ember from 'ember'; - -const { - Controller, - computed -} = Ember; - -export default Controller.extend({ - updateNotificationCount: 0, - - actions: { - updateNotificationChange(count) { - this.set('updateNotificationCount', count); - } - }, - - copyrightYear: computed(function () { - let date = new Date(); - return date.getFullYear(); - }) -}); diff --git a/core/client/app/controllers/application.js b/core/client/app/controllers/application.js deleted file mode 100644 index 4a26ce94666e..000000000000 --- a/core/client/app/controllers/application.js +++ /dev/null @@ -1,57 +0,0 @@ -import Ember from 'ember'; - -const { - Controller, - computed, - inject: {service} -} = Ember; - -export default Controller.extend({ - dropdown: service(), - session: service(), - - showNavMenu: computed('currentPath', 'session.isAuthenticated', function () { - return (this.get('currentPath') !== 'error404' || this.get('session.isAuthenticated')) && - !this.get('currentPath').match(/(signin|signup|setup|reset)/); - }), - - topNotificationCount: 0, - showMobileMenu: false, - showSettingsMenu: false, - showMarkdownHelpModal: false, - - autoNav: false, - autoNavOpen: computed('autoNav', { - get() { - return false; - }, - set(key, value) { - if (this.get('autoNav')) { - return value; - } - return false; - } - }), - - actions: { - topNotificationChange(count) { - this.set('topNotificationCount', count); - }, - - toggleAutoNav() { - this.toggleProperty('autoNav'); - }, - - openAutoNav() { - this.set('autoNavOpen', true); - }, - - closeAutoNav() { - this.set('autoNavOpen', false); - }, - - closeMobileMenu() { - this.set('showMobileMenu', false); - } - } -}); diff --git a/core/client/app/controllers/editor/edit.js b/core/client/app/controllers/editor/edit.js deleted file mode 100644 index 7cb7b575231a..000000000000 --- a/core/client/app/controllers/editor/edit.js +++ /dev/null @@ -1,14 +0,0 @@ -import Ember from 'ember'; -import EditorControllerMixin from 'ghost/mixins/editor-base-controller'; - -const {Controller} = Ember; - -export default Controller.extend(EditorControllerMixin, { - showDeletePostModal: false, - - actions: { - toggleDeletePostModal() { - this.toggleProperty('showDeletePostModal'); - } - } -}); diff --git a/core/client/app/controllers/editor/new.js b/core/client/app/controllers/editor/new.js deleted file mode 100644 index 6dc2ea504015..000000000000 --- a/core/client/app/controllers/editor/new.js +++ /dev/null @@ -1,25 +0,0 @@ -import Ember from 'ember'; -import EditorControllerMixin from 'ghost/mixins/editor-base-controller'; - -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: K, - actions: { - /** - * Redirect to editor after the first save - */ - save(options) { - return this._super(options).then((model) => { - if (model.get('id')) { - this.replaceRoute('editor.edit', model); - } - }); - } - } -}); diff --git a/core/client/app/controllers/error.js b/core/client/app/controllers/error.js deleted file mode 100644 index fae8a96ac530..000000000000 --- a/core/client/app/controllers/error.js +++ /dev/null @@ -1,20 +0,0 @@ -import Ember from 'ember'; - -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: 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'; - }) -}); diff --git a/core/client/app/controllers/post-settings-menu.js b/core/client/app/controllers/post-settings-menu.js deleted file mode 100644 index e83e69e4fefe..000000000000 --- a/core/client/app/controllers/post-settings-menu.js +++ /dev/null @@ -1,491 +0,0 @@ -import Ember from 'ember'; -import {parseDateString} from 'ghost/utils/date-formatting'; -import SettingsMenuMixin from 'ghost/mixins/settings-menu-controller'; -import boundOneWay from 'ghost/utils/bound-one-way'; -import isNumber from 'ghost/utils/isNumber'; - -const { - $, - ArrayProxy, - Controller, - Handlebars, - PromiseProxyMixin, - RSVP, - computed, - guidFor, - inject: {service, controller}, - isArray, - isBlank, - observer, - run -} = Ember; - -export default Controller.extend(SettingsMenuMixin, { - debounceId: null, - lastPromise: null, - selectedAuthor: null, - - application: controller(), - config: service(), - ghostPaths: service(), - notifications: service(), - session: service(), - slugGenerator: service(), - - initializeSelectedAuthor: observer('model', function () { - return this.get('model.author').then((author) => { - this.set('selectedAuthor', author); - return author; - }); - }), - - authors: computed(function () { - // Loaded asynchronously, so must use promise proxies. - let deferred = {}; - - deferred.promise = this.store.query('user', {limit: 'all'}).then((users) => { - return users.rejectBy('id', 'me').sortBy('name'); - }).then((users) => { - return users.filter((user) => { - return user.get('active'); - }); - }); - - return ArrayProxy - .extend(PromiseProxyMixin) - .create(deferred); - }), - - slugValue: boundOneWay('model.slug'), - - // Requests slug from title - 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 = RSVP.resolve(afterSave).then(() => { - return this.get('slugGenerator').generateSlug('post', title).then((slug) => { - if (!isBlank(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. - }); - }); - - this.set('lastPromise', promise); - }, - - metaTitleScratch: boundOneWay('model.metaTitle'), - metaDescriptionScratch: boundOneWay('model.metaDescription'), - - 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 = Handlebars.Utils.escapeExpression(metaTitle); - metaTitle = Ember.String.htmlSafe(`${metaTitle}…`); - } - - return metaTitle; - }), - - seoDescription: computed('model.scratch', 'metaDescriptionScratch', function () { - let metaDescription = this.get('metaDescriptionScratch') || ''; - let html = ''; - let el, placeholder; - - if (metaDescription.length > 0) { - placeholder = metaDescription; - } else { - el = $('.rendered-markdown'); - - // Get rendered markdown - if (el !== undefined && el.length > 0) { - html = el.clone(); - html.find('.js-drop-zone').remove(); - html = html[0].innerHTML; - } - - // Strip HTML - placeholder = $('
', {html}).text(); - // Replace new lines and trim - placeholder = placeholder.replace(/\n+/g, ' ').trim(); - } - - if (placeholder.length > 156) { - // Limit to 156 characters - placeholder = placeholder.substring(0, 156).trim(); - placeholder = Handlebars.Utils.escapeExpression(placeholder); - placeholder = Ember.String.htmlSafe(`${placeholder}…`); - } - - return placeholder; - }), - - 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) { - seoURL += '/'; - } - - if (seoURL.length > 70) { - seoURL = seoURL.substring(0, 70).trim(); - seoURL = Ember.String.htmlSafe(`${seoURL}…`); - } - - return seoURL; - }), - - // observe titleScratch, keeping the post's slug in sync - // with it until saved for the first time. - addTitleObserver: observer('model', function () { - if (this.get('model.isNew') || this.get('model.title') === '(Untitled)') { - this.addObserver('model.titleScratch', this, 'titleObserver'); - } - }), - - 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 = run.debounce(this, 'generateAndSetSlug', 'model.slug', 700); - } - - this.set('debounceId', debounceId); - }, - - // live-query of all tags for tag input autocomplete - availableTags: computed(function () { - return this.get('store').filter('tag', {limit: 'all'}, () => { - return true; - }); - }), - - showErrors(errors) { - errors = isArray(errors) ? errors : [errors]; - this.get('notifications').showErrors(errors); - }, - - actions: { - discardEnter() { - return false; - }, - - 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((errors) => { - this.showErrors(errors); - this.get('model').rollbackAttributes(); - }); - }, - - toggleFeatured() { - this.toggleProperty('model.featured'); - - // 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(this.get('saveOptions')).catch((errors) => { - this.showErrors(errors); - this.get('model').rollbackAttributes(); - }); - }, - - /** - * triggered by user manually changing slug - */ - updateSlug(newSlug) { - let slug = this.get('model.slug'); - - newSlug = newSlug || slug; - newSlug = newSlug && newSlug.trim(); - - // Ignore unchanged slugs or candidate slugs that are empty - if (!newSlug || slug === newSlug) { - // reset the input to its previous state - this.set('slugValue', slug); - - return; - } - - this.get('slugGenerator').generateSlug('post', 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) { - return; - } - - // Because the server transforms the candidate slug by stripping - // certain characters and appending a number onto the end of slugs - // to enforce uniqueness, there are cases where we can get back a - // candidate slug that is a duplicate of the original except for - // 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 - 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) { - this.set('slugValue', slug); - - return; - } - } - - this.set('model.slug', serverSlug); - - 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 (this.get('model.isNew')) { - return; - } - - return this.get('model').save(); - }).catch((errors) => { - this.showErrors(errors); - this.get('model').rollbackAttributes(); - }); - }, - - /** - * Parse user's set published date. - * Action sent by post settings menu view. - * (#1351) - */ - setPublishedAt(userInput) { - if (!userInput) { - // Clear out the publishedAt field for a draft - if (this.get('model.isDraft')) { - this.set('model.publishedAt', null); - } - - return; - } - - let newPublishedAt = parseDateString(userInput); - let publishedAt = moment(this.get('model.publishedAt')); - let errMessage = ''; - - // Clear previous errors - this.get('model.errors').remove('post-setting-date'); - - // Validate new Published date - if (!newPublishedAt.isValid()) { - errMessage = 'Published Date must be a valid date with format: ' + - 'DD MMM YY @ HH:mm (e.g. 6 Dec 14 @ 15:00)'; - } else if (newPublishedAt.diff(new Date(), 'h') > 0) { - errMessage = 'Published Date cannot currently be in the future.'; - } - - // If errors, notify and exit. - if (errMessage) { - this.get('model.errors').add('post-setting-date', errMessage); - return; - } - - // Validation complete, update the view - this.set('model.publishedAt', newPublishedAt); - - // Don't save the date if the user didn't actually changed the date - if (publishedAt && publishedAt.isSame(newPublishedAt)) { - return; - } - - // 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((errors) => { - this.showErrors(errors); - this.get('model').rollbackAttributes(); - }); - }, - - setMetaTitle(metaTitle) { - let property = 'metaTitle'; - let model = this.get('model'); - let currentTitle = model.get(property) || ''; - - // Only update if the title has changed - if (currentTitle === metaTitle) { - return; - } - - model.set(property, metaTitle); - - // If this is a new post. Don't save the model. Defer the save - // to the user pressing the save button - if (model.get('isNew')) { - return; - } - - model.save(); - }, - - setMetaDescription(metaDescription) { - let property = 'metaDescription'; - let model = this.get('model'); - let currentDescription = model.get(property) || ''; - - // Only update if the description has changed - if (currentDescription === metaDescription) { - return; - } - - model.set(property, metaDescription); - - // If this is a new post. Don't save the model. Defer the save - // to the user pressing the save button - if (model.get('isNew')) { - return; - } - - model.save(); - }, - - setCoverImage(image) { - this.set('model.image', image); - - if (this.get('model.isNew')) { - return; - } - - this.get('model').save().catch((errors) => { - this.showErrors(errors); - this.get('model').rollbackAttributes(); - }); - }, - - clearCoverImage() { - this.set('model.image', ''); - - if (this.get('model.isNew')) { - return; - } - - this.get('model').save().catch((errors) => { - this.showErrors(errors); - this.get('model').rollbackAttributes(); - }); - }, - - resetPubDate() { - this.set('publishedAtValue', ''); - }, - - closeNavMenu() { - this.get('application').send('closeNavMenu'); - }, - - changeAuthor(newAuthor) { - let author = this.get('model.author'); - let model = this.get('model'); - - // return if nothing changed - if (newAuthor.get('id') === author.get('id')) { - return; - } - - model.set('author', newAuthor); - - // if this is a new post (never been saved before), don't try to save it - if (this.get('model.isNew')) { - return; - } - - model.save().catch((errors) => { - this.showErrors(errors); - this.set('selectedAuthor', author); - model.rollbackAttributes(); - }); - }, - - 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(); - - // abort if tag is already selected - if (currentTagNames.contains(tagName.toLowerCase())) { - return; - } - - 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((tag) => { - return tag.get('name').toLowerCase() === tagName.toLowerCase(); - }); - } else { - 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', guidFor(tagToAdd)); - } - - // push tag onto post relationship - if (tagToAdd) { - this.get('model.tags').insertAt(index, tagToAdd); - } - }); - }, - - removeTag(tag) { - this.get('model.tags').removeObject(tag); - - if (tag.get('isNew')) { - tag.destroyRecord(); - } - } - } -}); diff --git a/core/client/app/controllers/posts.js b/core/client/app/controllers/posts.js deleted file mode 100644 index 6b34a9a87e22..000000000000 --- a/core/client/app/controllers/posts.js +++ /dev/null @@ -1,88 +0,0 @@ -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 -// publishedAt: DESC -// updatedAt: DESC -// id: DESC -function comparator(item1, item2) { - let updated1 = item1.get('updatedAt'); - let updated2 = item2.get('updatedAt'); - let idResult, - publishedAtResult, - statusResult, - updatedAtResult; - - // when `updatedAt` is undefined, the model is still - // being written to with the results from the server - if (item1.get('isNew') || !updated1) { - return -1; - } - - if (item2.get('isNew') || !updated2) { - return 1; - } - - 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) { - if (publishedAtResult === 0) { - if (updatedAtResult === 0) { - // This should be DESC - return idResult * -1; - } - // This should be DESC - return updatedAtResult * -1; - } - // This should be DESC - return publishedAtResult * -1; - } - - return statusResult; -} - -function publishedAtCompare(item1, item2) { - let published1 = item1.get('publishedAt'); - let published2 = item2.get('publishedAt'); - - if (!published1 && !published2) { - return 0; - } - - if (!published1 && published2) { - return -1; - } - - if (!published2 && published1) { - return 1; - } - - return compare(published1.valueOf(), published2.valueOf()); -} - -export default Controller.extend({ - - showDeletePostModal: false, - - // See PostsRoute's shortcuts - postListFocused: equal('keyboardFocus', 'postList'), - postContentFocused: equal('keyboardFocus', 'postContent'), - - sortedPosts: computed('model.@each.status', 'model.@each.publishedAt', 'model.@each.isNew', 'model.@each.updatedAt', function () { - let postsArray = this.get('model').toArray(); - - return postsArray.sort(comparator); - }), - - actions: { - toggleDeletePostModal() { - this.toggleProperty('showDeletePostModal'); - } - } -}); diff --git a/core/client/app/controllers/reset.js b/core/client/app/controllers/reset.js deleted file mode 100644 index e1a94d8e04d3..000000000000 --- a/core/client/app/controllers/reset.js +++ /dev/null @@ -1,75 +0,0 @@ -import Ember from 'ember'; -import ValidationEngine from 'ghost/mixins/validation-engine'; - -const { - Controller, - computed, - inject: {service} -} = Ember; - -export default Controller.extend(ValidationEngine, { - newPassword: '', - ne2Password: '', - token: '', - submitting: false, - flowErrors: '', - - validationType: 'reset', - - ghostPaths: service(), - notifications: service(), - session: service(), - ajax: service(), - - 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() { - this.setProperties({ - newPassword: '', - ne2Password: '', - token: '' - }); - }, - - actions: { - submit() { - let credentials = this.getProperties('newPassword', 'ne2Password', 'token'); - - this.set('flowErrors', ''); - this.get('hasValidated').addObjects(['newPassword', 'ne2Password']); - this.validate().then(() => { - let authUrl = this.get('ghostPaths.url').api('authentication', 'passwordreset'); - this.toggleProperty('submitting'); - this.get('ajax').put(authUrl, { - data: { - passwordreset: [credentials] - } - }).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((error) => { - this.get('notifications').showAPIError(error, {key: 'password.reset'}); - this.toggleProperty('submitting'); - }); - }).catch((error) => { - if (this.get('errors.newPassword')) { - this.set('flowErrors', this.get('errors.newPassword')[0].message); - } - - if (this.get('errors.ne2Password')) { - this.set('flowErrors', this.get('errors.ne2Password')[0].message); - } - - if (this.get('errors.length') === 0) { - throw error; - } - }); - } - } -}); diff --git a/core/client/app/controllers/settings/apps/index.js b/core/client/app/controllers/settings/apps/index.js deleted file mode 100644 index 5017fbc1d775..000000000000 --- a/core/client/app/controllers/settings/apps/index.js +++ /dev/null @@ -1,14 +0,0 @@ -import Ember from 'ember'; - -const { - computed, - inject: {controller} -} = Ember; - -const {alias} = computed; - -export default Ember.Controller.extend({ - appsController: controller('settings.apps'), - - slack: alias('appsController.model.slack.firstObject') -}); diff --git a/core/client/app/controllers/settings/apps/slack.js b/core/client/app/controllers/settings/apps/slack.js deleted file mode 100644 index 26877cd8a6b8..000000000000 --- a/core/client/app/controllers/settings/apps/slack.js +++ /dev/null @@ -1,75 +0,0 @@ -import Ember from 'ember'; -import { invoke } from 'ember-invoke-action'; - -const { - Controller, - computed: {empty}, - inject: {service} -} = Ember; - -export default Controller.extend({ - ghostPaths: service(), - ajax: service(), - notifications: service(), - - // will be set by route - settings: null, - - isSaving: false, - savePromise: null, - isSendingTest: false, - - testNotificationDisabled: empty('model.url'), - - actions: { - sendTestNotification() { - let notifications = this.get('notifications'); - let slackApi = this.get('ghostPaths.url').api('slack', 'test'); - - if (this.get('isSendingTest')) { - return; - } - - this.set('isSendingTest', true); - - invoke(this, 'save').then(() => { - this.get('ajax').post(slackApi).then(() => { - notifications.showAlert('Check your slack channel test message.', {type: 'info', key: 'slack-test.send.success'}); - }).catch((error) => { - notifications.showAPIError(error, {key: 'slack-test:send'}); - }); - }).catch(() => { - // noop - error already handled in .save - }).finally(() => { - this.set('isSendingTest', false); - }); - }, - - updateURL(value) { - this.set('model.url', value); - this.get('model.errors').clear(); - }, - - save() { - let slack = this.get('model'); - let settings = this.get('settings'); - - if (this.get('isSaving')) { - return; - } - - return slack.validate().then(() => { - settings.get('slack').clear().pushObject(slack); - - this.set('isSaving', true); - - return settings.save().catch((err) => { - this.get('notifications').showErrors(err); - throw err; - }).finally(() => { - this.set('isSaving', false); - }); - }); - } - } -}); diff --git a/core/client/app/controllers/settings/code-injection.js b/core/client/app/controllers/settings/code-injection.js deleted file mode 100644 index 576b611bb8ba..000000000000 --- a/core/client/app/controllers/settings/code-injection.js +++ /dev/null @@ -1,19 +0,0 @@ -import Ember from 'ember'; -import SettingsSaveMixin from 'ghost/mixins/settings-save'; - -const { - Controller, - inject: {service} -} = Ember; - -export default Controller.extend(SettingsSaveMixin, { - notifications: service(), - - 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 deleted file mode 100644 index 92938e2ea7ad..000000000000 --- a/core/client/app/controllers/settings/general.js +++ /dev/null @@ -1,253 +0,0 @@ -import Ember from 'ember'; -import SettingsSaveMixin from 'ghost/mixins/settings-save'; -import randomPassword from 'ghost/utils/random-password'; - -const { - Controller, - computed, - inject: {service}, - observer, - run -} = Ember; - -export default Controller.extend(SettingsSaveMixin, { - - showUploadLogoModal: false, - showUploadCoverModal: false, - - notifications: service(), - config: service(), - _scratchFacebook: null, - _scratchTwitter: null, - - 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; - } - }); - - return selectedTheme; - }), - - logoImageSource: computed('model.logo', function () { - return this.get('model.logo') || ''; - }), - - coverImageSource: computed('model.cover', function () { - return this.get('model.cover') || ''; - }), - - isDatedPermalinks: computed('model.permalinks', { - set(key, value) { - this.set('model.permalinks', value ? '/:year/:month/:day/:slug/' : '/:slug/'); - - let slugForm = this.get('model.permalinks'); - return slugForm !== '/:slug/'; - }, - - get() { - let slugForm = this.get('model.permalinks'); - - return slugForm !== '/:slug/'; - } - }), - - themes: computed(function () { - return this.get('model.availableThemes').reduce(function (themes, t) { - let theme = {}; - - theme.name = t.name; - theme.label = t.package ? `${t.package.name} - ${t.package.version}` : t.name; - theme.package = t.package; - theme.active = !!t.active; - - themes.push(theme); - - return themes; - }, []); - }).readOnly(), - - 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() { - let notifications = this.get('notifications'); - let config = this.get('config'); - - return this.get('model').save().then((model) => { - config.set('blogTitle', model.get('title')); - - // this forces the document title to recompute after - // a blog title change - this.send('collectTitleTokens', []); - - return model; - }).catch((error) => { - if (error) { - notifications.showAPIError(error, {key: 'settings.save'}); - } - throw error; - }); - }, - - actions: { - checkPostsPerPage() { - let postsPerPage = this.get('model.postsPerPage'); - - if (postsPerPage < 1 || postsPerPage > 1000 || isNaN(postsPerPage)) { - this.set('model.postsPerPage', 5); - } - }, - - setTheme(theme) { - this.set('model.activeTheme', theme.name); - }, - - toggleUploadCoverModal() { - this.toggleProperty('showUploadCoverModal'); - }, - - toggleUploadLogoModal() { - this.toggleProperty('showUploadLogoModal'); - }, - - validateFacebookUrl() { - let newUrl = this.get('_scratchFacebook'); - let oldUrl = this.get('model.facebook'); - let errMessage = ''; - - if (newUrl === '') { - // Clear out the Facebook url - this.set('model.facebook', ''); - this.get('model.errors').remove('facebook'); - return; - } - - // _scratchFacebook will be null unless the user has input something - if (!newUrl) { - newUrl = oldUrl; - } - - // If new url didn't change, exit - if (newUrl === oldUrl) { - this.get('model.errors').remove('facebook'); - return; - } - - if (newUrl.match(/(?:facebook\.com\/)(\S+)/) || newUrl.match(/([a-z\d\.]+)/i)) { - let username = []; - - if (newUrl.match(/(?:facebook\.com\/)(\S+)/)) { - [ , username ] = newUrl.match(/(?:facebook\.com\/)(\S+)/); - } else { - [ , username ] = newUrl.match(/(?:https\:\/\/|http\:\/\/)?(?:www\.)?(?:\w+\.\w+\/+)?(\S+)/mi); - } - - // check if we have a /page/username or without - if (username.match(/^(?:\/)?(pages?\/\S+)/mi)) { - // we got a page url, now save the username without the / in the beginning - - [ , username ] = username.match(/^(?:\/)?(pages?\/\S+)/mi); - } else if (username.match(/^(http|www)|(\/)/) || !username.match(/^([a-z\d\.]{5,50})$/mi)) { - errMessage = !username.match(/^([a-z\d\.]{5,50})$/mi) ? 'Your Page name is not a valid Facebook Page name' : 'The URL must be in a format like https://www.facebook.com/yourPage'; - - this.get('model.errors').add('facebook', errMessage); - this.get('model.hasValidated').pushObject('facebook'); - return; - } - - newUrl = `https://www.facebook.com/${username}`; - this.set('model.facebook', newUrl); - - this.get('model.errors').remove('facebook'); - this.get('model.hasValidated').pushObject('facebook'); - - // User input is validated - return this.save().then(() => { - this.set('model.facebook', ''); - run.schedule('afterRender', this, function () { - this.set('model.facebook', newUrl); - }); - }); - } else { - errMessage = 'The URL must be in a format like ' + - 'https://www.facebook.com/yourPage'; - this.get('model.errors').add('facebook', errMessage); - this.get('model.hasValidated').pushObject('facebook'); - return; - } - }, - - validateTwitterUrl() { - let newUrl = this.get('_scratchTwitter'); - let oldUrl = this.get('model.twitter'); - let errMessage = ''; - - if (newUrl === '') { - // Clear out the Twitter url - this.set('model.twitter', ''); - this.get('model.errors').remove('twitter'); - return; - } - - // _scratchTwitter will be null unless the user has input something - if (!newUrl) { - newUrl = oldUrl; - } - - // If new url didn't change, exit - if (newUrl === oldUrl) { - this.get('model.errors').remove('twitter'); - return; - } - - if (newUrl.match(/(?:twitter\.com\/)(\S+)/) || newUrl.match(/([a-z\d\.]+)/i)) { - let username = []; - - if (newUrl.match(/(?:twitter\.com\/)(\S+)/)) { - [ , username] = newUrl.match(/(?:twitter\.com\/)(\S+)/); - } else { - [username] = newUrl.match(/([^/]+)\/?$/mi); - } - - // check if username starts with http or www and show error if so - if (username.match(/^(http|www)|(\/)/) || !username.match(/^[a-z\d\.\_]{1,15}$/mi)) { - errMessage = !username.match(/^[a-z\d\.\_]{1,15}$/mi) ? 'Your Username is not a valid Twitter Username' : 'The URL must be in a format like https://twitter.com/yourUsername'; - - this.get('model.errors').add('twitter', errMessage); - this.get('model.hasValidated').pushObject('twitter'); - return; - } - - newUrl = `https://twitter.com/${username}`; - this.set('model.twitter', newUrl); - - this.get('model.errors').remove('twitter'); - this.get('model.hasValidated').pushObject('twitter'); - - // User input is validated - return this.save().then(() => { - this.set('model.twitter', ''); - run.schedule('afterRender', this, function () { - this.set('model.twitter', newUrl); - }); - }); - } else { - errMessage = 'The URL must be in a format like ' + - 'https://twitter.com/yourUsername'; - this.get('model.errors').add('twitter', errMessage); - this.get('model.hasValidated').pushObject('twitter'); - return; - } - } - } -}); diff --git a/core/client/app/controllers/settings/labs.js b/core/client/app/controllers/settings/labs.js deleted file mode 100644 index 911e941a927a..000000000000 --- a/core/client/app/controllers/settings/labs.js +++ /dev/null @@ -1,89 +0,0 @@ -import Ember from 'ember'; - -const { - $, - Controller, - inject: {service}, - isArray -} = Ember; - -export default Controller.extend({ - uploadButtonText: 'Import', - importErrors: '', - submitting: false, - showDeleteAllModal: false, - - ghostPaths: service(), - notifications: service(), - session: service(), - ajax: service(), - - actions: { - onUpload(file) { - let formData = new FormData(); - let notifications = this.get('notifications'); - let currentUserId = this.get('session.user.id'); - let dbUrl = this.get('ghostPaths.url').api('db'); - - this.set('uploadButtonText', 'Importing'); - this.set('importErrors', ''); - - formData.append('importfile', file); - - this.get('ajax').post(dbUrl, { - data: formData, - dataType: 'json', - cache: false, - contentType: false, - processData: false - }).then(() => { - // Clear the store, so that all the new data gets fetched correctly. - this.store.unloadAll(); - // Reload currentUser and set session - this.set('session.user', this.store.findRecord('user', currentUserId)); - // TODO: keep as notification, add link to view content - notifications.showNotification('Import successful.', {key: 'import.upload.success'}); - }).catch((response) => { - if (response && response.errors && isArray(response.errors)) { - this.set('importErrors', response.errors); - } - - notifications.showAlert('Import Failed', {type: 'error', key: 'import.upload.failed'}); - }).finally(() => { - this.set('uploadButtonText', 'Import'); - }); - }, - - 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 = $('