🎨 move theme activation to /themes endpoint

requires https://github.com/TryGhost/Ghost/pull/8093
- adds `theme.activate()` method and associated adapter method for activating themes rather than relying on `settings.activeTheme`
- minor refactors to the `modals/upload-theme` component to use a full theme model
This commit is contained in:
Kevin Ansfield 2017-03-03 15:31:42 +00:00
parent f26ddc68dd
commit 96743e64cd
15 changed files with 86 additions and 73 deletions

View File

@ -0,0 +1,13 @@
import ApplicationAdapter from './application';
export default ApplicationAdapter.extend({
activate(model) {
let url = `${this.buildURL('theme', model.get('id'))}activate/`;
return this.ajax(url, 'PUT', {data: {}}).then((data) => {
return this.store.pushPayload(data);
});
}
});

View File

@ -5,19 +5,18 @@ import get from 'ember-metal/get';
export default Component.extend({
themes: null,
activeTheme: null,
sortedThemes: computed('themes.[]', 'activeTheme', function () {
let activeTheme = get(this, 'activeTheme');
sortedThemes: computed('themes.@each.active', function () {
let themes = get(this, 'themes').map((t) => {
let theme = {};
let themePackage = get(t, 'package');
theme.model = t;
theme.name = get(t, 'name');
theme.label = themePackage ? `${themePackage.name}` : theme.name;
theme.version = themePackage ? `${themePackage.version}` : '1.0';
theme.package = themePackage;
theme.active = theme.name === activeTheme;
theme.active = get(t, 'active');
theme.isDeletable = !theme.active;
return theme;

View File

@ -21,6 +21,7 @@ export default ModalComponent.extend({
displayOverwriteWarning: false,
eventBus: injectService(),
store: injectService(),
hideUploader: or('theme', 'displayOverwriteWarning'),
@ -29,9 +30,10 @@ export default ModalComponent.extend({
}),
themeName: computed('theme.{name,package.name}', function () {
let t = this.get('theme');
let themePackage = this.get('theme.package');
let name = this.get('theme.name');
return t.package ? `${t.package.name} - ${t.package.version}` : t.name;
return themePackage ? `${themePackage.name} - ${themePackage.version}` : name;
}),
currentThemeNames: mapBy('model.themes', 'name'),
@ -43,13 +45,12 @@ export default ModalComponent.extend({
canActivateTheme: computed('theme', function () {
let theme = this.get('theme');
// TODO: do we still get theme.active back or do we need to check settings.activeTheme?
return theme && !theme.active;
return theme && !theme.get('active');
}),
actions: {
validateTheme(file) {
let themeName = file.name.replace(/\.zip$/, '').replace(/[^\w@.]/gi, '-');
let themeName = file.name.replace(/\.zip$/, '').replace(/[^\w@.]/gi, '-').toLowerCase();
let currentThemeNames = this.get('currentThemeNames');
@ -94,16 +95,18 @@ export default ModalComponent.extend({
},
uploadSuccess(response) {
let [theme] = response.themes;
this.get('store').pushPayload(response);
let theme = this.get('store').peekRecord('theme', response.themes[0].name);
this.set('theme', theme);
if (get(theme, 'warnings.length') > 0) {
this.set('validationWarnings', theme.warnings);
this.set('validationWarnings', get(theme, 'warnings'));
}
// invoke the passed in confirm action
invokeAction(this, 'model.uploadSuccess', this.get('theme'));
invokeAction(this, 'model.uploadSuccess', theme);
},
uploadFailed(error) {

View File

@ -109,9 +109,8 @@ export default Controller.extend(SettingsSaveMixin, {
navItem.set('url', url);
},
setTheme(theme) {
this.set('model.activeTheme', theme.name);
this.send('save');
activateTheme(theme) {
return theme.activate();
},
downloadTheme(theme) {

View File

@ -15,7 +15,6 @@ export default Model.extend(ValidationEngine, {
postsPerPage: attr('number'),
forceI18n: attr('boolean'),
permalinks: attr('string'),
activeTheme: attr('string'),
activeTimezone: attr('string', {defaultValue: 'Etc/UTC'}),
ghost_head: attr('string'),
ghost_foot: attr('string'),

View File

@ -3,5 +3,12 @@ import attr from 'ember-data/attr';
export default Model.extend({
name: attr('string'),
package: attr('raw')
package: attr('raw'),
active: attr('boolean'),
warnings: attr('raw'),
activate() {
let adapter = this.store.adapterFor(this.constructor.modelName);
return adapter.activate(this);
}
});

View File

@ -49,12 +49,8 @@ export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
return this._super(...arguments);
},
reloadThemes() {
return this.get('store').findAll('theme');
},
activateTheme(theme) {
return this.get('controller').send('setTheme', theme);
return this.get('controller').send('activateTheme', theme);
}
}
});

View File

@ -22,7 +22,7 @@
{{#if theme.active}}
<span class="gh-badge gh-badge-black apps-configured-action" data-test-theme-badge>Active</span>
{{else}}
<a href="#" {{action activateTheme theme}} class="apps-configured-action apps-configured-action-activate green-hover" data-test-theme-activate-button>
<a href="#" {{action activateTheme theme.model}} class="apps-configured-action apps-configured-action-activate green-hover" data-test-theme-activate-button>
Activate
</a>
{{/if}}

View File

@ -23,8 +23,7 @@
<div class="gh-themes-container">
{{gh-theme-table
themes=themes
activeTheme=model.activeTheme
activateTheme=(action "setTheme")
activateTheme=(action "activateTheme")
downloadTheme=(action "downloadTheme")
deleteTheme=(action "deleteTheme")}}

View File

@ -1,7 +1,6 @@
{{gh-fullscreen-modal "upload-theme"
model=(hash
themes=model
uploadSuccess=(route-action 'reloadThemes')
activate=(route-action 'activateTheme')
)
close=(route-action "cancel")

View File

@ -30,4 +30,11 @@ export default function mockThemes(server) {
return new Response(204, {}, null);
});
server.put('/themes/:theme/activate/', function ({themes}, {params}) {
themes.all().update('active', false);
themes.findBy({name: params.theme}).update({active: true});
return themes.all();
});
}

View File

@ -70,16 +70,6 @@ export default [
updated_at: '2015-10-27T17:39:58.280Z',
updated_by: 1
},
{
id: 8,
key: 'activeTheme',
value: 'casper',
type: 'theme',
created_at: '2013-11-25T14:48:11.000Z',
created_by: 1,
updated_at: '2015-10-27T17:39:58.284Z',
updated_by: 1
},
{
id: 9,
key: 'permalinks',

View File

@ -4,7 +4,8 @@ export default [
package: {
name: 'Blog',
version: '1.0'
}
},
active: true
},
{
name: 'foo',

View File

@ -400,39 +400,40 @@ describe('Acceptance: Settings - Design', function () {
// theme upload handles validation warnings
andThen(() => {
server.post('/themes/upload/', function () {
return new Mirage.Response(200, {}, {
themes: [
server.post('/themes/upload/', function ({themes}) {
let theme = {
name: 'blackpalm',
package: {
name: 'BlackPalm',
version: '1.0.0'
}
};
themes.create(theme);
theme.warnings = [{
level: 'warning',
rule: 'Assets such as CSS & JS must use the <code>{{asset}}</code> helper',
details: '<p>The listed files should be included using the <code>{{asset}}</code> helper. For more information, please see the <a href="http://themes.ghost.org/docs/asset">asset helper documentation</a>.</p>',
failures: [
{
name: 'blackpalm',
package: {
name: 'BlackPalm',
version: '1.0.0'
},
warnings: [
{
level: 'warning',
rule: 'Assets such as CSS & JS must use the <code>{{asset}}</code> helper',
details: '<p>The listed files should be included using the <code>{{asset}}</code> helper. For more information, please see the <a href="http://themes.ghost.org/docs/asset">asset helper documentation</a>.</p>',
failures: [
{
ref: '/assets/dist/img/apple-touch-icon.png'
},
{
ref: '/assets/dist/img/favicon.ico'
},
{
ref: '/assets/dist/css/blackpalm.min.css'
},
{
ref: '/assets/dist/js/blackpalm.min.js'
}
],
code: 'GS030-ASSET-REQ'
}
]
ref: '/assets/dist/img/apple-touch-icon.png'
},
{
ref: '/assets/dist/img/favicon.ico'
},
{
ref: '/assets/dist/css/blackpalm.min.css'
},
{
ref: '/assets/dist/js/blackpalm.min.js'
}
]
],
code: 'GS030-ASSET-REQ'
}];
return new Mirage.Response(200, {}, {
themes: [theme]
});
});
});
@ -461,6 +462,7 @@ describe('Acceptance: Settings - Design', function () {
// theme upload handles success then close
click(testSelector('upload-theme-button'));
fileUpload('.fullscreen-modal input[type="file"]', ['test'], {name: 'theme-1.zip', type: 'application/zip'});
andThen(() => {
expect(
find('.fullscreen-modal h1').text().trim(),
@ -475,7 +477,7 @@ describe('Acceptance: Settings - Design', function () {
expect(
find(testSelector('theme-id')).length,
'number of themes in list grows after upload'
).to.equal(4);
).to.equal(5);
expect(
find(`${testSelector('theme-active', 'true')} ${testSelector('theme-title')}`).text().trim(),
@ -492,7 +494,7 @@ describe('Acceptance: Settings - Design', function () {
expect(
find(testSelector('theme-id')).length,
'number of themes in list grows after upload and activate'
).to.equal(5);
).to.equal(6);
expect(
find(`${testSelector('theme-active', 'true')} ${testSelector('theme-title')}`).text().trim(),
@ -546,7 +548,7 @@ describe('Acceptance: Settings - Design', function () {
expect(
find(testSelector('theme-id')).length,
'number of themes in list shrinks after delete'
).to.equal(4);
).to.equal(5);
expect(
find(testSelector('theme-title')).text(),

View File

@ -15,7 +15,7 @@ describe('Integration: Component: gh-theme-table', function() {
it('renders', function() {
this.set('themes', [
{name: 'Daring', package: {name: 'Daring', version: '0.1.4'}},
{name: 'Daring', package: {name: 'Daring', version: '0.1.4'}, active: true},
{name: 'casper', package: {name: 'Casper', version: '1.3.1'}},
{name: 'oscar-ghost-1.1.0', package: {name: 'Lanyon', version: '1.1.0'}},
{name: 'foo'}
@ -24,7 +24,6 @@ describe('Integration: Component: gh-theme-table', function() {
this.render(hbs`{{gh-theme-table
themes=themes
activeTheme="Daring"
activateTheme=(action actionHandler)
downloadTheme=(action actionHandler)
deleteTheme=(action actionHandler)