From 594b0c2d1421776c5fcb28f8d273da88eb3bac7e Mon Sep 17 00:00:00 2001 From: Katharina Irrgang Date: Tue, 10 Oct 2017 14:36:35 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=20Custom=20post=20templates=20(#90?= =?UTF-8?q?73)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes #9060 - Update `gscan` - it now extracts custom templates and exposes them to Ghost - Add `custom_template` field to post schema w/ 1.13 migration - Return `templates` array for the active theme in `/themes/` requests - Users with Author/Editor roles can now request `/themes/` - Front-end will render `custom_template` for posts if it exists, template priority is now: 1. `post/page-{{slug}}.hbs` 2. `{{custom_template}}.hbs` 3. `post/page.hbs` --- core/server/api/themes.js | 6 + core/server/controllers/frontend/templates.js | 24 ++-- .../versions/1.13/1-custom-template-post.js | 30 +++++ .../versions/1.13/2-theme-permissions.js | 39 ++++++ .../server/data/schema/fixtures/fixtures.json | 6 +- core/server/data/schema/schema.js | 3 +- core/server/themes/active.js | 20 ++-- core/server/themes/to-json.js | 15 ++- core/test/functional/routes/api/posts_spec.js | 3 + .../test/functional/routes/api/themes_spec.js | 111 +++++++++++++++++- core/test/integration/migration_spec.js | 2 +- .../controllers/frontend/templates_spec.js | 80 ++++++++++++- .../test/unit/migration_fixture_utils_spec.js | 14 +-- core/test/unit/migration_spec.js | 4 +- core/test/unit/themes/active_spec.js | 7 +- package.json | 2 +- yarn.lock | 6 +- 17 files changed, 320 insertions(+), 52 deletions(-) create mode 100644 core/server/data/migrations/versions/1.13/1-custom-template-post.js create mode 100644 core/server/data/migrations/versions/1.13/2-theme-permissions.js diff --git a/core/server/api/themes.js b/core/server/api/themes.js index 3c67b5ec7179..7adb44059456 100644 --- a/core/server/api/themes.js +++ b/core/server/api/themes.js @@ -20,6 +20,12 @@ var debug = require('ghost-ignition').debug('api:themes'), * **See:** [API Methods](index.js.html#api%20methods) */ themes = { + /** + * Every role can browse all themes. The response contains a list of all available themes in your content folder. + * The active theme get's marked as `active:true` and contains an extra object `templates`, which + * contains the custom templates of the active theme. These custom templates are used to show a dropdown + * in the PSM to be able to choose a custom post template. + */ browse: function browse(options) { return apiUtils // Permissions diff --git a/core/server/controllers/frontend/templates.js b/core/server/controllers/frontend/templates.js index f6c8c311c41c..9d529de9909d 100644 --- a/core/server/controllers/frontend/templates.js +++ b/core/server/controllers/frontend/templates.js @@ -66,22 +66,26 @@ function getChannelTemplateHierarchy(channelOpts) { * * Fetch the ordered list of templates that can be used to render this request. * 'post' is the default / fallback - * For posts: [post-:slug, post] - * For pages: [page-:slug, page, post] + * For posts: [post-:slug, custom-*, post] + * For pages: [page-:slug, custom-*, page, post] * - * @param {Object} single + * @param {Object} postObject * @returns {String[]} */ -function getSingleTemplateHierarchy(single) { +function getSingleTemplateHierarchy(postObject) { var templateList = ['post'], - type = 'post'; + slugTemplate = 'post-' + postObject.slug; - if (single.page) { + if (postObject.page) { templateList.unshift('page'); - type = 'page'; + slugTemplate = 'page-' + postObject.slug; } - templateList.unshift(type + '-' + single.slug); + if (postObject.custom_template) { + templateList.unshift(postObject.custom_template); + } + + templateList.unshift(slugTemplate); return templateList; } @@ -117,8 +121,8 @@ function pickTemplate(templateList, fallback) { return template; } -function getTemplateForSingle(single) { - var templateList = getSingleTemplateHierarchy(single), +function getTemplateForSingle(postObject) { + var templateList = getSingleTemplateHierarchy(postObject), fallback = templateList[templateList.length - 1]; return pickTemplate(templateList, fallback); } diff --git a/core/server/data/migrations/versions/1.13/1-custom-template-post.js b/core/server/data/migrations/versions/1.13/1-custom-template-post.js new file mode 100644 index 000000000000..a7dd3756d690 --- /dev/null +++ b/core/server/data/migrations/versions/1.13/1-custom-template-post.js @@ -0,0 +1,30 @@ +'use strict'; + +const Promise = require('bluebird'), + logging = require('../../../../logging'), + commands = require('../../../schema').commands, + table = 'posts', + column = 'custom_template', + message = 'Adding column: ' + table + '.' + column; + +module.exports = function addCustomTemplateField(options) { + let transacting = options.transacting; + + return transacting.schema.hasTable(table) + .then(function (exists) { + if (!exists) { + return Promise.reject(new Error('Table does not exist!')); + } + + return transacting.schema.hasColumn(table, column); + }) + .then(function (exists) { + if (exists) { + logging.warn(message); + return Promise.resolve(); + } + + logging.info(message); + return commands.addColumn(table, column, transacting); + }); +}; diff --git a/core/server/data/migrations/versions/1.13/2-theme-permissions.js b/core/server/data/migrations/versions/1.13/2-theme-permissions.js new file mode 100644 index 000000000000..96d5f3ad5e24 --- /dev/null +++ b/core/server/data/migrations/versions/1.13/2-theme-permissions.js @@ -0,0 +1,39 @@ +var _ = require('lodash'), + utils = require('../../../schema/fixtures/utils'), + permissions = require('../../../../permissions'), + logging = require('../../../../logging'), + resource = 'theme', + _private = {}; + +_private.getPermissions = function getPermissions() { + return utils.findModelFixtures('Permission', {object_type: resource}); +}; + +_private.getRelations = function getRelations() { + return utils.findPermissionRelationsForObject(resource); +}; + +_private.printResult = function printResult(result, message) { + if (result.done === result.expected) { + logging.info(message); + } else { + logging.warn('(' + result.done + '/' + result.expected + ') ' + message); + } +}; + +module.exports = function addRedirectsPermissions(options) { + var modelToAdd = _private.getPermissions(), + relationToAdd = _private.getRelations(), + localOptions = _.merge({ + context: {internal: true} + }, options); + + return utils.addFixturesForModel(modelToAdd, localOptions).then(function (result) { + _private.printResult(result, 'Adding permissions fixtures for ' + resource + 's'); + return utils.addFixturesForRelation(relationToAdd, localOptions); + }).then(function (result) { + _private.printResult(result, 'Adding permissions_roles fixtures for ' + resource + 's'); + }).then(function () { + return permissions.init(localOptions); + }); +}; diff --git a/core/server/data/schema/fixtures/fixtures.json b/core/server/data/schema/fixtures/fixtures.json index 016e98bee7c6..efcb79a37bf5 100644 --- a/core/server/data/schema/fixtures/fixtures.json +++ b/core/server/data/schema/fixtures/fixtures.json @@ -482,7 +482,8 @@ "role": "all", "client": "all", "subscriber": ["add"], - "invite": "all" + "invite": "all", + "theme": ["browse"] }, "Author": { "post": ["browse", "read", "add"], @@ -492,7 +493,8 @@ "user": ["browse", "read"], "role": ["browse"], "client": "all", - "subscriber": ["add"] + "subscriber": ["add"], + "theme": ["browse"] } } }, diff --git a/core/server/data/schema/schema.js b/core/server/data/schema/schema.js index 0aa48849f7d0..457d7b4cc0b8 100644 --- a/core/server/data/schema/schema.js +++ b/core/server/data/schema/schema.js @@ -31,7 +31,8 @@ module.exports = { og_description: {type: 'string', maxlength: 500, nullable: true}, twitter_image: {type: 'string', maxlength: 2000, nullable: true}, twitter_title: {type: 'string', maxlength: 300, nullable: true}, - twitter_description: {type: 'string', maxlength: 500, nullable: true} + twitter_description: {type: 'string', maxlength: 500, nullable: true}, + custom_template: {type: 'string', maxlength: 100, nullable: true} }, users: { id: {type: 'string', maxlength: 24, nullable: false, primary: true}, diff --git a/core/server/themes/active.js b/core/server/themes/active.js index db32f30fbe67..dd85c28e1342 100644 --- a/core/server/themes/active.js +++ b/core/server/themes/active.js @@ -16,8 +16,7 @@ * No properties marked with an _ should be used directly. * */ -var _ = require('lodash'), - join = require('path').join, +var join = require('path').join, themeConfig = require('./config'), config = require('../config'), engine = require('./engine'), @@ -42,14 +41,11 @@ class ActiveTheme { this._packageInfo = loadedTheme['package.json']; this._partials = checkedTheme.partials; - // @TODO: get gscan to return a template collection for us - this._templates = _.reduce(checkedTheme.files, function (templates, entry) { - var tplMatch = entry.file.match(/(^[^\/]+).hbs$/); - if (tplMatch) { - templates.push(tplMatch[1]); - } - return templates; - }, []); + // all custom .hbs templates (e.g. custom-about) + this._customTemplates = checkedTheme.templates.custom; + + // all .hbs templates + this._templates = checkedTheme.templates.all; // Create a theme config object this._config = themeConfig.create(this._packageInfo); @@ -59,6 +55,10 @@ class ActiveTheme { return this._name; } + get customTemplates() { + return this._customTemplates; + } + get path() { return this._path; } diff --git a/core/server/themes/to-json.js b/core/server/themes/to-json.js index 7e18cda84e0a..766356c7024d 100644 --- a/core/server/themes/to-json.js +++ b/core/server/themes/to-json.js @@ -1,10 +1,16 @@ var _ = require('lodash'), themeList = require('./list'), + active = require('./active'), packages = require('../utils/packages'), settingsCache = require('../settings/cache'); /** - * Provides a JSON object which can be returned via the API + * + * Provides a JSON object which can be returned via the API. + * You can either request all themes or a specific theme if you pass the `name` argument. + * Furthermore, you can pass a gscan result to filter warnings/errors. + * + * @TODO: settingsCache.get('active_theme') vs. active.get().name * * @param {string} [name] - the theme to output * @param {object} [checkedTheme] - a theme result from gscan @@ -15,10 +21,8 @@ module.exports = function toJSON(name, checkedTheme) { if (!name) { toFilter = themeList.getAll(); - // Default to returning the full list themeResult = packages.filterPackages(toFilter, settingsCache.get('active_theme')); } else { - // If we pass in a gscan result, convert this instead toFilter = { [name]: themeList.get(name) }; @@ -34,5 +38,10 @@ module.exports = function toJSON(name, checkedTheme) { } } + // CASE: if you want a JSON response for a single theme, which is not active. + if (_.find(themeResult, {active: true}) && active.get()) { + _.find(themeResult, {active: true}).templates = active.get().customTemplates; + } + return {themes: themeResult}; }; diff --git a/core/test/functional/routes/api/posts_spec.js b/core/test/functional/routes/api/posts_spec.js index c549f5c655a6..0e7bd8036033 100644 --- a/core/test/functional/routes/api/posts_spec.js +++ b/core/test/functional/routes/api/posts_spec.js @@ -766,6 +766,7 @@ describe('Post API', function () { should.exist(jsonResponse.posts[0]); jsonResponse.posts[0].title = changedTitle; jsonResponse.posts[0].author = changedAuthor; + jsonResponse.posts[0].custom_template = 'custom-about'; request.put(testUtils.API.getApiQuery('posts/' + testUtils.DataGenerator.Content.posts[0].id + '/')) .set('Authorization', 'Bearer ' + ownerAccessToken) @@ -783,6 +784,8 @@ describe('Post API', function () { should.exist(putBody); putBody.posts[0].title.should.eql(changedTitle); putBody.posts[0].author.should.eql(changedAuthor); + putBody.posts[0].status.should.eql('published'); + putBody.posts[0].custom_template.should.eql('custom-about'); testUtils.API.checkResponse(putBody.posts[0], 'post'); done(); diff --git a/core/test/functional/routes/api/themes_spec.js b/core/test/functional/routes/api/themes_spec.js index c038c6d00a7e..2bdcf13e40c7 100644 --- a/core/test/functional/routes/api/themes_spec.js +++ b/core/test/functional/routes/api/themes_spec.js @@ -94,6 +94,20 @@ describe('Themes API (Forked)', function () { }) .then(function (token) { scope.editorAccessToken = token; + + return testUtils.createUser({ + user: testUtils.DataGenerator.forKnex.createUser({email: 'test+author@ghost.org'}), + role: testUtils.DataGenerator.Content.roles[2] + }); + }) + .then(function (user) { + scope.author = user; + + request.user = scope.author; + return testUtils.doAuth(request); + }) + .then(function (token) { + scope.authorAccessToken = token; done(); }) .catch(done); @@ -128,10 +142,11 @@ describe('Themes API (Forked)', function () { jsonResponse.themes[0].package.should.be.an.Object().with.properties('name', 'version'); jsonResponse.themes[0].active.should.be.false(); - testUtils.API.checkResponse(jsonResponse.themes[1], 'theme'); + testUtils.API.checkResponse(jsonResponse.themes[1], 'theme', 'templates'); jsonResponse.themes[1].name.should.eql('casper'); jsonResponse.themes[1].package.should.be.an.Object().with.properties('name', 'version'); jsonResponse.themes[1].active.should.be.true(); + jsonResponse.themes[1].templates.should.eql([]); testUtils.API.checkResponse(jsonResponse.themes[2], 'theme'); jsonResponse.themes[2].name.should.eql('test-theme'); @@ -218,7 +233,7 @@ describe('Themes API (Forked)', function () { // Casper should be present and still active casperTheme = _.find(jsonResponse.themes, {name: 'casper'}); should.exist(casperTheme); - testUtils.API.checkResponse(casperTheme, 'theme'); + testUtils.API.checkResponse(casperTheme, 'theme', 'templates'); casperTheme.active.should.be.true(); // The added theme should be here @@ -276,7 +291,7 @@ describe('Themes API (Forked)', function () { // Casper should be present and still active casperTheme = _.find(jsonResponse.themes, {name: 'casper'}); should.exist(casperTheme); - testUtils.API.checkResponse(casperTheme, 'theme'); + testUtils.API.checkResponse(casperTheme, 'theme', 'templates'); casperTheme.active.should.be.true(); // The deleted theme should not be here @@ -340,7 +355,7 @@ describe('Themes API (Forked)', function () { casperTheme = _.find(jsonResponse.themes, {name: 'casper'}); should.exist(casperTheme); - testUtils.API.checkResponse(casperTheme, 'theme'); + testUtils.API.checkResponse(casperTheme, 'theme', 'templates'); casperTheme.active.should.be.true(); testTheme = _.find(jsonResponse.themes, {name: 'test-theme'}); @@ -368,7 +383,7 @@ describe('Themes API (Forked)', function () { testTheme = _.find(jsonResponse.themes, {name: 'test-theme'}); should.exist(testTheme); - testUtils.API.checkResponse(testTheme, 'theme', ['warnings']); + testUtils.API.checkResponse(testTheme, 'theme', ['warnings', 'templates']); testTheme.active.should.be.true(); testTheme.warnings.should.be.an.Array(); @@ -492,7 +507,7 @@ describe('Themes API (Forked)', function () { testTheme = _.find(jsonResponse.themes, {name: 'test-theme'}); should.exist(testTheme); - testUtils.API.checkResponse(testTheme, 'theme', ['warnings']); + testUtils.API.checkResponse(testTheme, 'theme', ['warnings', 'templates']); testTheme.active.should.be.true(); testTheme.warnings.should.be.an.Array(); @@ -546,6 +561,19 @@ describe('Themes API (Forked)', function () { }); describe('As Editor', function () { + it('can browse themes', function (done) { + request.get(testUtils.API.getApiQuery('themes/')) + .set('Authorization', 'Bearer ' + scope.editorAccessToken) + .expect(200) + .end(function (err) { + if (err) { + return done(err); + } + + done(); + }); + }); + it('no permissions to upload theme', function (done) { scope.uploadTheme({ themePath: join(__dirname, '/../../../utils/fixtures/themes/valid.zip'), @@ -602,5 +630,76 @@ describe('Themes API (Forked)', function () { }); }); }); + + describe('As Author', function () { + it('can browse themes', function (done) { + request.get(testUtils.API.getApiQuery('themes/')) + .set('Authorization', 'Bearer ' + scope.authorAccessToken) + .expect(200) + .end(function (err) { + if (err) { + return done(err); + } + + done(); + }); + }); + + it('no permissions to upload theme', function (done) { + scope.uploadTheme({ + themePath: join(__dirname, '/../../../utils/fixtures/themes/valid.zip'), + accessToken: scope.authorAccessToken + }).end(function (err, res) { + if (err) { + return done(err); + } + + res.statusCode.should.eql(403); + + should.exist(res.body.errors); + res.body.errors.should.be.an.Array().with.lengthOf(1); + res.body.errors[0].errorType.should.eql('NoPermissionError'); + res.body.errors[0].message.should.eql('You do not have permission to add themes'); + + done(); + }); + }); + + it('no permissions to delete theme', function (done) { + request.del(testUtils.API.getApiQuery('themes/test')) + .set('Authorization', 'Bearer ' + scope.authorAccessToken) + .expect(403) + .end(function (err, res) { + if (err) { + return done(err); + } + + should.exist(res.body.errors); + res.body.errors.should.be.an.Array().with.lengthOf(1); + res.body.errors[0].errorType.should.eql('NoPermissionError'); + res.body.errors[0].message.should.eql('You do not have permission to destroy themes'); + + done(); + }); + }); + + it('no permissions to download theme', function (done) { + request.get(testUtils.API.getApiQuery('themes/casper/download/')) + .set('Authorization', 'Bearer ' + scope.authorAccessToken) + .expect(403) + .end(function (err, res) { + if (err) { + return done(err); + } + + should.exist(res.body.errors); + res.body.errors.should.be.an.Array().with.lengthOf(1); + res.body.errors[0].errorType.should.eql('NoPermissionError'); + res.body.errors[0].message.should.eql('You do not have permission to read themes'); + + done(); + }); + }); + }); }); }); diff --git a/core/test/integration/migration_spec.js b/core/test/integration/migration_spec.js index 53a7bff4ecf6..171000af678a 100644 --- a/core/test/integration/migration_spec.js +++ b/core/test/integration/migration_spec.js @@ -92,7 +92,7 @@ describe('Database Migration (special functions)', function () { // Themes permissions[21].name.should.eql('Browse themes'); - permissions[21].should.be.AssignedToRoles(['Administrator']); + permissions[21].should.be.AssignedToRoles(['Administrator', 'Editor', 'Author']); permissions[22].name.should.eql('Edit themes'); permissions[22].should.be.AssignedToRoles(['Administrator']); permissions[23].name.should.eql('Activate themes'); diff --git a/core/test/unit/controllers/frontend/templates_spec.js b/core/test/unit/controllers/frontend/templates_spec.js index 46e85cfb919b..ce5ed5e77947 100644 --- a/core/test/unit/controllers/frontend/templates_spec.js +++ b/core/test/unit/controllers/frontend/templates_spec.js @@ -126,7 +126,7 @@ describe('templates', function () { hasTemplateStub.withArgs('post').returns(true); }); - it('will return correct template for a post WITHOUT custom template', function () { + it('post without custom slug template', function () { var view = templates.single({ page: 0, slug: 'test-post' @@ -135,17 +135,17 @@ describe('templates', function () { view.should.eql('post'); }); - it('will return correct template for a post WITH custom template', function () { + it('post with custom slug template', function () { hasTemplateStub.withArgs('post-welcome-to-ghost').returns(true); var view = templates.single({ page: 0, slug: 'welcome-to-ghost' }); should.exist(view); - view.should.eql('post-welcome-to-ghost', 'post'); + view.should.eql('post-welcome-to-ghost'); }); - it('will return correct template for a page WITHOUT custom template', function () { + it('page without custom slug template', function () { var view = templates.single({ page: 1, slug: 'contact' @@ -154,7 +154,7 @@ describe('templates', function () { view.should.eql('page'); }); - it('will return correct template for a page WITH custom template', function () { + it('page with custom slug template', function () { var view = templates.single({ page: 1, slug: 'about' @@ -162,6 +162,76 @@ describe('templates', function () { should.exist(view); view.should.eql('page-about'); }); + + it('post with custom template', function () { + hasTemplateStub.withArgs('custom-about').returns(true); + + var view = templates.single({ + page: 0, + custom_template: 'custom-about' + }); + should.exist(view); + view.should.eql('custom-about'); + }); + + it('page with custom template', function () { + hasTemplateStub.withArgs('custom-about').returns(true); + + var view = templates.single({ + page: 1, + custom_template: 'custom-about' + }); + should.exist(view); + view.should.eql('custom-about'); + }); + + it('post with custom template configured, but the template is missing', function () { + hasTemplateStub.withArgs('custom-about').returns(false); + + var view = templates.single({ + page: 0, + custom_template: 'custom-about' + }); + should.exist(view); + view.should.eql('post'); + }); + + it('page with custom template configured, but the template is missing', function () { + hasTemplateStub.withArgs('custom-about').returns(false); + + var view = templates.single({ + page: 1, + custom_template: 'custom-about' + }); + should.exist(view); + view.should.eql('page'); + }); + + it('post with custom template configured, but slug template exists', function () { + hasTemplateStub.withArgs('custom-about').returns(true); + hasTemplateStub.withArgs('post-about').returns(true); + + var view = templates.single({ + page: 0, + slug: 'about', + custom_template: 'custom-about' + }); + should.exist(view); + view.should.eql('post-about'); + }); + + it('post with custom template configured, but slug template exists, but can\'t be found', function () { + hasTemplateStub.withArgs('custom-about').returns(false); + hasTemplateStub.withArgs('post-about').returns(false); + + var view = templates.single({ + page: 0, + slug: 'about', + custom_template: 'custom-about' + }); + should.exist(view); + view.should.eql('post'); + }); }); it('will fall back to post even if no index.hbs', function () { diff --git a/core/test/unit/migration_fixture_utils_spec.js b/core/test/unit/migration_fixture_utils_spec.js index 93faa80ecced..a258cf608193 100644 --- a/core/test/unit/migration_fixture_utils_spec.js +++ b/core/test/unit/migration_fixture_utils_spec.js @@ -151,19 +151,19 @@ describe('Migration Fixture Utils', function () { fixtureUtils.addFixturesForRelation(fixtures.relations[0]).then(function (result) { should.exist(result); result.should.be.an.Object(); - result.should.have.property('expected', 31); - result.should.have.property('done', 31); + result.should.have.property('expected', 33); + result.should.have.property('done', 33); // Permissions & Roles permsAllStub.calledOnce.should.be.true(); rolesAllStub.calledOnce.should.be.true(); - dataMethodStub.filter.callCount.should.eql(31); + dataMethodStub.filter.callCount.should.eql(33); dataMethodStub.find.callCount.should.eql(3); - baseUtilAttachStub.callCount.should.eql(31); + baseUtilAttachStub.callCount.should.eql(33); - fromItem.related.callCount.should.eql(31); - fromItem.findWhere.callCount.should.eql(31); - toItem[0].get.callCount.should.eql(62); + fromItem.related.callCount.should.eql(33); + fromItem.findWhere.callCount.should.eql(33); + toItem[0].get.callCount.should.eql(66); done(); }).catch(done); diff --git a/core/test/unit/migration_spec.js b/core/test/unit/migration_spec.js index 0b2888dc73fa..3b36e0a900aa 100644 --- a/core/test/unit/migration_spec.js +++ b/core/test/unit/migration_spec.js @@ -19,8 +19,8 @@ var should = require('should'), // jshint ignore:line // both of which are required for migrations to work properly. describe('DB version integrity', function () { // Only these variables should need updating - var currentSchemaHash = 'af4028653a7c0804f6bf7b98c50db5dc', - currentFixturesHash = '00e9b37f49b8eed5591ec2d381afb9e3'; + var currentSchemaHash = '0de1eaa8bc79046a9f43927917c294c3', + currentFixturesHash = '7c8b21872959945c22ed5780f513db67'; // If this test is failing, then it is likely a change has been made that requires a DB version bump, // and the values above will need updating as confirmation diff --git a/core/test/unit/themes/active_spec.js b/core/test/unit/themes/active_spec.js index df995df19a99..e723c5c4591e 100644 --- a/core/test/unit/themes/active_spec.js +++ b/core/test/unit/themes/active_spec.js @@ -32,7 +32,12 @@ describe('Themes', function () { name: 'casper', path: 'my/fake/theme/path' }; - fakeCheckedTheme = {}; + fakeCheckedTheme = { + templates: { + all: ['post', 'about', 'post-hey', 'custom-test'], + custom: ['custom-test', 'post-hey'] + } + }; }); it('should mount active theme with partials', function () { diff --git a/package.json b/package.json index b2f9e720da5e..c9400f3d4920 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "ghost-storage-base": "0.0.1", "glob": "5.0.15", "got": "7.1.0", - "gscan": "1.1.7", + "gscan": "1.2.0", "html-to-text": "3.3.0", "image-size": "0.6.1", "intl": "1.2.5", diff --git a/yarn.lock b/yarn.lock index 380c64661b2a..05fb77b0e7f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2160,9 +2160,9 @@ grunt@~0.4.0: underscore.string "~2.2.1" which "~1.0.5" -gscan@1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/gscan/-/gscan-1.1.7.tgz#27244687af130851b7d04cd035f4dae8d96bedec" +gscan@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gscan/-/gscan-1.2.0.tgz#c2a75f31cebd6b110d1bf8ef3412f3a44bf2a965" dependencies: bluebird "3.4.6" chalk "1.1.1"