mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-24 06:35:49 +03:00
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:
parent
099cc91a90
commit
bad68bd7c2
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -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'),
|
||||||
|
7
ghost/admin/app/models/theme.js
Normal file
7
ghost/admin/app/models/theme.js
Normal 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')
|
||||||
|
});
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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: {
|
||||||
|
5
ghost/admin/app/serializers/theme.js
Normal file
5
ghost/admin/app/serializers/theme.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import ApplicationSerializer from './application';
|
||||||
|
|
||||||
|
export default ApplicationSerializer.extend({
|
||||||
|
primaryKey: 'name'
|
||||||
|
});
|
@ -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>
|
||||||
|
@ -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")}}
|
||||||
|
@ -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")
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
@ -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',
|
||||||
|
19
ghost/admin/mirage/fixtures/themes.js
Normal file
19
ghost/admin/mirage/fixtures/themes.js
Normal 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'
|
||||||
|
}
|
||||||
|
];
|
4
ghost/admin/mirage/models/theme.js
Normal file
4
ghost/admin/mirage/models/theme.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import {Model} from 'ember-cli-mirage';
|
||||||
|
|
||||||
|
export default Model.extend({
|
||||||
|
});
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user