mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-28 22:43:30 +03:00
🔥✨ No more availableThemes (#8085)
no issue 🎨 Switch themes API to use config.availableThemes - this gets rid of the only places where settings.availableThemes are used 🔥 Get rid of settings.availableThemes - this is no longer used anywhere - also get rid of every related call to updateSettingsCache 🔥 Replace config.availableThemes with theme cache - Creates a tailor-made in-memory cache for themes inside the theme module - Add methods for getting & setting items on the cache - Move all references to config.availableThemes to use the new cache - This can be abstracted later to support other kinds of caches? 🎨 Start improving theme lib's API Still TODO: simplifying/clarifying: - what is the structure of the internal list - what is the difference between a package list, and a theme list? - what is the difference between reading a theme and loading it? - how do we update the theme list (add/remove) - how do we refresh the theme list? (hot reload?!) - how do we get from an internal list, to one that is sent as part of the API? - how are we going to handle theme storage: read/write, such that the path is configurable 🎨 Use themeList consistently 🎨 Update list after storage
This commit is contained in:
parent
0b68458eb7
commit
f8b498d6e7
@ -3,12 +3,10 @@
|
||||
var _ = require('lodash'),
|
||||
dataProvider = require('../models'),
|
||||
Promise = require('bluebird'),
|
||||
config = require('../config'),
|
||||
canThis = require('../permissions').canThis,
|
||||
errors = require('../errors'),
|
||||
utils = require('./utils'),
|
||||
i18n = require('../i18n'),
|
||||
filterPackages = require('../utils/packages').filterPackages,
|
||||
|
||||
docName = 'settings',
|
||||
settings,
|
||||
@ -68,6 +66,17 @@ settingsFilter = function (settings, filter) {
|
||||
|
||||
/**
|
||||
* ### Read Settings Result
|
||||
*
|
||||
* Converts the models to keyed JSON
|
||||
* E.g.
|
||||
* dbHash: {
|
||||
* id: '123abc',
|
||||
* key: 'dbash',
|
||||
* value: 'xxxx',
|
||||
* type: 'core',
|
||||
* timestamps
|
||||
* }
|
||||
*
|
||||
* @private
|
||||
* @param {Array} settingsModels
|
||||
* @returns {Settings}
|
||||
@ -79,20 +88,7 @@ readSettingsResult = function (settingsModels) {
|
||||
}
|
||||
|
||||
return memo;
|
||||
}, {}),
|
||||
themes = config.get('paths').availableThemes,
|
||||
res;
|
||||
|
||||
// @TODO: remove availableThemes from settings cache and create an endpoint to fetch themes
|
||||
if (settings.activeTheme && themes) {
|
||||
res = filterPackages(themes, settings.activeTheme.value);
|
||||
|
||||
settings.availableThemes = {
|
||||
key: 'availableThemes',
|
||||
value: res,
|
||||
type: 'theme'
|
||||
};
|
||||
}
|
||||
}, {});
|
||||
|
||||
return settings;
|
||||
};
|
||||
@ -255,7 +251,7 @@ settings = {
|
||||
}
|
||||
|
||||
object.settings = _.reject(object.settings, function (setting) {
|
||||
return setting.key === 'type' || setting.key === 'availableThemes';
|
||||
return setting.key === 'type';
|
||||
});
|
||||
|
||||
return canEditAllSettings(object.settings, options).then(function () {
|
||||
|
@ -1,6 +1,7 @@
|
||||
// # Themes API
|
||||
// RESTful API for Themes
|
||||
var Promise = require('bluebird'),
|
||||
var debug = require('debug')('ghost:api:themes'),
|
||||
Promise = require('bluebird'),
|
||||
_ = require('lodash'),
|
||||
gscan = require('gscan'),
|
||||
fs = require('fs-extra'),
|
||||
@ -9,12 +10,12 @@ var Promise = require('bluebird'),
|
||||
events = require('../events'),
|
||||
logging = require('../logging'),
|
||||
storage = require('../storage'),
|
||||
settings = require('./settings'),
|
||||
settingsCache = require('../settings/cache'),
|
||||
apiUtils = require('./utils'),
|
||||
utils = require('./../utils'),
|
||||
i18n = require('../i18n'),
|
||||
themeUtils = require('../themes'),
|
||||
themeList = themeUtils.list,
|
||||
packageUtils = require('../utils/packages'),
|
||||
themes;
|
||||
|
||||
/**
|
||||
@ -24,7 +25,10 @@ var Promise = require('bluebird'),
|
||||
*/
|
||||
themes = {
|
||||
browse: function browse() {
|
||||
return Promise.resolve({themes: settingsCache.get('availableThemes')});
|
||||
debug('browsing');
|
||||
var result = packageUtils.filterPackages(themeList.getAll());
|
||||
debug('got result');
|
||||
return Promise.resolve({themes: result});
|
||||
},
|
||||
|
||||
upload: function upload(options) {
|
||||
@ -80,23 +84,18 @@ themes = {
|
||||
}, config.getContentPath('themes'));
|
||||
})
|
||||
.then(function () {
|
||||
// force reload of availableThemes
|
||||
// right now the logic is in the ConfigManager
|
||||
// if we create a theme collection, we don't have to read them from disk
|
||||
return themeUtils.load();
|
||||
return themeUtils.loadOne(zip.shortName);
|
||||
})
|
||||
.then(function () {
|
||||
// the settings endpoint is used to fetch the availableThemes
|
||||
// so we have to force updating the in process cache
|
||||
return settings.updateSettingsCache();
|
||||
})
|
||||
.then(function (settings) {
|
||||
.then(function (themeObject) {
|
||||
// @TODO fix this craziness
|
||||
var toFilter = {};
|
||||
toFilter[zip.shortName] = themeObject;
|
||||
themeObject = packageUtils.filterPackages(toFilter);
|
||||
// gscan theme structure !== ghost theme structure
|
||||
var themeObject = _.find(settings.availableThemes.value, {name: zip.shortName}) || {};
|
||||
if (theme.results.warning.length > 0) {
|
||||
themeObject.warnings = _.cloneDeep(theme.results.warning);
|
||||
}
|
||||
return {themes: [themeObject]};
|
||||
return {themes: themeObject};
|
||||
})
|
||||
.finally(function () {
|
||||
// remove zip upload from multer
|
||||
@ -119,7 +118,7 @@ themes = {
|
||||
|
||||
download: function download(options) {
|
||||
var themeName = options.name,
|
||||
theme = config.get('paths').availableThemes[themeName],
|
||||
theme = themeList.get(themeName),
|
||||
storageAdapter = storage.getStorage('themes');
|
||||
|
||||
if (!theme) {
|
||||
@ -148,20 +147,17 @@ themes = {
|
||||
throw new errors.ValidationError({message: i18n.t('errors.api.themes.destroyCasper')});
|
||||
}
|
||||
|
||||
theme = config.get('paths').availableThemes[name];
|
||||
theme = themeList.get(name);
|
||||
|
||||
if (!theme) {
|
||||
throw new errors.NotFoundError({message: i18n.t('errors.api.themes.themeDoesNotExist')});
|
||||
}
|
||||
|
||||
events.emit('theme.deleted', name);
|
||||
return storageAdapter.delete(name, config.getContentPath('themes'));
|
||||
})
|
||||
.then(function () {
|
||||
return themeUtils.load();
|
||||
})
|
||||
.then(function () {
|
||||
return settings.updateSettingsCache();
|
||||
themeList.del(name);
|
||||
events.emit('theme.deleted', name);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -7,6 +7,7 @@ var rewire = require('rewire'),
|
||||
errors = require('../../../errors'),
|
||||
should = require('should'),
|
||||
configUtils = require('../../../../test/utils/configUtils'),
|
||||
themeList = require('../../../themes').list,
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
// Helper function to prevent unit tests
|
||||
@ -53,10 +54,11 @@ describe('AMP Controller', function () {
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
configUtils.restore();
|
||||
themeList.init();
|
||||
});
|
||||
|
||||
it('should render default amp page when theme has no amp template', function (done) {
|
||||
configUtils.set({paths: {availableThemes: {casper: {}}}});
|
||||
themeList.init({casper: {}});
|
||||
|
||||
setResponseContextStub = sandbox.stub();
|
||||
ampController.__set__('setResponseContext', setResponseContextStub);
|
||||
@ -70,9 +72,9 @@ describe('AMP Controller', function () {
|
||||
});
|
||||
|
||||
it('should render theme amp page when theme has amp template', function (done) {
|
||||
configUtils.set({paths: {availableThemes: {casper: {
|
||||
themeList.init({casper: {
|
||||
'amp.hbs': '/content/themes/casper/amp.hbs'
|
||||
}}}});
|
||||
}});
|
||||
|
||||
setResponseContextStub = sandbox.stub();
|
||||
ampController.__set__('setResponseContext', setResponseContextStub);
|
||||
@ -86,7 +88,7 @@ describe('AMP Controller', function () {
|
||||
});
|
||||
|
||||
it('should render with error when error is passed in', function (done) {
|
||||
configUtils.set({paths: {availableThemes: {casper: {}}}});
|
||||
themeList.init({casper: {}});
|
||||
res.error = 'Test Error';
|
||||
|
||||
setResponseContextStub = sandbox.stub();
|
||||
@ -103,7 +105,7 @@ describe('AMP Controller', function () {
|
||||
|
||||
it('does not render amp page when amp context is missing', function (done) {
|
||||
var renderSpy;
|
||||
configUtils.set({paths: {availableThemes: {casper: {}}}});
|
||||
themeList.init({casper: {}});
|
||||
|
||||
setResponseContextStub = sandbox.stub();
|
||||
ampController.__set__('setResponseContext', setResponseContextStub);
|
||||
@ -121,7 +123,7 @@ describe('AMP Controller', function () {
|
||||
|
||||
it('does not render amp page when context is other than amp and post', function (done) {
|
||||
var renderSpy;
|
||||
configUtils.set({paths: {availableThemes: {casper: {}}}});
|
||||
themeList.init({casper: {}});
|
||||
|
||||
setResponseContextStub = sandbox.stub();
|
||||
ampController.__set__('setResponseContext', setResponseContextStub);
|
||||
|
@ -3,6 +3,7 @@ var privateController = require('../lib/router').controller,
|
||||
path = require('path'),
|
||||
sinon = require('sinon'),
|
||||
configUtils = require('../../../../test/utils/configUtils'),
|
||||
themeList = require('../../../themes').list,
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
describe('Private Controller', function () {
|
||||
@ -42,10 +43,11 @@ describe('Private Controller', function () {
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
configUtils.restore();
|
||||
themeList.init();
|
||||
});
|
||||
|
||||
it('Should render default password page when theme has no password template', function (done) {
|
||||
configUtils.set({paths: {availableThemes: {casper: {}}}});
|
||||
themeList.init({casper: {}});
|
||||
|
||||
res.render = function (view) {
|
||||
view.should.eql(defaultPath);
|
||||
@ -56,9 +58,9 @@ describe('Private Controller', function () {
|
||||
});
|
||||
|
||||
it('Should render theme password page when it exists', function (done) {
|
||||
configUtils.set({paths: {availableThemes: {casper: {
|
||||
themeList.init({casper: {
|
||||
'private.hbs': '/content/themes/casper/private.hbs'
|
||||
}}}});
|
||||
}});
|
||||
|
||||
res.render = function (view) {
|
||||
view.should.eql('private');
|
||||
@ -69,7 +71,7 @@ describe('Private Controller', function () {
|
||||
});
|
||||
|
||||
it('Should render with error when error is passed in', function (done) {
|
||||
configUtils.set({paths: {availableThemes: {casper: {}}}});
|
||||
themeList.init({casper: {}});
|
||||
res.error = 'Test Error';
|
||||
|
||||
res.render = function (view, context) {
|
||||
|
@ -2,11 +2,11 @@
|
||||
//
|
||||
// Figure out which template should be used to render a request
|
||||
// based on the templates which are allowed, and what is available in the theme
|
||||
var _ = require('lodash'),
|
||||
config = require('../../config');
|
||||
var _ = require('lodash'),
|
||||
themeList = require('../../themes').list;
|
||||
|
||||
function getActiveThemePaths(activeTheme) {
|
||||
return config.get('paths').availableThemes[activeTheme];
|
||||
return themeList.get(activeTheme);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -9,6 +9,7 @@ var _ = require('lodash'),
|
||||
logging = require('../logging'),
|
||||
errors = require('../errors'),
|
||||
i18n = require('../i18n'),
|
||||
themeList = require('../themes').list,
|
||||
themeHandler;
|
||||
|
||||
themeHandler = {
|
||||
@ -95,7 +96,7 @@ themeHandler = {
|
||||
// Check if the theme changed
|
||||
if (activeTheme.value !== blogApp.get('activeTheme')) {
|
||||
// Change theme
|
||||
if (!config.get('paths').availableThemes.hasOwnProperty(activeTheme.value)) {
|
||||
if (!themeList.get(activeTheme.value)) {
|
||||
if (!res.isAdmin) {
|
||||
return next(new errors.NotFoundError({
|
||||
message: i18n.t('errors.middleware.themehandler.missingTheme', {theme: activeTheme.value})
|
||||
|
@ -1,7 +1,11 @@
|
||||
var themeLoader = require('./loader');
|
||||
|
||||
// @TODO: reduce the amount of things we expose to the outside world
|
||||
// Make this a nice clean sensible API we can all understand!
|
||||
module.exports = {
|
||||
init: themeLoader.init,
|
||||
load: themeLoader.load,
|
||||
loadAll: themeLoader.loadAllThemes,
|
||||
loadOne: themeLoader.loadOneTheme,
|
||||
list: require('./list'),
|
||||
validate: require('./validate')
|
||||
};
|
||||
|
36
core/server/themes/list.js
Normal file
36
core/server/themes/list.js
Normal file
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Store themes after loading them from the file system
|
||||
*/
|
||||
var _ = require('lodash'),
|
||||
themeListCache = {};
|
||||
|
||||
module.exports = {
|
||||
get: function get(key) {
|
||||
return themeListCache[key];
|
||||
},
|
||||
|
||||
getAll: function getAll() {
|
||||
return themeListCache;
|
||||
},
|
||||
|
||||
set: function set(key, theme) {
|
||||
themeListCache[key] = _.cloneDeep(theme);
|
||||
return themeListCache[key];
|
||||
},
|
||||
|
||||
del: function del(key) {
|
||||
delete themeListCache[key];
|
||||
},
|
||||
|
||||
init: function init(themes) {
|
||||
var self = this;
|
||||
// First, reset the cache
|
||||
themeListCache = {};
|
||||
// For each theme, call set. Allows us to do processing on set later.
|
||||
_.each(themes, function (theme, key) {
|
||||
self.set(key, theme);
|
||||
});
|
||||
|
||||
return themeListCache;
|
||||
}
|
||||
};
|
@ -1,23 +1,32 @@
|
||||
var debug = require('debug')('ghost:themes:loader'),
|
||||
config = require('../config'),
|
||||
events = require('../events'),
|
||||
themeList = require('./list'),
|
||||
read = require('./read'),
|
||||
settingsApi = require('../api/settings'),
|
||||
settingsCache = require('../settings/cache'),
|
||||
updateConfigAndCache,
|
||||
loadThemes,
|
||||
updateThemeList,
|
||||
loadAllThemes,
|
||||
loadOneTheme,
|
||||
initThemes;
|
||||
|
||||
updateConfigAndCache = function updateConfigAndCache(themes) {
|
||||
updateThemeList = function updateThemeList(themes) {
|
||||
debug('loading themes', Object.keys(themes));
|
||||
config.set('paths:availableThemes', themes);
|
||||
settingsApi.updateSettingsCache();
|
||||
themeList.init(themes);
|
||||
};
|
||||
|
||||
loadThemes = function loadThemes() {
|
||||
loadAllThemes = function loadAllThemes() {
|
||||
return read
|
||||
.all(config.getContentPath('themes'))
|
||||
.then(updateConfigAndCache);
|
||||
.then(updateThemeList);
|
||||
};
|
||||
|
||||
loadOneTheme = function loadOneTheme(themeName) {
|
||||
return read
|
||||
.one(config.getContentPath('themes'), themeName)
|
||||
.then(function (readThemes) {
|
||||
// @TODO change read one to not return a keyed object
|
||||
return themeList.set(themeName, readThemes[themeName]);
|
||||
});
|
||||
};
|
||||
|
||||
initThemes = function initThemes() {
|
||||
@ -25,16 +34,17 @@ initThemes = function initThemes() {
|
||||
|
||||
// Register a listener for server-start to load all themes
|
||||
events.on('server:start', function readAllThemesOnServerStart() {
|
||||
loadThemes();
|
||||
loadAllThemes();
|
||||
});
|
||||
|
||||
// Just read the active theme for now
|
||||
return read
|
||||
.one(config.getContentPath('themes'), settingsCache.get('activeTheme'))
|
||||
.then(updateConfigAndCache);
|
||||
.then(updateThemeList);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
init: initThemes,
|
||||
load: loadThemes
|
||||
loadAllThemes: loadAllThemes,
|
||||
loadOneTheme: loadOneTheme
|
||||
};
|
||||
|
@ -1,19 +1,19 @@
|
||||
var Promise = require('bluebird'),
|
||||
config = require('../config'),
|
||||
errors = require('../errors'),
|
||||
i18n = require('../i18n'),
|
||||
themeList = require('./list'),
|
||||
validateActiveTheme;
|
||||
|
||||
// @TODO replace this with something PROPER - we should probably attempt to read the theme from the
|
||||
// File system at this point and validate the theme using gscan rather than just checking if it's in a cache object
|
||||
validateActiveTheme = function validateActiveTheme(themeName) {
|
||||
if (!config.get('paths').availableThemes || Object.keys(config.get('paths').availableThemes).length === 0) {
|
||||
if (!themeList.getAll() || Object.keys(themeList.getAll()).length === 0) {
|
||||
// We haven't yet loaded all themes, this is probably being called early?
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Else, if we have a list, check if the theme is in it
|
||||
if (!config.get('paths').availableThemes.hasOwnProperty(themeName)) {
|
||||
if (!themeList.get(themeName)) {
|
||||
return Promise.reject(new errors.ValidationError({message: i18n.t('notices.data.validation.index.themeCannotBeActivated', {themeName: themeName}), context: 'activeTheme'}));
|
||||
}
|
||||
};
|
||||
|
@ -65,17 +65,18 @@ describe('Themes API', function () {
|
||||
});
|
||||
|
||||
describe('success cases', function () {
|
||||
it('get all available themes', function (done) {
|
||||
request.get(testUtils.API.getApiQuery('settings/'))
|
||||
it('get all themes', function (done) {
|
||||
request.get(testUtils.API.getApiQuery('themes/'))
|
||||
.set('Authorization', 'Bearer ' + scope.ownerAccessToken)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var availableThemes = _.find(res.body.settings, {key: 'availableThemes'});
|
||||
should.exist(availableThemes);
|
||||
availableThemes.value.length.should.be.above(0);
|
||||
var jsonResponse = res.body;
|
||||
should.exist(jsonResponse.themes);
|
||||
testUtils.API.checkResponse(jsonResponse, 'themes');
|
||||
jsonResponse.themes.length.should.be.above(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -108,19 +109,21 @@ describe('Themes API', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('get all available themes + new theme', function (done) {
|
||||
request.get(testUtils.API.getApiQuery('settings/'))
|
||||
it('get all themes + new theme', function (done) {
|
||||
request.get(testUtils.API.getApiQuery('themes/'))
|
||||
.set('Authorization', 'Bearer ' + scope.ownerAccessToken)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
var availableThemes = _.find(res.body.settings, {key: 'availableThemes'});
|
||||
should.exist(availableThemes);
|
||||
var jsonResponse = res.body;
|
||||
should.exist(jsonResponse.themes);
|
||||
testUtils.API.checkResponse(jsonResponse, 'themes');
|
||||
jsonResponse.themes.length.should.be.above(0);
|
||||
|
||||
// ensure the new 'valid' theme is available
|
||||
should.exist(_.find(availableThemes.value, {name: 'valid'}));
|
||||
should.exist(_.find(jsonResponse.themes, {name: 'valid'}));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -6,7 +6,7 @@ var should = require('should'),
|
||||
// Stuff we are testing
|
||||
channels = require('../../../../server/controllers/frontend/channels'),
|
||||
api = require('../../../../server/api'),
|
||||
configUtils = require('../../../utils/configUtils'),
|
||||
themeList = require('../../../../server/themes').list,
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
describe('Channels', function () {
|
||||
@ -104,7 +104,7 @@ describe('Channels', function () {
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
configUtils.restore();
|
||||
themeList.init();
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
@ -120,9 +120,9 @@ describe('Channels', function () {
|
||||
|
||||
// Return basic paths for the activeTheme
|
||||
function setupActiveTheme() {
|
||||
configUtils.set('paths', {availableThemes: {casper: {
|
||||
themeList.init({casper: {
|
||||
'index.hbs': '/content/themes/casper/index.hbs'
|
||||
}}});
|
||||
}});
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
@ -141,10 +141,10 @@ describe('Channels', function () {
|
||||
});
|
||||
|
||||
it('should render the first page of the index channel using home.hbs if available', function (done) {
|
||||
configUtils.set({paths: {availableThemes: {casper: {
|
||||
themeList.init({casper: {
|
||||
'index.hbs': '/content/themes/casper/index.hbs',
|
||||
'home.hbs': '/content/themes/casper/home.hbs'
|
||||
}}}});
|
||||
}});
|
||||
|
||||
testChannelRender({url: '/'}, function (view) {
|
||||
should.exist(view);
|
||||
@ -163,10 +163,10 @@ describe('Channels', function () {
|
||||
});
|
||||
|
||||
it('should use index.hbs for second page even if home.hbs is available', function (done) {
|
||||
configUtils.set({paths: {availableThemes: {casper: {
|
||||
themeList.init({casper: {
|
||||
'index.hbs': '/content/themes/casper/index.hbs',
|
||||
'home.hbs': '/content/themes/casper/home.hbs'
|
||||
}}}});
|
||||
}});
|
||||
|
||||
testChannelRender({url: '/page/2/'}, function (view) {
|
||||
should.exist(view);
|
||||
@ -255,9 +255,9 @@ describe('Channels', function () {
|
||||
|
||||
// Return basic paths for the activeTheme
|
||||
function setupActiveTheme() {
|
||||
configUtils.set('paths', {availableThemes: {casper: {
|
||||
themeList.init({casper: {
|
||||
'index.hbs': '/content/themes/casper/index.hbs'
|
||||
}}});
|
||||
}});
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
@ -277,10 +277,10 @@ describe('Channels', function () {
|
||||
});
|
||||
|
||||
it('should render the first page of the tag channel using tag.hbs by default', function (done) {
|
||||
configUtils.set('paths',{availableThemes: {casper: {
|
||||
themeList.init({casper: {
|
||||
'index.hbs': '/content/themes/casper/index.hbs',
|
||||
'tag.hbs': '/content/themes/casper/tag.hbs'
|
||||
}}});
|
||||
}});
|
||||
|
||||
testChannelRender({url: '/tag/my-tag/'}, function (view) {
|
||||
should.exist(view);
|
||||
@ -291,11 +291,11 @@ describe('Channels', function () {
|
||||
});
|
||||
|
||||
it('should render the first page of the tag channel using tag-:slug.hbs if available', function (done) {
|
||||
configUtils.set('paths', {availableThemes: {casper: {
|
||||
themeList.init({casper: {
|
||||
'index.hbs': '/content/themes/casper/index.hbs',
|
||||
'tag.hbs': '/content/themes/casper/tag.hbs',
|
||||
'tag-my-tag.hbs': '/content/themes/casper/tag-my-tag.hbs'
|
||||
}}});
|
||||
}});
|
||||
|
||||
testChannelRender({url: '/tag/my-tag/'}, function (view) {
|
||||
should.exist(view);
|
||||
@ -315,10 +315,10 @@ describe('Channels', function () {
|
||||
});
|
||||
|
||||
it('should use tag.hbs to render the tag channel if available', function (done) {
|
||||
configUtils.set('paths', {availableThemes: {casper: {
|
||||
themeList.init({casper: {
|
||||
'index.hbs': '/content/themes/casper/index.hbs',
|
||||
'tag.hbs': '/content/themes/casper/tag.hbs'
|
||||
}}});
|
||||
}});
|
||||
|
||||
testChannelRender({url: '/tag/my-tag/page/2/'}, function (view) {
|
||||
should.exist(view);
|
||||
@ -328,11 +328,11 @@ describe('Channels', function () {
|
||||
});
|
||||
|
||||
it('should use tag-:slug.hbs to render the tag channel if available', function (done) {
|
||||
configUtils.set('paths', {availableThemes: {casper: {
|
||||
themeList.init({casper: {
|
||||
'index.hbs': '/content/themes/casper/index.hbs',
|
||||
'tag.hbs': '/content/themes/casper/tag.hbs',
|
||||
'tag-my-tag.hbs': '/content/themes/casper/tag-my-tag.hbs'
|
||||
}}});
|
||||
}});
|
||||
|
||||
testChannelRender({url: '/tag/my-tag/page/2/'}, function (view) {
|
||||
should.exist(view);
|
||||
|
@ -6,6 +6,7 @@ var moment = require('moment'),
|
||||
api = require('../../../../server/api'),
|
||||
frontend = require('../../../../server/controllers/frontend'),
|
||||
configUtils = require('../../../utils/configUtils'),
|
||||
themeList = require('../../../../server/themes').list,
|
||||
settingsCache = require('../../../../server/settings/cache'),
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
@ -121,7 +122,7 @@ describe('Frontend Controller', function () {
|
||||
describe('static pages', function () {
|
||||
describe('custom page templates', function () {
|
||||
it('it will render a custom page-slug template if it exists', function (done) {
|
||||
configUtils.set({paths: {availableThemes: {casper: casper}}});
|
||||
themeList.init({casper: casper});
|
||||
req.path = '/' + mockPosts[2].posts[0].slug + '/';
|
||||
req.route = {path: '*'};
|
||||
res.render = function (view, context) {
|
||||
@ -136,7 +137,7 @@ describe('Frontend Controller', function () {
|
||||
|
||||
it('it will use page.hbs if it exists and no page-slug template is present', function (done) {
|
||||
delete casper['page-about.hbs'];
|
||||
configUtils.set({paths: {availableThemes: {casper: casper}}});
|
||||
themeList.init({casper: casper});
|
||||
req.path = '/' + mockPosts[2].posts[0].slug + '/';
|
||||
req.route = {path: '*'};
|
||||
res.render = function (view, context) {
|
||||
@ -152,7 +153,7 @@ describe('Frontend Controller', function () {
|
||||
it('defaults to post.hbs without a page.hbs or page-slug template', function (done) {
|
||||
delete casper['page-about.hbs'];
|
||||
delete casper['page.hbs'];
|
||||
configUtils.set({paths: {availableThemes: {casper: casper}}});
|
||||
themeList.init({casper: casper});
|
||||
req.path = '/' + mockPosts[2].posts[0].slug + '/';
|
||||
req.route = {path: '*'};
|
||||
res.render = function (view, context) {
|
||||
@ -168,7 +169,7 @@ describe('Frontend Controller', function () {
|
||||
|
||||
describe('permalink set to slug', function () {
|
||||
it('will render static page via /:slug/', function (done) {
|
||||
configUtils.set({paths: {availableThemes: {casper: casper}}});
|
||||
themeList.init({casper: casper});
|
||||
|
||||
req.path = '/' + mockPosts[0].posts[0].slug + '/';
|
||||
req.route = {path: '*'};
|
||||
@ -237,7 +238,7 @@ describe('Frontend Controller', function () {
|
||||
});
|
||||
|
||||
it('will render static page via /:slug', function (done) {
|
||||
configUtils.set({paths: {availableThemes: {casper: casper}}});
|
||||
themeList.init({casper: casper});
|
||||
|
||||
req.path = '/' + mockPosts[0].posts[0].slug + '/';
|
||||
req.route = {path: '*'};
|
||||
@ -293,7 +294,7 @@ describe('Frontend Controller', function () {
|
||||
});
|
||||
|
||||
it('will render post via /:slug/', function (done) {
|
||||
configUtils.set({paths: {availableThemes: {casper: casper}}});
|
||||
themeList.init({casper: casper});
|
||||
|
||||
req.path = '/' + mockPosts[1].posts[0].slug + '/';
|
||||
req.route = {path: '*'};
|
||||
@ -382,7 +383,7 @@ describe('Frontend Controller', function () {
|
||||
});
|
||||
|
||||
it('will render post via /YYYY/MM/DD/:slug/', function (done) {
|
||||
configUtils.set({paths: {availableThemes: {casper: casper}}});
|
||||
themeList.init({casper: casper});
|
||||
var date = moment(mockPosts[1].posts[0].published_at).format('YYYY/MM/DD');
|
||||
req.path = '/' + [date, mockPosts[1].posts[0].slug].join('/') + '/';
|
||||
req.route = {path: '*'};
|
||||
@ -459,7 +460,7 @@ describe('Frontend Controller', function () {
|
||||
});
|
||||
|
||||
it('will render post via /:author/:slug/', function (done) {
|
||||
configUtils.set({paths: {availableThemes: {casper: casper}}});
|
||||
themeList.init({casper: casper});
|
||||
|
||||
req.path = '/' + ['test', mockPosts[1].posts[0].slug].join('/') + '/';
|
||||
req.route = {path: '*'};
|
||||
@ -540,7 +541,7 @@ describe('Frontend Controller', function () {
|
||||
beforeEach(function () {
|
||||
localSettingsCache.permalinks = '/:year/:slug/';
|
||||
|
||||
configUtils.set({paths: {availableThemes: {casper: casper}}});
|
||||
themeList.init({casper: casper});
|
||||
|
||||
var date = moment(mockPosts[1].posts[0].published_at).format('YYYY');
|
||||
mockPosts[1].posts[0].url = '/' + date + '/' + mockPosts[1].posts[0].slug + '/';
|
||||
@ -752,7 +753,7 @@ describe('Frontend Controller', function () {
|
||||
redirect: sinon.spy()
|
||||
};
|
||||
|
||||
configUtils.set({paths: {availableThemes: {casper: {}}}});
|
||||
themeList.init({casper: {}});
|
||||
});
|
||||
|
||||
it('should render draft post', function (done) {
|
||||
@ -768,7 +769,7 @@ describe('Frontend Controller', function () {
|
||||
});
|
||||
|
||||
it('should render draft page', function (done) {
|
||||
configUtils.set({paths: {availableThemes: {casper: {'page.hbs': '/content/themes/casper/page.hbs'}}}});
|
||||
themeList.init({casper: {'page.hbs': '/content/themes/casper/page.hbs'}});
|
||||
req.params = {uuid: 'abc-1234-01'};
|
||||
res.render = function (view, context) {
|
||||
view.should.equal('page');
|
||||
|
@ -4,11 +4,11 @@ var should = require('should'),
|
||||
// Stuff we are testing
|
||||
templates = rewire('../../../../server/controllers/frontend/templates'),
|
||||
|
||||
configUtils = require('../../../utils/configUtils');
|
||||
themeList = require('../../../../server/themes').list;
|
||||
|
||||
describe('templates', function () {
|
||||
afterEach(function () {
|
||||
configUtils.restore();
|
||||
themeList.init();
|
||||
});
|
||||
|
||||
describe('utils', function () {
|
||||
@ -48,21 +48,16 @@ describe('templates', function () {
|
||||
describe('single', function () {
|
||||
describe('with many templates', function () {
|
||||
beforeEach(function () {
|
||||
configUtils.set({
|
||||
paths: {
|
||||
availableThemes: {
|
||||
casper: {
|
||||
assets: null,
|
||||
'default.hbs': '/content/themes/casper/default.hbs',
|
||||
'index.hbs': '/content/themes/casper/index.hbs',
|
||||
'page.hbs': '/content/themes/casper/page.hbs',
|
||||
'page-about.hbs': '/content/themes/casper/page-about.hbs',
|
||||
'post.hbs': '/content/themes/casper/post.hbs',
|
||||
'post-welcome-to-ghost.hbs': '/content/themes/casper/post-welcome-to-ghost.hbs'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
themeList.init({casper: {
|
||||
assets: null,
|
||||
'default.hbs': '/content/themes/casper/default.hbs',
|
||||
'index.hbs': '/content/themes/casper/index.hbs',
|
||||
'page.hbs': '/content/themes/casper/page.hbs',
|
||||
'page-about.hbs': '/content/themes/casper/page-about.hbs',
|
||||
'post.hbs': '/content/themes/casper/post.hbs',
|
||||
'post-welcome-to-ghost.hbs': '/content/themes/casper/post-welcome-to-ghost.hbs'
|
||||
|
||||
}});
|
||||
});
|
||||
|
||||
it('will return correct template for a post WITHOUT custom template', function () {
|
||||
@ -103,10 +98,10 @@ describe('templates', function () {
|
||||
});
|
||||
|
||||
it('will fall back to post even if no index.hbs', function () {
|
||||
configUtils.set({paths: {availableThemes: {casper: {
|
||||
themeList.init({casper: {
|
||||
assets: null,
|
||||
'default.hbs': '/content/themes/casper/default.hbs'
|
||||
}}}});
|
||||
}});
|
||||
|
||||
var view = templates.single('casper', {page: 1});
|
||||
should.exist(view);
|
||||
@ -117,11 +112,11 @@ describe('templates', function () {
|
||||
describe('channel', function () {
|
||||
describe('without tag templates', function () {
|
||||
beforeEach(function () {
|
||||
configUtils.set({paths: {availableThemes: {casper: {
|
||||
themeList.init({casper: {
|
||||
assets: null,
|
||||
'default.hbs': '/content/themes/casper/default.hbs',
|
||||
'index.hbs': '/content/themes/casper/index.hbs'
|
||||
}}}});
|
||||
}});
|
||||
});
|
||||
|
||||
it('will return correct view for a tag', function () {
|
||||
@ -133,13 +128,13 @@ describe('templates', function () {
|
||||
|
||||
describe('with tag templates', function () {
|
||||
beforeEach(function () {
|
||||
configUtils.set({paths: {availableThemes: {casper: {
|
||||
themeList.init({casper: {
|
||||
assets: null,
|
||||
'default.hbs': '/content/themes/casper/default.hbs',
|
||||
'index.hbs': '/content/themes/casper/index.hbs',
|
||||
'tag.hbs': '/content/themes/casper/tag.hbs',
|
||||
'tag-design.hbs': '/content/themes/casper/tag-about.hbs'
|
||||
}}}});
|
||||
}});
|
||||
});
|
||||
|
||||
it('will return correct view for a tag', function () {
|
||||
@ -156,10 +151,10 @@ describe('templates', function () {
|
||||
});
|
||||
|
||||
it('will fall back to index even if no index.hbs', function () {
|
||||
configUtils.set({paths: {availableThemes: {casper: {
|
||||
themeList.init({casper: {
|
||||
assets: null,
|
||||
'default.hbs': '/content/themes/casper/default.hbs'
|
||||
}}}});
|
||||
}});
|
||||
|
||||
var view = templates.channel('casper', {name: 'tag', slugParam: 'development', slugTemplate: true});
|
||||
should.exist(view);
|
||||
|
@ -4,10 +4,10 @@ var sinon = require('sinon'),
|
||||
Promise = require('bluebird'),
|
||||
fs = require('fs'),
|
||||
hbs = require('express-hbs'),
|
||||
themeList = require('../../../server/themes').list,
|
||||
themeHandler = require('../../../server/middleware/theme-handler'),
|
||||
logging = require('../../../server/logging'),
|
||||
api = require('../../../server/api'),
|
||||
configUtils = require('../../utils/configUtils'),
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
describe('Theme Handler', function () {
|
||||
@ -23,7 +23,7 @@ describe('Theme Handler', function () {
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
configUtils.restore();
|
||||
themeList.init();
|
||||
});
|
||||
|
||||
describe('activateTheme', function () {
|
||||
@ -89,6 +89,10 @@ describe('Theme Handler', function () {
|
||||
});
|
||||
|
||||
describe('updateActiveTheme', function () {
|
||||
beforeEach(function () {
|
||||
themeList.init({casper: {}});
|
||||
});
|
||||
|
||||
it('updates the active theme if changed', function (done) {
|
||||
var activateThemeSpy = sandbox.spy(themeHandler, 'activateTheme');
|
||||
|
||||
@ -99,7 +103,6 @@ describe('Theme Handler', function () {
|
||||
}]
|
||||
}));
|
||||
blogApp.set('activeTheme', 'not-casper');
|
||||
configUtils.set({paths: {availableThemes: {casper: {}}}});
|
||||
|
||||
themeHandler.updateActiveTheme(req, res, function () {
|
||||
activateThemeSpy.called.should.be.true();
|
||||
@ -116,7 +119,6 @@ describe('Theme Handler', function () {
|
||||
}]
|
||||
}));
|
||||
blogApp.set('activeTheme', 'casper');
|
||||
configUtils.set({paths: {availableThemes: {casper: {}}}});
|
||||
|
||||
themeHandler.updateActiveTheme(req, res, function () {
|
||||
activateThemeSpy.called.should.be.false();
|
||||
@ -135,7 +137,6 @@ describe('Theme Handler', function () {
|
||||
}));
|
||||
|
||||
blogApp.set('activeTheme', 'not-casper');
|
||||
configUtils.set({paths: {availableThemes: {casper: {}}}});
|
||||
|
||||
themeHandler.updateActiveTheme(req, res, function (err) {
|
||||
should.exist(err);
|
||||
@ -158,7 +159,6 @@ describe('Theme Handler', function () {
|
||||
|
||||
res.isAdmin = true;
|
||||
blogApp.set('activeTheme', 'not-casper');
|
||||
configUtils.set({paths: {availableThemes: {casper: {}}}});
|
||||
|
||||
themeHandler.updateActiveTheme(req, res, function () {
|
||||
activateThemeSpy.called.should.be.false();
|
||||
|
@ -1,7 +1,7 @@
|
||||
var should = require('should'),
|
||||
hbs = require('express-hbs'),
|
||||
utils = require('./utils'),
|
||||
configUtils = require('../../utils/configUtils'),
|
||||
themeList = require('../../../server/themes').list,
|
||||
|
||||
// Stuff we are testing
|
||||
handlebars = hbs.handlebars,
|
||||
@ -11,18 +11,16 @@ describe('{{body_class}} helper', function () {
|
||||
var options = {};
|
||||
before(function () {
|
||||
utils.loadHelpers();
|
||||
configUtils.set({paths: {
|
||||
availableThemes: {
|
||||
casper: {
|
||||
assets: null,
|
||||
'default.hbs': '/content/themes/casper/default.hbs',
|
||||
'index.hbs': '/content/themes/casper/index.hbs',
|
||||
'page.hbs': '/content/themes/casper/page.hbs',
|
||||
'page-about.hbs': '/content/themes/casper/page-about.hbs',
|
||||
'post.hbs': '/content/themes/casper/post.hbs'
|
||||
}
|
||||
themeList.init({
|
||||
casper: {
|
||||
assets: null,
|
||||
'default.hbs': '/content/themes/casper/default.hbs',
|
||||
'index.hbs': '/content/themes/casper/index.hbs',
|
||||
'page.hbs': '/content/themes/casper/page.hbs',
|
||||
'page-about.hbs': '/content/themes/casper/page-about.hbs',
|
||||
'post.hbs': '/content/themes/casper/post.hbs'
|
||||
}
|
||||
}});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
@ -37,7 +35,7 @@ describe('{{body_class}} helper', function () {
|
||||
});
|
||||
|
||||
after(function () {
|
||||
configUtils.restore();
|
||||
themeList.init();
|
||||
});
|
||||
|
||||
it('has loaded body_class helper', function () {
|
||||
|
@ -1,13 +1,22 @@
|
||||
var should = require('should'),
|
||||
sinon = require('sinon'),
|
||||
_ = require('lodash'),
|
||||
fs = require('fs'),
|
||||
tmp = require('tmp'),
|
||||
join = require('path').join,
|
||||
readThemes = require('../../server/themes/read');
|
||||
themeList = require('../../server/themes').list,
|
||||
readThemes = require('../../server/themes/read'),
|
||||
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
// To stop jshint complaining
|
||||
should.equal(true, true);
|
||||
|
||||
describe('Themes', function () {
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('Read All', function () {
|
||||
it('should read directory and include only folders', function (done) {
|
||||
var themePath = tmp.dirSync({unsafeCleanup: true});
|
||||
@ -126,4 +135,73 @@ describe('Themes', function () {
|
||||
.finally(themePath.removeCallback);
|
||||
});
|
||||
});
|
||||
|
||||
describe('List', function () {
|
||||
beforeEach(function () {
|
||||
themeList.init({
|
||||
casper: {foo: 'bar'},
|
||||
'not-casper': {bar: 'baz'}
|
||||
});
|
||||
});
|
||||
|
||||
it('get() allows getting a single theme', function () {
|
||||
themeList.get('casper').should.eql({foo: 'bar'});
|
||||
});
|
||||
|
||||
it('get() with no args should do nothing', function () {
|
||||
should.not.exist(themeList.get());
|
||||
});
|
||||
|
||||
it('getAll() returns all themes', function () {
|
||||
themeList.getAll().should.be.an.Object().with.properties('casper', 'not-casper');
|
||||
Object.keys(themeList.getAll()).should.have.length(2);
|
||||
});
|
||||
|
||||
it('set() updates an existing theme', function () {
|
||||
var origCasper = _.cloneDeep(themeList.get('casper'));
|
||||
themeList.set('casper', {magic: 'update'});
|
||||
|
||||
themeList.get('casper').should.not.eql(origCasper);
|
||||
themeList.get('casper').should.eql({magic: 'update'});
|
||||
});
|
||||
|
||||
it('set() can add a new theme', function () {
|
||||
themeList.set('rasper', {color: 'red'});
|
||||
themeList.get('rasper').should.eql({color: 'red'});
|
||||
});
|
||||
|
||||
it('del() removes a key from the list', function () {
|
||||
should.exist(themeList.get('casper'));
|
||||
should.exist(themeList.get('not-casper'));
|
||||
themeList.del('casper');
|
||||
should.not.exist(themeList.get('casper'));
|
||||
should.exist(themeList.get('not-casper'));
|
||||
});
|
||||
|
||||
it('del() with no argument does nothing', function () {
|
||||
should.exist(themeList.get('casper'));
|
||||
should.exist(themeList.get('not-casper'));
|
||||
themeList.del();
|
||||
should.exist(themeList.get('casper'));
|
||||
should.exist(themeList.get('not-casper'));
|
||||
});
|
||||
|
||||
it('init() calls set for each theme', function () {
|
||||
var setSpy = sandbox.spy(themeList, 'set');
|
||||
|
||||
themeList.init({test: {a: 'b'}, casper: {c: 'd'}});
|
||||
setSpy.calledTwice.should.be.true();
|
||||
setSpy.firstCall.calledWith('test', {a: 'b'}).should.be.true();
|
||||
setSpy.secondCall.calledWith('casper', {c: 'd'}).should.be.true();
|
||||
});
|
||||
|
||||
it('init() with empty object resets the list', function () {
|
||||
themeList.init();
|
||||
var result = themeList.getAll();
|
||||
should.exist(result);
|
||||
result.should.be.an.Object();
|
||||
result.should.eql({});
|
||||
Object.keys(result).should.have.length(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -32,6 +32,7 @@ var _ = require('lodash'),
|
||||
permission: _.keys(schema.permissions),
|
||||
notification: ['type', 'message', 'status', 'id', 'dismissible', 'location'],
|
||||
theme: ['uuid', 'name', 'version', 'active'],
|
||||
themes: ['themes'],
|
||||
invites: _(schema.invites).keys().without('token').value()
|
||||
};
|
||||
|
||||
|
@ -449,7 +449,7 @@ toDoList = {
|
||||
clients: function insertClients() { return fixtures.insertClients(); },
|
||||
filter: function createFilterParamFixtures() { return filterData(DataGenerator); },
|
||||
invites: function insertInvites() { return fixtures.insertInvites(); },
|
||||
themes: function loadThemes() { return themes.load(); }
|
||||
themes: function loadThemes() { return themes.loadAll(); }
|
||||
};
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user