mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-25 03:44:29 +03:00
🎨 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:
parent
f26ddc68dd
commit
96743e64cd
13
ghost/admin/app/adapters/theme.js
Normal file
13
ghost/admin/app/adapters/theme.js
Normal 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);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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'),
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -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}}
|
||||
|
@ -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")}}
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -4,7 +4,8 @@ export default [
|
||||
package: {
|
||||
name: 'Blog',
|
||||
version: '1.0'
|
||||
}
|
||||
},
|
||||
active: true
|
||||
},
|
||||
{
|
||||
name: 'foo',
|
||||
|
@ -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(),
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user