2017-09-11 10:41:17 +03:00
|
|
|
import ModalComponent from 'ghost-admin/components/modal-base';
|
2016-08-17 18:01:46 +03:00
|
|
|
import ghostPaths from 'ghost-admin/utils/ghost-paths';
|
2016-08-24 21:22:20 +03:00
|
|
|
import {
|
|
|
|
UnsupportedMediaTypeError,
|
|
|
|
isThemeValidationError
|
|
|
|
} from 'ghost-admin/services/ajax';
|
2017-08-22 10:53:26 +03:00
|
|
|
import {computed} from '@ember/object';
|
|
|
|
import {get} from '@ember/object';
|
|
|
|
import {inject as injectService} from '@ember/service';
|
2017-05-29 21:50:03 +03:00
|
|
|
import {invokeAction} from 'ember-invoke-action';
|
2017-08-22 10:53:26 +03:00
|
|
|
import {mapBy, or} from '@ember/object/computed';
|
|
|
|
import {run} from '@ember/runloop';
|
2016-08-17 18:01:46 +03:00
|
|
|
|
|
|
|
export default ModalComponent.extend({
|
|
|
|
|
2016-09-01 09:50:34 +03:00
|
|
|
accept: ['application/zip', 'application/x-zip-compressed'],
|
2016-09-14 11:54:16 +03:00
|
|
|
extensions: ['zip'],
|
2017-02-21 21:28:44 +03:00
|
|
|
themes: null,
|
2016-08-17 18:01:46 +03:00
|
|
|
closeDisabled: false,
|
|
|
|
file: null,
|
|
|
|
theme: false,
|
|
|
|
displayOverwriteWarning: false,
|
|
|
|
|
|
|
|
eventBus: injectService(),
|
2017-03-03 18:31:42 +03:00
|
|
|
store: injectService(),
|
2016-08-17 18:01:46 +03:00
|
|
|
|
|
|
|
hideUploader: or('theme', 'displayOverwriteWarning'),
|
|
|
|
|
|
|
|
uploadUrl: computed(function () {
|
|
|
|
return `${ghostPaths().apiRoot}/themes/upload/`;
|
|
|
|
}),
|
|
|
|
|
|
|
|
themeName: computed('theme.{name,package.name}', function () {
|
2017-03-03 18:31:42 +03:00
|
|
|
let themePackage = this.get('theme.package');
|
|
|
|
let name = this.get('theme.name');
|
2016-08-17 18:01:46 +03:00
|
|
|
|
2017-03-03 18:31:42 +03:00
|
|
|
return themePackage ? `${themePackage.name} - ${themePackage.version}` : name;
|
2016-08-17 18:01:46 +03:00
|
|
|
}),
|
|
|
|
|
2017-02-21 21:28:44 +03:00
|
|
|
currentThemeNames: mapBy('model.themes', 'name'),
|
2016-08-17 18:01:46 +03:00
|
|
|
|
|
|
|
fileThemeName: computed('file', function () {
|
|
|
|
let file = this.get('file');
|
|
|
|
return file.name.replace(/\.zip$/, '');
|
|
|
|
}),
|
|
|
|
|
|
|
|
canActivateTheme: computed('theme', function () {
|
|
|
|
let theme = this.get('theme');
|
2017-03-03 18:31:42 +03:00
|
|
|
return theme && !theme.get('active');
|
2016-08-17 18:01:46 +03:00
|
|
|
}),
|
|
|
|
|
|
|
|
actions: {
|
|
|
|
validateTheme(file) {
|
2017-03-03 18:31:42 +03:00
|
|
|
let themeName = file.name.replace(/\.zip$/, '').replace(/[^\w@.]/gi, '-').toLowerCase();
|
2016-09-14 20:23:04 +03:00
|
|
|
|
2017-02-21 21:28:44 +03:00
|
|
|
let currentThemeNames = this.get('currentThemeNames');
|
2016-08-17 18:01:46 +03:00
|
|
|
|
|
|
|
this.set('file', file);
|
|
|
|
|
2016-09-14 11:54:16 +03:00
|
|
|
let [, extension] = (/(?:\.([^.]+))?$/).exec(file.name);
|
|
|
|
let extensions = this.get('extensions');
|
|
|
|
|
|
|
|
if (!extension || extensions.indexOf(extension.toLowerCase()) === -1) {
|
2016-08-17 18:01:46 +03:00
|
|
|
return new UnsupportedMediaTypeError();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (file.name.match(/^casper\.zip$/i)) {
|
|
|
|
return {errors: [{message: 'Sorry, the default Casper theme cannot be overwritten.<br>Please rename your zip file and try again.'}]};
|
|
|
|
}
|
|
|
|
|
2017-02-21 21:28:44 +03:00
|
|
|
if (!this._allowOverwrite && currentThemeNames.includes(themeName)) {
|
2016-08-17 18:01:46 +03:00
|
|
|
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.get('eventBus').publish('themeUploader:upload', this.get('file'));
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
uploadStarted() {
|
|
|
|
this.set('closeDisabled', true);
|
|
|
|
},
|
|
|
|
|
|
|
|
uploadFinished() {
|
|
|
|
this.set('closeDisabled', false);
|
|
|
|
},
|
|
|
|
|
|
|
|
uploadSuccess(response) {
|
2017-03-03 18:31:42 +03:00
|
|
|
this.get('store').pushPayload(response);
|
|
|
|
|
|
|
|
let theme = this.get('store').peekRecord('theme', response.themes[0].name);
|
2016-09-14 20:34:07 +03:00
|
|
|
|
|
|
|
this.set('theme', theme);
|
|
|
|
|
|
|
|
if (get(theme, 'warnings.length') > 0) {
|
2017-03-03 18:31:42 +03:00
|
|
|
this.set('validationWarnings', get(theme, 'warnings'));
|
2016-09-14 20:34:07 +03:00
|
|
|
}
|
|
|
|
|
2017-06-06 09:09:52 +03:00
|
|
|
// 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'));
|
|
|
|
}
|
|
|
|
|
2017-09-01 15:46:25 +03:00
|
|
|
this.set('hasWarningsOrErrors', this.get('validationErrors.length') || this.get('validationWarnings.length'));
|
2017-06-06 09:09:52 +03:00
|
|
|
|
2016-08-17 18:01:46 +03:00
|
|
|
// invoke the passed in confirm action
|
2017-03-03 18:31:42 +03:00
|
|
|
invokeAction(this, 'model.uploadSuccess', theme);
|
2016-08-17 18:01:46 +03:00
|
|
|
},
|
|
|
|
|
2016-08-24 21:22:20 +03:00
|
|
|
uploadFailed(error) {
|
|
|
|
if (isThemeValidationError(error)) {
|
2017-06-06 09:09:52 +03:00
|
|
|
let errors = error.errors[0].errorDetails;
|
|
|
|
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
|
2017-09-11 10:41:17 +03:00
|
|
|
if (errors && errors.length > 0) {
|
2017-06-06 09:09:52 +03:00
|
|
|
for (let i = 0; i < errors.length; i++) {
|
|
|
|
if (errors[i].fatal) {
|
|
|
|
fatalErrors.push(errors[i]);
|
|
|
|
} else {
|
|
|
|
normalErrors.push(errors[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.set('fatalValidationErrors', fatalErrors);
|
|
|
|
this.set('validationErrors', normalErrors);
|
2016-08-24 21:22:20 +03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2016-08-17 18:01:46 +03:00
|
|
|
confirm() {
|
|
|
|
// noop - we don't want the enter key doing anything
|
|
|
|
},
|
|
|
|
|
|
|
|
activate() {
|
|
|
|
invokeAction(this, 'model.activate', this.get('theme'));
|
|
|
|
invokeAction(this, 'closeModal');
|
|
|
|
},
|
|
|
|
|
|
|
|
closeModal() {
|
|
|
|
if (!this.get('closeDisabled')) {
|
|
|
|
this._super(...arguments);
|
|
|
|
}
|
2016-08-24 21:22:20 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
reset() {
|
2017-06-06 09:09:52 +03:00
|
|
|
this.set('validationWarnings', []);
|
|
|
|
this.set('validationErrors', []);
|
|
|
|
this.set('fatalValidationErrors', []);
|
|
|
|
this.set('hasWarningsOrErrors', false);
|
2016-08-17 18:01:46 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|