-
-
- {{/each}}
+ {{#unless theme.active}}
+
Activate
+ {{/unless}}
+
+ {{svg-jar "dotdotdot"}}
-{{else}}
-
-{{/if}}
+ {{/each}}
diff --git a/ghost/admin/app/components/gh-theme-table.js b/ghost/admin/app/components/gh-theme-table.js
index 129e4f9aa3..ebcbacafef 100644
--- a/ghost/admin/app/components/gh-theme-table.js
+++ b/ghost/admin/app/components/gh-theme-table.js
@@ -1,13 +1,25 @@
-import Component from '@ember/component';
-import {computed} from '@ember/object';
+import Component from '@glimmer/component';
+import {action} from '@ember/object';
import {get} from '@ember/object';
+import {inject as service} from '@ember/service';
-export default Component.extend({
+export default class GhThemeTableComponent extends Component {
+ @service ghostPaths;
+ @service modals;
+ @service themeManagement;
+ @service utils;
- themes: null,
+ activateTaskInstance = null;
+ confirmDeleteModal = null;
- sortedThemes: computed('themes.@each.active', function () {
- let themes = this.themes.map((t) => {
+ willDestroy() {
+ super.willDestroy(...arguments);
+ this.confirmDeleteModal?.close?.();
+ this.activateTaskInstance?.cancel();
+ }
+
+ get sortedThemes() {
+ let themes = this.args.themes.map((t) => {
let theme = {};
let themePackage = get(t, 'package');
@@ -60,6 +72,28 @@ export default Component.extend({
}
return 0;
});
- }).readOnly()
+ }
-});
+ @action
+ downloadTheme(themeName, dropdown) {
+ dropdown?.actions.close();
+ this.utils.downloadFile(`${this.ghostPaths.apiRoot}/themes/${themeName}/download/`);
+ }
+
+ @action
+ activateTheme(theme, dropdown) {
+ dropdown?.actions.close();
+ this.activateTaskInstance = this.themeManagement.activateTask.perform(theme);
+ }
+
+ @action
+ deleteTheme(theme, dropdown) {
+ dropdown?.actions.close();
+
+ this.confirmDeleteModal = this.modals.open('modals/design/confirm-delete-theme', {
+ theme
+ }).finally(() => {
+ this.confirmDeleteModal = null;
+ });
+ }
+}
diff --git a/ghost/admin/app/components/modal-delete-theme.hbs b/ghost/admin/app/components/modal-delete-theme.hbs
deleted file mode 100644
index 4333ac11b0..0000000000
--- a/ghost/admin/app/components/modal-delete-theme.hbs
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
{{svg-jar "close"}}Close
-
-
-
You're about to delete "{{this.theme.label}} ". This is permanent! We warned you, k? Maybe download your theme before continuing
-
-
-
diff --git a/ghost/admin/app/components/modal-delete-theme.js b/ghost/admin/app/components/modal-delete-theme.js
deleted file mode 100644
index 363d0f397f..0000000000
--- a/ghost/admin/app/components/modal-delete-theme.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import ModalComponent from 'ghost-admin/components/modal-base';
-import {alias} from '@ember/object/computed';
-import {task} from 'ember-concurrency';
-
-export default ModalComponent.extend({
- // Allowed actions
- confirm: () => {},
-
- theme: alias('model.theme'),
- download: alias('model.download'),
-
- actions: {
- confirm() {
- this.deleteTheme.perform();
- }
- },
-
- deleteTheme: task(function* () {
- try {
- yield this.confirm();
- } finally {
- this.send('closeModal');
- }
- }).drop()
-});
diff --git a/ghost/admin/app/components/modal-install-theme.hbs b/ghost/admin/app/components/modal-install-theme.hbs
deleted file mode 100644
index 1cc61c2a63..0000000000
--- a/ghost/admin/app/components/modal-install-theme.hbs
+++ /dev/null
@@ -1,147 +0,0 @@
-
-
-
{{svg-jar "close"}}Close
-
-
- {{#if this.isReady}}
-
- You're about to install {{this.themeName}} from the theme directory.
-
- {{#if this.willOverwriteExisting}}
-
- This will overwrite your existing version of {{this.themeName}}{{if this.willOverwriteExisting.active " which is your active theme"}}.
- Any custom changes will be lost.
-
- {{/if}}
-
- {{#if this.marketplaceTheme.shortImage}}
-
-
-
- {{/if}}
- {{/if}}
-
- {{#if this.willOverwriteDefault}}
-
- Sorry, the default Casper theme cannot be overwritten.
- If you wish to make changes please download the theme and upload a renamed zip file.
-
- {{/if}}
-
- {{#if this.installSuccess}}
- {{#if this.hasWarningsOrErrors}}
-
- The theme "{{this.themeName}}" was installed successfully but we detected some {{if this.validationErrors "errors" "warnings"}}.
- {{#unless this.theme.active}}
- You are still able to activate and use the theme but it is recommended to fix these {{if this.validationErrors "errors" "warnings"}} before you do so.
- {{/unless}}
-
- {{else}}
- {{!-- Installed with no errors --}}
-
The theme "{{this.themeName}}" was installed successfully. {{unless this.theme.active "Do you want to activate it now?"}}
- {{/if}}
- {{/if}}
-
- {{#if this.installError}}
- {{!-- Outright failure - not found, not a theme, server error, etc --}}
-
{{this.themeName}} failed to install.
-
{{this.installError}}
- {{/if}}
-
- {{#if this.installFailure}}
- {{!-- Invalid theme --}}
-
This theme is invalid and cannot be activated. Contact the theme developer.
- {{/if}}
-
- {{#if this.fatalValidationErrors}}
-
-
Fatal Errors
-
Must-fix to activate theme
-
-
-
- {{#each this.fatalValidationErrors as |error|}}
-
-
-
- {{/each}}
-
- {{/if}}
-
- {{#if this.validationErrors}}
-
-
Errors
-
Highly recommended to fix, functionality could be restricted
-
-
- {{#each this.validationErrors as |error|}}
-
-
-
- {{/each}}
-
- {{/if}}
-
- {{#if this.validationWarnings}}
-
-
Warnings
-
-
- {{#each this.validationWarnings as |error|}}
-
-
-
- {{/each}}
-
- {{/if}}
-
-
-
-
\ No newline at end of file
diff --git a/ghost/admin/app/components/modal-install-theme.js b/ghost/admin/app/components/modal-install-theme.js
deleted file mode 100644
index 8aa5a82402..0000000000
--- a/ghost/admin/app/components/modal-install-theme.js
+++ /dev/null
@@ -1,163 +0,0 @@
-import ModalBase from 'ghost-admin/components/modal-base';
-import classic from 'ember-classic-decorator';
-import {action} from '@ember/object';
-import {isThemeValidationError} from 'ghost-admin/services/ajax';
-import {inject as service} from '@ember/service';
-import {task} from 'ember-concurrency-decorators';
-import {tracked} from '@glimmer/tracking';
-
-import {MARKETPLACE_THEMES} from 'ghost-admin/controllers/settings/theme';
-
-// TODO: update modals to work fully with Glimmer components
-@classic
-export default class ModalInstallThemeComponent extends ModalBase {
- @service ajax;
- @service ghostPaths;
- @service store;
-
- @tracked model;
- @tracked theme;
- @tracked installError = '';
- @tracked validationWarnings = [];
- @tracked validationErrors = [];
- @tracked fatalValidationErrors = [];
-
- get themeName() {
- return this.model.ref.split('/')[1];
- }
-
- get marketplaceTheme() {
- return MARKETPLACE_THEMES.find(theme => theme.name.toLowerCase() === this.themeName.toLowerCase());
- }
-
- get currentThemeNames() {
- return this.model.themes.mapBy('name');
- }
-
- get willOverwriteDefault() {
- return this.themeName.toLowerCase() === 'casper';
- }
-
- get willOverwriteExisting() {
- return this.model.themes.findBy('name', this.themeName.toLowerCase());
- }
-
- get installSuccess() {
- return !!this.theme;
- }
-
- get installFailure() {
- return !this.installSuccess && (this.validationErrors.length || this.fatalValidationErrors.length);
- }
-
- get isReady() {
- return !this.installSuccess && !this.installError && !this.installFailure && !this.willOverwriteDefault;
- }
-
- get hasWarningsOrErrors() {
- return this.validationWarnings.length > 0 || this.validationErrors.length > 0;
- }
-
- get shouldShowInstall() {
- return !this.installSuccess && !this.installFailure && !this.willOverwriteDefault;
- }
-
- get shouldShowActivate() {
- return this.installSuccess && !this.theme.active;
- }
-
- get hasActionButton() {
- return this.shouldShowInstall || this.shouldShowActivate;
- }
-
- @action
- close() {
- this.closeModal();
- }
-
- @action
- reset() {
- this.theme = null;
- this.resetErrors();
- }
-
- actions = {
- confirm() {
- // noop - needed to override ModalBase.actions.confirm
- },
-
- // needed because ModalBase uses .send() for keyboard events
- closeModal() {
- this.closeModal();
- }
- }
-
- @task({drop: true})
- *installTask() {
- try {
- const url = this.ghostPaths.url.api('themes/install') + `?source=github&ref=${this.model.ref}`;
- const result = yield this.ajax.post(url);
-
- this.installError = '';
-
- if (result.themes) {
- // show theme in list immediately
- this.store.pushPayload(result);
-
- this.theme = this.store.peekRecord('theme', result.themes[0].name);
-
- this.validationWarnings = this.theme.warnings || [];
- this.validationErrors = this.theme.errors || [];
- this.fatalValidationErrors = [];
-
- return true;
- }
- } catch (error) {
- if (isThemeValidationError(error)) {
- this.resetErrors();
-
- let errors = error.payload.errors[0].details.errors;
- let fatalErrors = [];
- let normalErrors = [];
-
- // to have a proper grouping of fatal errors and none fatal, we need to check
- // our errors for the fatal property
- if (errors && errors.length > 0) {
- for (let i = 0; i < errors.length; i += 1) {
- if (errors[i].fatal) {
- fatalErrors.push(errors[i]);
- } else {
- normalErrors.push(errors[i]);
- }
- }
- }
-
- this.fatalValidationErrors = fatalErrors;
- this.validationErrors = normalErrors;
-
- return false;
- }
-
- if (error.payload?.errors) {
- this.installError = error.payload.errors[0].message;
- return false;
- }
-
- this.installError = error.message;
- throw error;
- }
- }
-
- @task({drop: true})
- *activateTask() {
- yield this.theme.activate();
- this.closeModal();
- }
-
- resetErrors() {
- this.installError = '';
- this.validationWarnings = [];
- this.validationErrors = [];
- this.fatalValidationErrors = [];
- }
-}
diff --git a/ghost/admin/app/components/modal-theme-warnings.hbs b/ghost/admin/app/components/modal-theme-warnings.hbs
deleted file mode 100644
index dad848a20a..0000000000
--- a/ghost/admin/app/components/modal-theme-warnings.hbs
+++ /dev/null
@@ -1,64 +0,0 @@
-
-
-
{{svg-jar "close"}}Close
-
-
- {{#if this.fatalErrors}}
-
-
Fatal Errors
-
Must-fix to activate theme
-
-
- {{#each this.fatalErrors as |error|}}
-
-
-
- {{/each}}
-
- {{/if}}
-
- {{#if this.errors}}
-
-
Errors
-
Highly recommended to fix, functionality could be restricted
-
-
-
- {{#each this.errors as |error|}}
-
-
-
- {{/each}}
-
- {{/if}}
-
- {{#if (and this.warnings (or this.fatalErrors this.errors))}}
-
-
Warnings
-
- {{/if}}
- {{#if this.warnings}}
-
- {{#each this.warnings as |error|}}
-
-
-
- {{/each}}
-
- {{/if}}
-
-
-
-
diff --git a/ghost/admin/app/components/modal-theme-warnings.js b/ghost/admin/app/components/modal-theme-warnings.js
deleted file mode 100644
index 97fa8610d0..0000000000
--- a/ghost/admin/app/components/modal-theme-warnings.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import ModalComponent from 'ghost-admin/components/modal-base';
-import {reads} from '@ember/object/computed';
-
-export default ModalComponent.extend({
- title: reads('model.title'),
- message: reads('model.message'),
- warnings: reads('model.warnings'),
- errors: reads('model.errors'),
- fatalErrors: reads('model.fatalErrors'),
- canActivate: reads('model.canActivate'),
-
- actions: {
- confirm() {
- this.send('closeModal');
- }
- }
-});
diff --git a/ghost/admin/app/components/modal-upgrade-host-limit-custom-theme.hbs b/ghost/admin/app/components/modal-upgrade-host-limit-custom-theme.hbs
deleted file mode 100644
index 3f4344d10b..0000000000
--- a/ghost/admin/app/components/modal-upgrade-host-limit-custom-theme.hbs
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
{{svg-jar "close"}}Close
-
-
-
- {{#if this.model.limitErrorMessage}}
- {{html-safe this.model.limitErrorMessage}}
- {{else}}
- Your current plan only supports official themes. You can install them from the Ghost theme marketplace .
- {{/if}}
-
-
-
-
\ No newline at end of file
diff --git a/ghost/admin/app/components/modal-upgrade-host-limit-custom-theme.js b/ghost/admin/app/components/modal-upgrade-host-limit-custom-theme.js
deleted file mode 100644
index 99c3e17ee7..0000000000
--- a/ghost/admin/app/components/modal-upgrade-host-limit-custom-theme.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import ModalComponent from 'ghost-admin/components/modal-base';
-import {inject as service} from '@ember/service';
-
-export default ModalComponent.extend({
- router: service(),
-
- actions: {
- upgrade() {
- this.router.transitionTo('pro');
- },
-
- confirm() {
- this.send('upgrade');
- }
- }
-});
diff --git a/ghost/admin/app/components/modal-upload-theme.hbs b/ghost/admin/app/components/modal-upload-theme.hbs
deleted file mode 100644
index 97969900d6..0000000000
--- a/ghost/admin/app/components/modal-upload-theme.hbs
+++ /dev/null
@@ -1,138 +0,0 @@
-
-
-
{{svg-jar "close"}}Close
-
-
- {{#if this.theme}}
- {{#if this.hasWarningsOrErrors}}
-
- The theme "{{this.themeName}}" was installed successfully but we detected some {{if this.validationErrors "errors" "warnings"}}.
- {{#if this.canActivateTheme}}
- You are still able to activate and use the theme but it is recommended to fix these {{if this.validationErrors "errors" "warnings"}} before you do so.
- {{/if}}
-
-
- {{#if this.validationErrors}}
-
-
Errors
-
Highly recommended to fix, functionality could be restricted
-
-
- {{#each this.validationErrors as |error|}}
-
-
-
- {{/each}}
-
- {{/if}}
-
- {{#if this.validationWarnings}}
-
-
Warnings
-
-
- {{#each this.validationWarnings as |error|}}
-
-
-
- {{/each}}
-
- {{/if}}
- {{else}}
-
- "{{this.themeName}}" uploaded successfully.
- {{#if this.canActivateTheme}}Do you want to activate it now?{{/if}}
-
- {{/if}}
- {{else if this.displayOverwriteWarning}}
-
- The theme folder "{{this.fileThemeName}}" already exists. Do you want to overwrite it?
-
- {{else if (or this.validationErrors this.fatalValidationErrors)}}
-
-
- This theme is invalid and cannot be activated. Fix the following errors and re-upload the theme.
-
-
- {{#if this.fatalValidationErrors}}
-
-
Fatal Errors
-
Must-fix to activate theme
-
-
-
- {{#each this.fatalValidationErrors as |error|}}
-
-
-
- {{/each}}
-
- {{/if}}
-
- {{#if this.validationErrors}}
-
-
Errors
-
Highly recommended to fix, functionality could be restricted
-
-
- {{#each this.validationErrors as |error|}}
-
-
-
- {{/each}}
-
- {{/if}}
- {{else}}
-
- {{/if}}
-
-
-
-
diff --git a/ghost/admin/app/components/modal-upload-theme.js b/ghost/admin/app/components/modal-upload-theme.js
deleted file mode 100644
index 7a530de1fb..0000000000
--- a/ghost/admin/app/components/modal-upload-theme.js
+++ /dev/null
@@ -1,176 +0,0 @@
-import ModalComponent from 'ghost-admin/components/modal-base';
-import ghostPaths from 'ghost-admin/utils/ghost-paths';
-import {
- UnsupportedMediaTypeError,
- isThemeValidationError
-} from 'ghost-admin/services/ajax';
-import {computed} from '@ember/object';
-import {get} from '@ember/object';
-import {mapBy, or} from '@ember/object/computed';
-import {run} from '@ember/runloop';
-import {inject as service} from '@ember/service';
-
-const DEFAULTS = {
- accept: ['application/zip', 'application/x-zip-compressed'],
- extensions: ['zip']
-};
-
-export default ModalComponent.extend({
- eventBus: service(),
- store: service(),
-
- accept: null,
- extensions: null,
- themes: null,
- closeDisabled: false,
- file: null,
- theme: false,
- displayOverwriteWarning: false,
-
- hideUploader: or('theme', 'displayOverwriteWarning'),
- currentThemeNames: mapBy('model.themes', 'name'),
-
- uploadUrl: computed(function () {
- return `${ghostPaths().apiRoot}/themes/upload/`;
- }),
-
- themeName: computed('theme.{name,package.name}', function () {
- let themePackage = this.get('theme.package');
- let name = this.get('theme.name');
-
- return themePackage ? `${themePackage.name} - ${themePackage.version}` : name;
- }),
-
- fileThemeName: computed('file', function () {
- let file = this.file;
- return file.name.replace(/\.zip$/, '');
- }),
-
- canActivateTheme: computed('theme', function () {
- let theme = this.theme;
- return theme && !theme.get('active');
- }),
-
- init() {
- this._super(...arguments);
-
- this.accept = this.accept || DEFAULTS.accept;
- this.extensions = this.extensions || DEFAULTS.extensions;
- },
-
- actions: {
- validateTheme(file) {
- let themeName = file.name.replace(/\.zip$/, '').replace(/[^\w@.]/gi, '-').toLowerCase();
-
- let currentThemeNames = this.currentThemeNames;
-
- this.set('file', file);
-
- let [, extension] = (/(?:\.([^.]+))?$/).exec(file.name);
- let extensions = this.extensions;
-
- if (!extension || extensions.indexOf(extension.toLowerCase()) === -1) {
- return new UnsupportedMediaTypeError();
- }
-
- if (file.name.match(/^casper\.zip$/i)) {
- return {payload: {errors: [{message: 'Sorry, the default Casper theme cannot be overwritten.
Please rename your zip file and try again.'}]}};
- }
-
- if (!this._allowOverwrite && currentThemeNames.includes(themeName)) {
- this.set('displayOverwriteWarning', true);
- return false;
- }
-
- return true;
- },
-
- confirmOverwrite() {
- this._allowOverwrite = true;
- this.set('displayOverwriteWarning', false);
-
- // we need to schedule afterRender so that the upload component is
- // displayed again in order to subscribe/respond to the event bus
- run.schedule('afterRender', this, function () {
- this.eventBus.publish('themeUploader:upload', this.file);
- });
- },
-
- uploadStarted() {
- this.set('closeDisabled', true);
- },
-
- uploadFinished() {
- this.set('closeDisabled', false);
- },
-
- uploadSuccess(response) {
- this.store.pushPayload(response);
-
- let theme = this.store.peekRecord('theme', response.themes[0].name);
-
- this.set('theme', theme);
-
- if (get(theme, 'warnings.length') > 0) {
- this.set('validationWarnings', get(theme, 'warnings'));
- }
-
- // Ghost differentiates between errors and fatal errors
- // You can't activate a theme with fatal errors, but with errors.
- if (get(theme, 'errors.length') > 0) {
- this.set('validationErrors', get(theme, 'errors'));
- }
-
- this.set('hasWarningsOrErrors', this.get('validationErrors.length') || this.get('validationWarnings.length'));
-
- // invoke the passed in confirm action
- this.get('model.uploadSuccess')(theme);
- },
-
- uploadFailed(error) {
- if (isThemeValidationError(error)) {
- let errors = error.payload.errors[0].details.errors;
- let fatalErrors = [];
- let normalErrors = [];
-
- // to have a proper grouping of fatal errors and none fatal, we need to check
- // our errors for the fatal property
- if (errors && errors.length > 0) {
- for (let i = 0; i < errors.length; i += 1) {
- if (errors[i].fatal) {
- fatalErrors.push(errors[i]);
- } else {
- normalErrors.push(errors[i]);
- }
- }
- }
-
- this.set('fatalValidationErrors', fatalErrors);
- this.set('validationErrors', normalErrors);
- }
- },
-
- confirm() {
- // noop - we don't want the enter key doing anything
- },
-
- activate() {
- this.get('model.activate')(this.theme);
- this.closeModal();
- },
-
- closeModal() {
- if (!this.closeDisabled) {
- this._super(...arguments);
- }
- },
-
- reset() {
- this.set('theme', null);
- this.set('validationWarnings', []);
- this.set('validationErrors', []);
- this.set('fatalValidationErrors', []);
- this.set('hasWarningsOrErrors', false);
- }
- }
-});
diff --git a/ghost/admin/app/controllers/settings/theme.js b/ghost/admin/app/controllers/settings/theme.js
deleted file mode 100644
index d5f4e81831..0000000000
--- a/ghost/admin/app/controllers/settings/theme.js
+++ /dev/null
@@ -1,169 +0,0 @@
-/* eslint-disable ghost/ember/alias-model-in-controller */
-import Controller from '@ember/controller';
-import {isEmpty} from '@ember/utils';
-import {isThemeValidationError} from 'ghost-admin/services/ajax';
-import {notEmpty} from '@ember/object/computed';
-import {inject as service} from '@ember/service';
-
-export const MARKETPLACE_THEMES = [{
- name: 'Edition',
- category: 'Newsletter',
- url: 'https://github.com/TryGhost/Edition',
- previewUrl: 'https://ghost.org/themes/edition',
- ref: 'TryGhost/Edition',
- image: 'assets/img/themes/Edition.jpg',
- shortImage: 'assets/img/themes/Edition-cut.jpg'
-}, {
- name: 'Alto',
- category: 'Blog',
- url: 'https://github.com/TryGhost/Alto',
- previewUrl: 'https://ghost.org/themes/alto',
- ref: 'TryGhost/Alto',
- image: 'assets/img/themes/Alto.jpg',
- shortImage: 'assets/img/themes/Alto-cut.jpg'
-}, {
- name: 'London',
- category: 'Photography',
- url: 'https://github.com/TryGhost/London',
- previewUrl: 'https://ghost.org/themes/london',
- ref: 'TryGhost/London',
- image: 'assets/img/themes/London.jpg',
- shortImage: 'assets/img/themes/London-cut.jpg'
-}, {
- name: 'Ease',
- category: 'Documentation',
- url: 'https://github.com/TryGhost/Ease',
- previewUrl: 'https://ghost.org/themes/ease',
- ref: 'TryGhost/Ease',
- image: 'assets/img/themes/Ease.jpg',
- shortImage: 'assets/img/themes/Ease-cut.jpg'
-}];
-
-export default Controller.extend({
- config: service(),
- ghostPaths: service(),
- limit: service(),
- notifications: service(),
- session: service(),
- settings: service(),
- utils: service(),
-
- dirtyAttributes: false,
- newNavItem: null,
- newSecondaryNavItem: null,
- themes: null,
- themeToDelete: null,
- displayUpgradeModal: false,
- limitErrorMessage: null,
-
- init() {
- this._super(...arguments);
- this.marketplaceThemes = MARKETPLACE_THEMES;
- },
-
- showDeleteThemeModal: notEmpty('themeToDelete'),
-
- actions: {
- async activateTheme(theme) {
- const isOverLimit = await this.limit.checkWouldGoOverLimit('customThemes', {value: theme.name});
- if (isOverLimit) {
- try {
- await this.limit.limiter.errorIfWouldGoOverLimit('customThemes', {value: theme.name});
- this.limitErrorMessage = null;
- } catch (error) {
- if (error.errorType !== 'HostLimitError') {
- throw error;
- }
-
- this.limitErrorMessage = error.message;
- }
-
- this.set('displayUpgradeModal', true);
- return;
- }
- return theme.activate().then((activatedTheme) => {
- if (!isEmpty(activatedTheme.get('warnings'))) {
- this.set('themeWarnings', activatedTheme.get('warnings'));
- this.set('showThemeWarningsModal', true);
- }
-
- if (!isEmpty(activatedTheme.get('errors'))) {
- this.set('themeErrors', activatedTheme.get('errors'));
- this.set('showThemeWarningsModal', true);
- }
- }).catch((error) => {
- if (isThemeValidationError(error)) {
- let errors = error.payload.errors[0].details.errors;
- let fatalErrors = [];
- let normalErrors = [];
-
- // to have a proper grouping of fatal errors and none fatal, we need to check
- // our errors for the fatal property
- if (errors.length > 0) {
- for (let i = 0; i < errors.length; i += 1) {
- if (errors[i].fatal) {
- fatalErrors.push(errors[i]);
- } else {
- normalErrors.push(errors[i]);
- }
- }
- }
-
- this.set('themeErrors', normalErrors);
- this.set('themeFatalErrors', fatalErrors);
- this.set('showThemeErrorsModal', true);
- return;
- }
-
- throw error;
- });
- },
-
- downloadTheme(theme) {
- this.utils.downloadFile(`${this.get('ghostPaths.apiRoot')}/themes/${theme.name}/download/`);
- },
-
- deleteTheme(theme) {
- if (theme) {
- return this.set('themeToDelete', theme);
- }
-
- return this._deleteTheme();
- },
-
- hideDeleteThemeModal() {
- this.set('themeToDelete', null);
- },
-
- hideThemeWarningsModal() {
- this.set('themeWarnings', null);
- this.set('themeErrors', null);
- this.set('themeFatalErrors', null);
- this.set('showThemeWarningsModal', false);
- this.set('showThemeErrorsModal', false);
- },
-
- hideUpgradeModal() {
- this.set('displayUpgradeModal', false);
- },
-
- reset() {}
- },
-
- _deleteTheme() {
- let theme = this.store.peekRecord('theme', this.themeToDelete.name);
-
- if (!theme) {
- return;
- }
-
- return theme.destroyRecord().then(() => {
- // HACK: this is a private method, we need to unload from the store
- // here so that uploading another theme with the same "id" doesn't
- // attempt to update the deleted record
- theme.unloadRecord();
- }).catch((error) => {
- this.notifications.showAPIError(error);
- });
- }
-});
diff --git a/ghost/admin/app/controllers/settings/theme/install.js b/ghost/admin/app/controllers/settings/theme/install.js
deleted file mode 100644
index a242e4c75f..0000000000
--- a/ghost/admin/app/controllers/settings/theme/install.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import Controller from '@ember/controller';
-import {action} from '@ember/object';
-import {inject as service} from '@ember/service';
-import {tracked} from '@glimmer/tracking';
-
-export default class InstallThemeController extends Controller {
- @service router;
-
- queryParams = ['source', 'ref'];
-
- @tracked source = '';
- @tracked ref = '';
-
- @action
- close() {
- this.router.transitionTo('settings.theme');
- }
-}
diff --git a/ghost/admin/app/controllers/settings/theme/uploadtheme.js b/ghost/admin/app/controllers/settings/theme/uploadtheme.js
deleted file mode 100644
index 3e8889fd55..0000000000
--- a/ghost/admin/app/controllers/settings/theme/uploadtheme.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import Controller from '@ember/controller';
-import {inject as service} from '@ember/service';
-
-export default class UploadThemeController extends Controller {
- @service limit;
-
- get isAllowed() {
- return !this.limit.limiter.isLimited('customThemes');
- }
-}
diff --git a/ghost/admin/app/router.js b/ghost/admin/app/router.js
index e8dc7bbf2e..35140b79f4 100644
--- a/ghost/admin/app/router.js
+++ b/ghost/admin/app/router.js
@@ -74,12 +74,6 @@ Router.map(function () {
this.route('settings.integrations.unsplash', {path: '/settings/integrations/unsplash'});
this.route('settings.integrations.zapier', {path: '/settings/integrations/zapier'});
- // TODO: remove in customThemeSettings cleanup
- // this.route('settings.theme', {path: '/settings/theme'}, function () {
- // this.route('uploadtheme');
- // this.route('install');
- // });
-
this.route('settings.navigation', {path: '/settings/navigation'});
this.route('settings.labs', {path: '/settings/labs'});
diff --git a/ghost/admin/app/routes/settings/design.js b/ghost/admin/app/routes/settings/design.js
index 6c73c601ab..4122e1b2bf 100644
--- a/ghost/admin/app/routes/settings/design.js
+++ b/ghost/admin/app/routes/settings/design.js
@@ -15,10 +15,6 @@ export default class SettingsDesignRoute extends AuthenticatedRoute {
if (!this.session.user.isAdmin) {
return this.transitionTo('site');
}
-
- if (!this.feature.customThemeSettings) {
- return this.transitionTo('settings');
- }
}
model() {
diff --git a/ghost/admin/app/routes/settings/theme.js b/ghost/admin/app/routes/settings/theme.js
deleted file mode 100644
index 535468d47b..0000000000
--- a/ghost/admin/app/routes/settings/theme.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
-import CurrentUserSettings from 'ghost-admin/mixins/current-user-settings';
-import RSVP from 'rsvp';
-import {inject as service} from '@ember/service';
-
-export default AuthenticatedRoute.extend(CurrentUserSettings, {
- feature: service(),
- settings: service(),
-
- beforeModel() {
- this._super(...arguments);
- this.transitionAuthor(this.session.user);
-
- if (this.feature.customThemeSettings) {
- this.transitionTo('settings.design');
- }
- },
-
- model() {
- return RSVP.hash({
- settings: this.settings.reload(),
- themes: this.store.findAll('theme')
- });
- },
-
- setupController(controller) {
- controller.set('themes', this.store.peekAll('theme'));
- this.controller.send('reset');
- },
-
- actions: {
- activateTheme(theme) {
- return this.controller.send('activateTheme', theme);
- }
- },
-
- buildRouteInfoMetadata() {
- return {
- titleToken: 'Settings - Theme'
- };
- }
-});
diff --git a/ghost/admin/app/services/feature.js b/ghost/admin/app/services/feature.js
index e203a72742..b5904bc253 100644
--- a/ghost/admin/app/services/feature.js
+++ b/ghost/admin/app/services/feature.js
@@ -49,7 +49,6 @@ export default Service.extend({
nightShift: feature('nightShift', {user: true, onChange: '_setAdminTheme'}),
multipleProducts: feature('multipleProducts'),
oauthLogin: feature('oauthLogin'),
- customThemeSettings: feature('customThemeSettings'),
membersActivity: feature('membersActivity'),
cardSettingsPanel: feature('cardSettingsPanel'),
membersAutoLogin: feature('membersAutoLogin'),
diff --git a/ghost/admin/app/styles/app-dark.css b/ghost/admin/app/styles/app-dark.css
index ddf90460ee..7edeae7c82 100644
--- a/ghost/admin/app/styles/app-dark.css
+++ b/ghost/admin/app/styles/app-dark.css
@@ -537,19 +537,15 @@ input:focus,
background-color: var(--black-90);
}
-.td-cta-box {
- background: #191b1f;
-}
-
.td-item-empty {
background: var(--whitegrey-l1);
}
-.gh-themes-container-labs {
+.gh-themes-container {
background: var(--whitegrey-l2);
}
-.gh-themes-container-labs .apps-grid {
+.gh-themes-container .apps-grid {
background: none;
}
diff --git a/ghost/admin/app/styles/layouts/settings.css b/ghost/admin/app/styles/layouts/settings.css
index 3f93613282..9ca60088d3 100644
--- a/ghost/admin/app/styles/layouts/settings.css
+++ b/ghost/admin/app/styles/layouts/settings.css
@@ -405,19 +405,6 @@
stroke-width: 4px;
}
-.gh-theme-directory-container {
- padding: 25px 0 0;
-}
-
-.theme-directory {
- display: grid;
- justify-content: space-between;
- grid-template-columns: 1fr 1fr 1fr 1fr;
- grid-gap: 25px;
- max-width: 1320px;
- margin: 0 0 4vw;
-}
-
.td-item {
display: flex;
flex-direction: column;
@@ -505,22 +492,7 @@
width: 80px;
}
-@media (max-width: 1200px) {
- .td-cta {
- grid-template-columns: 1fr;
- }
-}
-
-@media (max-width: 1100px) {
- .theme-directory {
- grid-template-columns: 1fr 1fr 1fr;
- }
-}
-
@media (max-width: 1000px) {
- .theme-directory {
- grid-template-columns: 1fr 1fr 1fr;
- }
.td-item:nth-child(4),
.td-item:nth-child(5),
.td-item:nth-child(6) {
@@ -528,16 +500,6 @@
}
}
-@media (max-width: 600px) {
- .theme-directory {
- grid-template-columns: 1fr 1fr;
- margin-bottom: 25px;
- }
- .td-cta {
- margin: 50px 0;
- }
-}
-
/* General
/* ---------------------------------------------------------- */
@@ -1713,29 +1675,29 @@ p.theme-validation-details {
stroke: var(--darkgrey);
}
-.gh-themes-container-labs {
+.gh-themes-container {
margin-bottom: 40px;
background: var(--main-color-content-greybg);
border-radius: var(--border-radius);
}
-.gh-themes-container-labs .apps-grid-cell {
+.gh-themes-container .apps-grid-cell {
background: none;
}
-.gh-themes-container-labs .apps-grid-cell:hover {
+.gh-themes-container .apps-grid-cell:hover {
background: var(--whitegrey-l1);
}
-.gh-themes-container-labs .apps-card-app {
+.gh-themes-container .apps-card-app {
padding: 16px 24px;
}
-.gh-themes-container-labs .apps-grid-cell:last-of-type .apps-card-app {
+.gh-themes-container .apps-grid-cell:last-of-type .apps-card-app {
border-bottom: none;
}
-.gh-themes-container-labs .apps-configured-action {
+.gh-themes-container .apps-configured-action {
display: block;
margin-right: 16px;
padding: 2px 6px;
@@ -1743,15 +1705,15 @@ p.theme-validation-details {
border-radius: var(--border-radius);
}
-.gh-themes-container-labs .gh-btn-icon {
+.gh-themes-container .gh-btn-icon {
background: none;
}
-.gh-themes-container-labs .gh-btn-icon:hover {
+.gh-themes-container .gh-btn-icon:hover {
background: var(--whitegrey-d1);
}
-.gh-themes-container-labs .gh-btn-icon svg {
+.gh-themes-container .gh-btn-icon svg {
margin-right: 0;
}
@@ -1760,19 +1722,19 @@ p.theme-validation-details {
}
@media (max-width: 500px) {
- .gh-themes-container-labs .apps-configured {
+ .gh-themes-container .apps-configured {
justify-content: flex-end;
}
- .gh-themes-container-labs .apps-card-meta {
+ .gh-themes-container .apps-card-meta {
flex-basis: auto;
}
}
-.gh-theme-directory-container-labs {
+.gh-theme-directory-container {
padding: 8px 0 0;
}
-.theme-directory-labs {
+.theme-directory {
display: grid;
justify-content: space-between;
grid-template-columns: 1fr 1fr 1fr;
@@ -1782,32 +1744,32 @@ p.theme-validation-details {
}
@media (min-width: 1800px) {
- .theme-directory-labs {
+ .theme-directory {
grid-template-columns: 1fr 1fr 1fr 1fr;
}
}
@media (max-width: 1120px) {
- .theme-directory-labs {
+ .theme-directory {
grid-template-columns: 1fr 1fr;
}
}
@media (min-width: 800px) and (max-width: 890px) {
- .theme-directory-labs {
+ .theme-directory {
grid-template-columns: 1fr;
}
}
@media (max-width: 800px) {
- .theme-directory-labs {
+ .theme-directory {
grid-column-gap: 32px;
grid-row-gap: 48px;
}
}
@media (max-width: 430px) {
- .theme-directory-labs {
+ .theme-directory {
grid-template-columns: 1fr;
}
}
@@ -1862,7 +1824,7 @@ p.theme-validation-details {
border-radius: 0 0 3px 3px;
}
-.theme-directory-labs .td-item-desc {
+.theme-directory .td-item-desc {
display: flex;
flex-direction: column;
}
diff --git a/ghost/admin/app/templates/dashboard.hbs b/ghost/admin/app/templates/dashboard.hbs
index 20fe584d85..acd37c06ae 100644
--- a/ghost/admin/app/templates/dashboard.hbs
+++ b/ghost/admin/app/templates/dashboard.hbs
@@ -164,7 +164,7 @@
-
Customize your site{{unless this.feature.customThemeSettings " design"}}
+
Customize your site
Stand out from the crowd. Ghost lets you customize everything so you can create a publication that doesn’t just look the same as what everyone else has.