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

View File

@ -14,7 +14,7 @@ export default ModalComponent.extend({
accept: ['application/zip', 'application/x-zip-compressed'],
extensions: ['zip'],
availableThemes: null,
themes: null,
closeDisabled: false,
file: null,
theme: false,
@ -34,7 +34,7 @@ export default ModalComponent.extend({
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 () {
let file = this.get('file');
@ -43,6 +43,7 @@ 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;
}),
@ -50,7 +51,7 @@ export default ModalComponent.extend({
validateTheme(file) {
let themeName = file.name.replace(/\.zip$/, '').replace(/[^\w@.]/gi, '-');
let availableThemeNames = this.get('availableThemeNames');
let currentThemeNames = this.get('currentThemeNames');
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.'}]};
}
if (!this._allowOverwrite && availableThemeNames.includes(themeName)) {
if (!this._allowOverwrite && currentThemeNames.includes(themeName)) {
this.set('displayOverwriteWarning', true);
return false;
}

View File

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

View File

@ -16,7 +16,6 @@ export default Model.extend(ValidationEngine, {
forceI18n: attr('boolean'),
permalinks: attr('string'),
activeTheme: attr('string'),
availableThemes: attr(),
activeTimezone: attr('string', {defaultValue: 'Etc/UTC'}),
ghost_head: 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() {
return RSVP.hash({
settings: this.querySettings(),
themes: this.get('store').findAll('theme'),
availableTimezones: this.get('config.availableTimezones')
});
},
setupController(controller, models) {
controller.set('model', models.settings);
controller.set('themes', this.get('store').peekAll('theme'));
controller.set('availableTimezones', models.availableTimezones);
},
@ -42,10 +44,14 @@ export default AuthenticatedRoute.extend(styleBody, CurrentUserSettings, {
reloadSettings() {
return this.querySettings((settings) => {
this.set('controller.model', settings);
this.get('controller').set('model', settings);
});
},
reloadThemes() {
return this.get('store').findAll('theme');
},
activateTheme(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({
model() {
return this.modelFor('settings.general').settings.get('availableThemes');
return this.get('store').findAll('theme');
},
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">
{{#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-body">
<span class="name">{{theme.label}}</span>

View File

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

View File

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

View File

@ -32,19 +32,6 @@ export default function mockSettings(server) {
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 {
meta: {},
settings: db.settings

View File

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

View File

@ -193,31 +193,6 @@ export default [
updated_by: 1,
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,
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
// - deletes theme and refreshes list
server.loadFixtures('themes');
visit('/settings/general');
// 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() {
this.set('availableThemes', [
{name: 'Daring', package: {name: 'Daring', version: '0.1.4'}, active: true},
this.set('themes', [
{name: 'Daring', package: {name: 'Daring', version: '0.1.4'}},
{name: 'casper', package: {name: 'Casper', version: '1.3.1'}},
{name: 'oscar-ghost-1.1.0', package: {name: 'Lanyon', version: '1.1.0'}},
{name: 'foo'}
@ -22,7 +22,8 @@ describe('Integration: Component: gh-theme-table', function() {
this.set('actionHandler', sinon.spy());
this.render(hbs`{{gh-theme-table
availableThemes=availableThemes
themes=themes
activeTheme="Daring"
activateTheme=(action actionHandler)
downloadTheme=(action actionHandler)
deleteTheme=(action actionHandler)
@ -85,7 +86,7 @@ describe('Integration: Component: gh-theme-table', function() {
let deleteAction = sinon.spy();
let actionHandler = sinon.spy();
this.set('availableThemes', [
this.set('themes', [
{name: 'Foo', active: true},
{name: 'Bar'}
]);
@ -93,7 +94,7 @@ describe('Integration: Component: gh-theme-table', function() {
this.set('actionHandler', actionHandler);
this.render(hbs`{{gh-theme-table
availableThemes=availableThemes
themes=themes
activateTheme=(action actionHandler)
downloadTheme=(action actionHandler)
deleteTheme=(action deleteAction)
@ -111,7 +112,7 @@ describe('Integration: Component: gh-theme-table', function() {
let downloadAction = sinon.spy();
let actionHandler = sinon.spy();
this.set('availableThemes', [
this.set('themes', [
{name: 'Foo', active: true},
{name: 'Bar'}
]);
@ -119,7 +120,7 @@ describe('Integration: Component: gh-theme-table', function() {
this.set('actionHandler', actionHandler);
this.render(hbs`{{gh-theme-table
availableThemes=availableThemes
themes=themes
activateTheme=(action actionHandler)
downloadTheme=(action downloadAction)
deleteTheme=(action actionHandler)
@ -137,7 +138,7 @@ describe('Integration: Component: gh-theme-table', function() {
let activateAction = sinon.spy();
let actionHandler = sinon.spy();
this.set('availableThemes', [
this.set('themes', [
{name: 'Foo', active: true},
{name: 'Bar'}
]);
@ -145,7 +146,7 @@ describe('Integration: Component: gh-theme-table', function() {
this.set('actionHandler', actionHandler);
this.render(hbs`{{gh-theme-table
availableThemes=availableThemes
themes=themes
activateTheme=(action activateAction)
downloadTheme=(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 () {
this.set('availableThemes', [
this.set('themes', [
{name: 'daring', package: {name: 'Daring', version: '0.1.4'}, active: true},
{name: 'daring-0.1.5', package: {name: 'Daring', version: '0.1.4'}},
{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.render(hbs`{{gh-theme-table
availableThemes=availableThemes
themes=themes
activateTheme=(action actionHandler)
downloadTheme=(action actionHandler)
deleteTheme=(action actionHandler)