diff --git a/ghost/admin/app/adapters/theme.js b/ghost/admin/app/adapters/theme.js new file mode 100644 index 0000000000..92025e6998 --- /dev/null +++ b/ghost/admin/app/adapters/theme.js @@ -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); + }); + } + +}); diff --git a/ghost/admin/app/components/gh-theme-table.js b/ghost/admin/app/components/gh-theme-table.js index c2f4cb68be..6ffae061ed 100644 --- a/ghost/admin/app/components/gh-theme-table.js +++ b/ghost/admin/app/components/gh-theme-table.js @@ -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; diff --git a/ghost/admin/app/components/modals/upload-theme.js b/ghost/admin/app/components/modals/upload-theme.js index 56431a888c..037a758932 100644 --- a/ghost/admin/app/components/modals/upload-theme.js +++ b/ghost/admin/app/components/modals/upload-theme.js @@ -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) { diff --git a/ghost/admin/app/controllers/settings/design.js b/ghost/admin/app/controllers/settings/design.js index afc53c30c2..d6653e8a91 100644 --- a/ghost/admin/app/controllers/settings/design.js +++ b/ghost/admin/app/controllers/settings/design.js @@ -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) { diff --git a/ghost/admin/app/models/setting.js b/ghost/admin/app/models/setting.js index 0214e3b362..39771646fd 100644 --- a/ghost/admin/app/models/setting.js +++ b/ghost/admin/app/models/setting.js @@ -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'), diff --git a/ghost/admin/app/models/theme.js b/ghost/admin/app/models/theme.js index ded4933b45..c68e382d5f 100644 --- a/ghost/admin/app/models/theme.js +++ b/ghost/admin/app/models/theme.js @@ -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); + } }); diff --git a/ghost/admin/app/routes/settings/design.js b/ghost/admin/app/routes/settings/design.js index 376572fc7f..1dacdb23af 100644 --- a/ghost/admin/app/routes/settings/design.js +++ b/ghost/admin/app/routes/settings/design.js @@ -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); } } }); diff --git a/ghost/admin/app/templates/components/gh-theme-table.hbs b/ghost/admin/app/templates/components/gh-theme-table.hbs index 83a5faff24..20cc353418 100644 --- a/ghost/admin/app/templates/components/gh-theme-table.hbs +++ b/ghost/admin/app/templates/components/gh-theme-table.hbs @@ -22,7 +22,7 @@ {{#if theme.active}} Active {{else}} - + Activate {{/if}} diff --git a/ghost/admin/app/templates/settings/design.hbs b/ghost/admin/app/templates/settings/design.hbs index 1ff151d16a..699809d3ed 100644 --- a/ghost/admin/app/templates/settings/design.hbs +++ b/ghost/admin/app/templates/settings/design.hbs @@ -23,8 +23,7 @@
{{gh-theme-table themes=themes - activeTheme=model.activeTheme - activateTheme=(action "setTheme") + activateTheme=(action "activateTheme") downloadTheme=(action "downloadTheme") deleteTheme=(action "deleteTheme")}} diff --git a/ghost/admin/app/templates/settings/design/uploadtheme.hbs b/ghost/admin/app/templates/settings/design/uploadtheme.hbs index 56aa8141e8..29a45babc3 100644 --- a/ghost/admin/app/templates/settings/design/uploadtheme.hbs +++ b/ghost/admin/app/templates/settings/design/uploadtheme.hbs @@ -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") diff --git a/ghost/admin/mirage/config/themes.js b/ghost/admin/mirage/config/themes.js index ee677aaa2d..817b4ff0e6 100644 --- a/ghost/admin/mirage/config/themes.js +++ b/ghost/admin/mirage/config/themes.js @@ -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(); + }); } diff --git a/ghost/admin/mirage/fixtures/settings.js b/ghost/admin/mirage/fixtures/settings.js index c50d2de5f9..bf274431b2 100644 --- a/ghost/admin/mirage/fixtures/settings.js +++ b/ghost/admin/mirage/fixtures/settings.js @@ -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', diff --git a/ghost/admin/mirage/fixtures/themes.js b/ghost/admin/mirage/fixtures/themes.js index b6d55811d6..8cf3150c1c 100644 --- a/ghost/admin/mirage/fixtures/themes.js +++ b/ghost/admin/mirage/fixtures/themes.js @@ -4,7 +4,8 @@ export default [ package: { name: 'Blog', version: '1.0' - } + }, + active: true }, { name: 'foo', diff --git a/ghost/admin/tests/acceptance/settings/design-test.js b/ghost/admin/tests/acceptance/settings/design-test.js index 20cf8da3e9..74c9ffa77f 100644 --- a/ghost/admin/tests/acceptance/settings/design-test.js +++ b/ghost/admin/tests/acceptance/settings/design-test.js @@ -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 {{asset}} helper', + details: '

The listed files should be included using the {{asset}} helper. For more information, please see the asset helper documentation.

', + failures: [ { - name: 'blackpalm', - package: { - name: 'BlackPalm', - version: '1.0.0' - }, - warnings: [ - { - level: 'warning', - rule: 'Assets such as CSS & JS must use the {{asset}} helper', - details: '

The listed files should be included using the {{asset}} helper. For more information, please see the asset helper documentation.

', - 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(), diff --git a/ghost/admin/tests/integration/components/gh-theme-table-test.js b/ghost/admin/tests/integration/components/gh-theme-table-test.js index de194a1fd0..78732a60b9 100644 --- a/ghost/admin/tests/integration/components/gh-theme-table-test.js +++ b/ghost/admin/tests/integration/components/gh-theme-table-test.js @@ -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)