fetch themes from /themes endpoint (#542)

refs https://github.com/TryGhost/Ghost/pull/8022

- use `/themes` API endpoint to fetch list of themes instead of `settings[0].availableThemes`
This commit is contained in:
Kevin Ansfield 2017-02-21 18:28:44 +00:00 committed by Hannah Wolfe
parent 099cc91a90
commit bad68bd7c2
18 changed files with 88 additions and 87 deletions

View File

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

View File

@ -14,7 +14,7 @@ export default ModalComponent.extend({
accept: ['application/zip', 'application/x-zip-compressed'], accept: ['application/zip', 'application/x-zip-compressed'],
extensions: ['zip'], extensions: ['zip'],
availableThemes: null, themes: null,
closeDisabled: false, closeDisabled: false,
file: null, file: null,
theme: false, theme: false,
@ -34,7 +34,7 @@ export default ModalComponent.extend({
return t.package ? `${t.package.name} - ${t.package.version}` : t.name; return t.package ? `${t.package.name} - ${t.package.version}` : t.name;
}), }),
availableThemeNames: mapBy('model.availableThemes', 'name'), currentThemeNames: mapBy('model.themes', 'name'),
fileThemeName: computed('file', function () { fileThemeName: computed('file', function () {
let file = this.get('file'); let file = this.get('file');
@ -43,6 +43,7 @@ export default ModalComponent.extend({
canActivateTheme: computed('theme', function () { canActivateTheme: computed('theme', function () {
let theme = this.get('theme'); 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.active;
}), }),
@ -50,7 +51,7 @@ export default ModalComponent.extend({
validateTheme(file) { validateTheme(file) {
let themeName = file.name.replace(/\.zip$/, '').replace(/[^\w@.]/gi, '-'); let themeName = file.name.replace(/\.zip$/, '').replace(/[^\w@.]/gi, '-');
let availableThemeNames = this.get('availableThemeNames'); let currentThemeNames = this.get('currentThemeNames');
this.set('file', file); this.set('file', file);
@ -65,7 +66,7 @@ export default ModalComponent.extend({
return {errors: [{message: 'Sorry, the default Casper theme cannot be overwritten.<br>Please rename your zip file and try again.'}]}; return {errors: [{message: 'Sorry, the default Casper theme cannot be overwritten.<br>Please rename your zip file and try again.'}]};
} }
if (!this._allowOverwrite && availableThemeNames.includes(themeName)) { if (!this._allowOverwrite && currentThemeNames.includes(themeName)) {
this.set('displayOverwriteWarning', true); this.set('displayOverwriteWarning', true);
return false; return false;
} }

View File

@ -9,6 +9,7 @@ import $ from 'jquery';
export default Controller.extend(SettingsSaveMixin, { export default Controller.extend(SettingsSaveMixin, {
themes: null,
availableTimezones: null, availableTimezones: null,
themeToDelete: null, themeToDelete: null,
@ -51,16 +52,13 @@ export default Controller.extend(SettingsSaveMixin, {
}), }),
_deleteTheme() { _deleteTheme() {
let theme = this.get('themeToDelete'); let theme = this.get('store').peekRecord('theme', this.get('themeToDelete').name);
let themeURL = `${this.get('ghostPaths.apiRoot')}/themes/${theme.name}/`;
if (!theme) { if (!theme) {
return; return;
} }
return this.get('ajax').del(themeURL).then(() => { return theme.destroyRecord().catch((error) => {
this.send('reloadSettings');
}).catch((error) => {
this.get('notifications').showAPIError(error); this.get('notifications').showAPIError(error);
}); });
}, },

View File

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

View File

@ -0,0 +1,7 @@
import Model from 'ember-data/model';
import attr from 'ember-data/attr';
export default Model.extend({
name: attr('string'),
package: attr('raw')
});

View File

@ -26,12 +26,14 @@ export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
model() { model() {
return RSVP.hash({ return RSVP.hash({
settings: this.querySettings(), settings: this.querySettings(),
themes: this.get('store').findAll('theme'),
availableTimezones: this.get('config.availableTimezones') availableTimezones: this.get('config.availableTimezones')
}); });
}, },
setupController(controller, models) { setupController(controller, models) {
controller.set('model', models.settings); controller.set('model', models.settings);
controller.set('themes', this.get('store').peekAll('theme'));
controller.set('availableTimezones', models.availableTimezones); controller.set('availableTimezones', models.availableTimezones);
}, },
@ -42,10 +44,14 @@ export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
reloadSettings() { reloadSettings() {
return this.querySettings((settings) => { return this.querySettings((settings) => {
this.set('controller.model', settings); this.get('controller').set('model', settings);
}); });
}, },
reloadThemes() {
return this.get('store').findAll('theme');
},
activateTheme(theme) { activateTheme(theme) {
return this.get('controller').send('setTheme', theme); return this.get('controller').send('setTheme', theme);
} }

View File

@ -3,7 +3,7 @@ import AuthenticatedRoute from 'ghost-admin/routes/authenticated';
export default AuthenticatedRoute.extend({ export default AuthenticatedRoute.extend({
model() { model() {
return this.modelFor('settings.general').settings.get('availableThemes'); return this.get('store').findAll('theme');
}, },
actions: { actions: {

View File

@ -0,0 +1,5 @@
import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
primaryKey: 'name'
});

View File

@ -1,6 +1,6 @@
{{#if themes}} {{#if sortedThemes}}
<div class="theme-list"> <div class="theme-list">
{{#each themes as |theme|}} {{#each sortedThemes as |theme|}}
<div class="theme-list-item {{if theme.active "theme-list-item--active"}}"> <div class="theme-list-item {{if theme.active "theme-list-item--active"}}">
<div class="theme-list-item-body"> <div class="theme-list-item-body">
<span class="name">{{theme.label}}</span> <span class="name">{{theme.label}}</span>

View File

@ -175,7 +175,8 @@
<h3 id="themes">Themes</h3> <h3 id="themes">Themes</h3>
{{gh-theme-table {{gh-theme-table
availableThemes=model.availableThemes themes=themes
activeTheme=model.activeTheme
activateTheme=(action "setTheme") activateTheme=(action "setTheme")
downloadTheme=(action "downloadTheme") downloadTheme=(action "downloadTheme")
deleteTheme=(action "deleteTheme")}} deleteTheme=(action "deleteTheme")}}

View File

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

View File

@ -32,19 +32,6 @@ export default function mockSettings(server) {
db.settings.update({key}, newSetting); db.settings.update({key}, newSetting);
}); });
let [activeTheme] = db.settings.where({key: 'activeTheme'});
let [availableThemes] = db.settings.where({key: 'availableThemes'});
availableThemes.value.forEach((theme) => {
if (theme.name === activeTheme.value) {
theme.active = true;
} else {
theme.active = false;
}
});
db.settings.update({key: 'availableThemes'}, availableThemes);
return { return {
meta: {}, meta: {},
settings: db.settings settings: db.settings

View File

@ -3,8 +3,9 @@ import {Response} from 'ember-cli-mirage';
let themeCount = 1; let themeCount = 1;
export default function mockThemes(server) { export default function mockThemes(server) {
server.post('/themes/upload/', function ({db}) { server.get('/themes');
let [availableThemes] = db.settings.where({key: 'availableThemes'});
server.post('/themes/upload/', function ({themes}) {
// pretender/mirage doesn't currently process FormData so we can't use // pretender/mirage doesn't currently process FormData so we can't use
// any info passed in through the request // any info passed in through the request
let theme = { let theme = {
@ -12,28 +13,20 @@ export default function mockThemes(server) {
package: { package: {
name: `Test ${themeCount}`, name: `Test ${themeCount}`,
version: '0.1' version: '0.1'
}, }
active: false
}; };
themeCount++; themeCount++;
availableThemes.value.pushObject(theme); theme = themes.create(theme);
db.settings.update({key: 'availableThemes'}, availableThemes);
return { return {
themes: [theme] themes: [theme]
}; };
}); });
server.del('/themes/:theme/', function ({db}, {params}) { server.del('/themes/:theme/', function ({themes}, {params}) {
let [availableThemes] = db.settings.where({key: 'availableThemes'}); themes.findBy({name: params.theme}).destroy();
availableThemes.value = availableThemes.value.filter((theme) => {
return theme.name !== params.theme;
});
db.settings.update({key: 'availableThemes'}, availableThemes);
return new Response(204, {}, null); return new Response(204, {}, null);
}); });

View File

@ -193,31 +193,6 @@ export default [
updated_by: 1, updated_by: 1,
value: 'Etc/UTC' value: 'Etc/UTC'
}, },
{
id: 20,
key: 'availableThemes',
value: [
{
name: 'casper',
package: {
name: 'Blog',
version: '1.0'
},
active: true
},
{
name: 'foo',
package: {
name: 'Foo',
version: '0.1'
}
},
{
name: 'bar'
}
],
type: 'theme'
},
{ {
id: 21, id: 21,
created_at: '2017-01-09T08:40:59.000Z', created_at: '2017-01-09T08:40:59.000Z',

View File

@ -0,0 +1,19 @@
export default [
{
name: 'casper',
package: {
name: 'Blog',
version: '1.0'
}
},
{
name: 'foo',
package: {
name: 'Foo',
version: '0.1'
}
},
{
name: 'bar'
}
];

View File

@ -0,0 +1,4 @@
import {Model} from 'ember-cli-mirage';
export default Model.extend({
});

View File

@ -380,6 +380,7 @@ describe('Acceptance: Settings - General', function () {
// - displays modal // - displays modal
// - deletes theme and refreshes list // - deletes theme and refreshes list
server.loadFixtures('themes');
visit('/settings/general'); visit('/settings/general');
// lists available themes (themes are specified in mirage/fixtures/settings) // lists available themes (themes are specified in mirage/fixtures/settings)

View File

@ -13,8 +13,8 @@ describe('Integration: Component: gh-theme-table', function() {
}); });
it('renders', function() { it('renders', function() {
this.set('availableThemes', [ this.set('themes', [
{name: 'Daring', package: {name: 'Daring', version: '0.1.4'}, active: true}, {name: 'Daring', package: {name: 'Daring', version: '0.1.4'}},
{name: 'casper', package: {name: 'Casper', version: '1.3.1'}}, {name: 'casper', package: {name: 'Casper', version: '1.3.1'}},
{name: 'oscar-ghost-1.1.0', package: {name: 'Lanyon', version: '1.1.0'}}, {name: 'oscar-ghost-1.1.0', package: {name: 'Lanyon', version: '1.1.0'}},
{name: 'foo'} {name: 'foo'}
@ -22,7 +22,8 @@ describe('Integration: Component: gh-theme-table', function() {
this.set('actionHandler', sinon.spy()); this.set('actionHandler', sinon.spy());
this.render(hbs`{{gh-theme-table this.render(hbs`{{gh-theme-table
availableThemes=availableThemes themes=themes
activeTheme="Daring"
activateTheme=(action actionHandler) activateTheme=(action actionHandler)
downloadTheme=(action actionHandler) downloadTheme=(action actionHandler)
deleteTheme=(action actionHandler) deleteTheme=(action actionHandler)
@ -85,7 +86,7 @@ describe('Integration: Component: gh-theme-table', function() {
let deleteAction = sinon.spy(); let deleteAction = sinon.spy();
let actionHandler = sinon.spy(); let actionHandler = sinon.spy();
this.set('availableThemes', [ this.set('themes', [
{name: 'Foo', active: true}, {name: 'Foo', active: true},
{name: 'Bar'} {name: 'Bar'}
]); ]);
@ -93,7 +94,7 @@ describe('Integration: Component: gh-theme-table', function() {
this.set('actionHandler', actionHandler); this.set('actionHandler', actionHandler);
this.render(hbs`{{gh-theme-table this.render(hbs`{{gh-theme-table
availableThemes=availableThemes themes=themes
activateTheme=(action actionHandler) activateTheme=(action actionHandler)
downloadTheme=(action actionHandler) downloadTheme=(action actionHandler)
deleteTheme=(action deleteAction) deleteTheme=(action deleteAction)
@ -111,7 +112,7 @@ describe('Integration: Component: gh-theme-table', function() {
let downloadAction = sinon.spy(); let downloadAction = sinon.spy();
let actionHandler = sinon.spy(); let actionHandler = sinon.spy();
this.set('availableThemes', [ this.set('themes', [
{name: 'Foo', active: true}, {name: 'Foo', active: true},
{name: 'Bar'} {name: 'Bar'}
]); ]);
@ -119,7 +120,7 @@ describe('Integration: Component: gh-theme-table', function() {
this.set('actionHandler', actionHandler); this.set('actionHandler', actionHandler);
this.render(hbs`{{gh-theme-table this.render(hbs`{{gh-theme-table
availableThemes=availableThemes themes=themes
activateTheme=(action actionHandler) activateTheme=(action actionHandler)
downloadTheme=(action downloadAction) downloadTheme=(action downloadAction)
deleteTheme=(action actionHandler) deleteTheme=(action actionHandler)
@ -137,7 +138,7 @@ describe('Integration: Component: gh-theme-table', function() {
let activateAction = sinon.spy(); let activateAction = sinon.spy();
let actionHandler = sinon.spy(); let actionHandler = sinon.spy();
this.set('availableThemes', [ this.set('themes', [
{name: 'Foo', active: true}, {name: 'Foo', active: true},
{name: 'Bar'} {name: 'Bar'}
]); ]);
@ -145,7 +146,7 @@ describe('Integration: Component: gh-theme-table', function() {
this.set('actionHandler', actionHandler); this.set('actionHandler', actionHandler);
this.render(hbs`{{gh-theme-table this.render(hbs`{{gh-theme-table
availableThemes=availableThemes themes=themes
activateTheme=(action activateAction) activateTheme=(action activateAction)
downloadTheme=(action actionHandler) downloadTheme=(action actionHandler)
deleteTheme=(action actionHandler) deleteTheme=(action actionHandler)
@ -160,7 +161,7 @@ describe('Integration: Component: gh-theme-table', function() {
}); });
it('displays folder names if there are duplicate package names', function () { it('displays folder names if there are duplicate package names', function () {
this.set('availableThemes', [ this.set('themes', [
{name: 'daring', package: {name: 'Daring', version: '0.1.4'}, active: true}, {name: 'daring', package: {name: 'Daring', version: '0.1.4'}, active: true},
{name: 'daring-0.1.5', package: {name: 'Daring', version: '0.1.4'}}, {name: 'daring-0.1.5', package: {name: 'Daring', version: '0.1.4'}},
{name: 'casper', package: {name: 'Casper', version: '1.3.1'}}, {name: 'casper', package: {name: 'Casper', version: '1.3.1'}},
@ -171,7 +172,7 @@ describe('Integration: Component: gh-theme-table', function() {
this.set('actionHandler', sinon.spy()); this.set('actionHandler', sinon.spy());
this.render(hbs`{{gh-theme-table this.render(hbs`{{gh-theme-table
availableThemes=availableThemes themes=themes
activateTheme=(action actionHandler) activateTheme=(action actionHandler)
downloadTheme=(action actionHandler) downloadTheme=(action actionHandler)
deleteTheme=(action actionHandler) deleteTheme=(action actionHandler)