Add setting filter

closes #172
- added type to ghost.settings()
- added /api/settings?type=<filter>
- added availableThemes to settingsCache
- removed cachedSettingsRequestHandler
- removed /api/themes (including front end)
- changed activePlugins to type "plugin" in default-settings.json
This commit is contained in:
Sebastian Gierlinger 2013-09-15 18:03:31 +02:00
parent 596c1dccd2
commit bd8db968ea
12 changed files with 136 additions and 167 deletions

View File

@ -3,7 +3,7 @@
"use strict";
//id:0 is used to issue PUT requests
Ghost.Models.Settings = Backbone.Model.extend({
url: Ghost.settings.apiRoot + '/settings',
url: Ghost.settings.apiRoot + '/settings?type=blog,theme',
id: "0"
});

View File

@ -1,9 +0,0 @@
/*global window, document, Ghost, $, _, Backbone */
(function () {
"use strict";
Ghost.Models.Themes = Backbone.Model.extend({
url: Ghost.settings.apiRoot + '/themes'
});
}());

View File

@ -57,8 +57,7 @@
showContent: function (id) {
var self = this,
model,
themes;
model;
Ghost.router.navigate('/settings/' + id);
Ghost.trigger('urlchange');
@ -70,13 +69,9 @@
this.pane = new Settings[id]({ el: '.settings-content'});
if (!this.models.hasOwnProperty(this.pane.options.modelType)) {
themes = this.models.Themes = new Ghost.Models.Themes();
model = this.models[this.pane.options.modelType] = new Ghost.Models[this.pane.options.modelType]();
themes.fetch().then(function () {
model.fetch().then(function () {
model.set({availableThemes: themes.toJSON()});
self.renderPane(model);
});
model.fetch().then(function () {
self.renderPane(model);
});
} else {
model = this.models[this.pane.options.modelType];
@ -157,8 +152,7 @@
},
saveSettings: function () {
var themes = this.model.get('availableThemes'),
title = this.$('#blog-title').val(),
var title = this.$('#blog-title').val(),
description = this.$('#blog-description').val(),
email = this.$('#email-address').val(),
postsPerPage = this.$('#postsPerPage').val();
@ -180,8 +174,6 @@
if (Ghost.Validate._errors.length > 0) {
Ghost.Validate.handleErrors();
} else {
this.model.unset('availableThemes');
this.model.save({
title: title,
description: description,
@ -194,7 +186,6 @@
success: this.saveSuccess,
error: this.saveError
});
this.model.set({availableThemes: themes});
}
},
showLogo: function () {
@ -208,16 +199,12 @@
showUpload: function (id, key, src) {
var self = this, upload = new Ghost.Models.uploadModal({'id': id, 'key': key, 'src': src, 'accept': {
func: function () { // The function called on acceptance
var data = {},
themes;
var data = {};
data[key] = this.$('.js-upload-target').attr('src');
themes = self.model.get('availableThemes');
self.model.unset('availableThemes');
self.model.save(data, {
success: self.saveSuccess,
error: self.saveError
});
self.model.set({availableThemes: themes});
self.render();
return true;
},

View File

@ -100,10 +100,10 @@ Ghost = function () {
* this data is what becomes globally available to themes */
return {
url: instance.config().url,
title: instance.settings().title,
description: instance.settings().description,
logo: instance.settings().logo,
cover: instance.settings().cover
title: instance.settings().title.value,
description: instance.settings().description.value,
logo: instance.settings().logo.value,
cover: instance.settings().cover.value
};
},
statuses: function () { return statuses; },
@ -121,7 +121,7 @@ Ghost = function () {
'appRoot': appRoot,
'themePath': themePath,
'pluginPath': pluginPath,
'activeTheme': path.join(themePath, !instance.settingsCache ? "" : instance.settingsCache.activeTheme),
'activeTheme': path.join(themePath, !instance.settingsCache ? "" : instance.settingsCache.activeTheme.value),
'adminViews': path.join(appRoot, '/core/server/views/'),
'helperTemplates': path.join(appRoot, '/core/server/helpers/tpl/'),
'lang': path.join(appRoot, '/core/shared/lang/'),
@ -142,15 +142,14 @@ Ghost.prototype.init = function () {
instance.dataProvider.init(),
instance.getPaths(),
instance.mail.init(self)
).then(function () {
return models.Settings.populateDefaults();
}).then(function () {
// Initialize plugins
return self.initPlugins();
}).then(function () {
// Initialize the settings cache
return self.updateSettingsCache();
}).then(function () {
// Initialize plugins
return self.initPlugins();
}).then(function () {
// Initialize the permissions actions and objects
@ -164,7 +163,7 @@ Ghost.prototype.init = function () {
}).otherwise(function (error) {
// this is where all the "first run" functionality should go
var dbhash = uuid.v4();
return when(models.Settings.add({key: 'dbHash', value: dbhash})).then(function (returned) {
return when(models.Settings.add({key: 'dbHash', value: dbhash, type: 'core'})).then(function (returned) {
self.dbHash = dbhash;
return dbhash;
});
@ -179,33 +178,62 @@ Ghost.prototype.updateSettingsCache = function (settings) {
settings = settings || {};
if (!_.isEmpty(settings)) {
self.settingsCache = settings;
_.map(settings, function (setting, key) {
self.settingsCache[key].value = setting.value;
});
} else {
// TODO: this should use api.browse
return models.Settings.findAll().then(function (result) {
var settings = {};
_.map(result.models, function (member) {
if (!settings.hasOwnProperty(member.attributes.key)) {
if (member.attributes.key === 'activeTheme') {
member.attributes.value = member.attributes.value.substring(member.attributes.value.lastIndexOf('/') + 1);
var settingsThemePath = path.join(themePath, member.attributes.value);
fs.exists(settingsThemePath, function (exists) {
if (!exists) {
member.attributes.value = "casper";
}
settings[member.attributes.key] = member.attributes.value;
});
return;
}
settings[member.attributes.key] = member.attributes.value;
}
return when(models.Settings.findAll()).then(function (result) {
return when(self.readSettingsResult(result)).then(function (s) {
self.settingsCache = s;
});
self.settingsCache = settings;
}, errors.logAndThrowError);
});
}
};
Ghost.prototype.readSettingsResult = function (result) {
var settings = {};
return when(_.map(result.models, function (member) {
if (!settings.hasOwnProperty(member.attributes.key)) {
var val = {};
if (member.attributes.key === 'activeTheme') {
member.attributes.value = member.attributes.value.substring(member.attributes.value.lastIndexOf('/') + 1);
val.value = member.attributes.value;
val.type = member.attributes.type;
settings[member.attributes.key] = val;
} else {
val.value = member.attributes.value;
val.type = member.attributes.type;
settings[member.attributes.key] = val;
}
}
})).then(function () {
return when(instance.paths().availableThemes).then(function (themes) {
var themeKeys = Object.keys(themes),
res = [],
i,
item;
for (i = 0; i < themeKeys.length; i += 1) {
//do not include hidden files
if (themeKeys[i].indexOf('.') !== 0) {
item = {};
item.name = themeKeys[i];
//data about files currently not used
//item.details = themes[themeKeys[i]];
if (themeKeys[i] === settings.activeTheme.value) {
item.active = true;
}
res.push(item);
}
}
settings.availableThemes = {};
settings.availableThemes.value = res;
settings.availableThemes.type = 'theme';
return settings;
});
});
};
// ## Template utils
// Compile a template for a handlebars helper
@ -303,7 +331,6 @@ Ghost.prototype.doFilter = function (name, args, callback) {
// Initialise plugins. Will load from config.activePlugins by default
Ghost.prototype.initPlugins = function (pluginsToLoad) {
pluginsToLoad = pluginsToLoad || models.Settings.activePlugins;
var self = this;
return plugins.init(this, pluginsToLoad).then(function (loadedPlugins) {
@ -326,11 +353,11 @@ Ghost.prototype.initTheme = function (app) {
// self.globals is a hack til we have a better way of getting combined settings & config
hbsOptions = {templateOptions: {data: {blog: self.blogGlobals()}}};
if (!self.themeDirectories.hasOwnProperty(self.settings().activeTheme)) {
if (!self.themeDirectories.hasOwnProperty(self.settings().activeTheme.value)) {
// Throw an error if the theme is not available...
// TODO: move this to happen on app start
errors.logAndThrowError('The currently active theme ' + self.settings().activeTheme + ' is missing.');
} else if (self.themeDirectories[self.settings().activeTheme].hasOwnProperty('partials')) {
errors.logAndThrowError('The currently active theme ' + self.settings().activeTheme.value + ' is missing.');
} else if (self.themeDirectories[self.settings().activeTheme.value].hasOwnProperty('partials')) {
// Check that the theme has a partials directory before trying to use it
hbsOptions.partialsDir = path.join(self.paths().activeTheme, 'partials');
}

View File

@ -192,11 +192,9 @@ when.all([ghost.init(), helpers.loadCoreHelpers(ghost)]).then(function () {
server.put('/api/v0.1/posts/:id', authAPI, disableCachedResult, api.requestHandler(api.posts.edit));
server.del('/api/v0.1/posts/:id', authAPI, disableCachedResult, api.requestHandler(api.posts.destroy));
// #### Settings
server.get('/api/v0.1/settings', authAPI, disableCachedResult, api.cachedSettingsRequestHandler(api.settings.browse));
server.get('/api/v0.1/settings/:key', authAPI, disableCachedResult, api.cachedSettingsRequestHandler(api.settings.read));
server.put('/api/v0.1/settings', authAPI, disableCachedResult, api.cachedSettingsRequestHandler(api.settings.edit));
// #### Themes
server.get('/api/v0.1/themes', authAPI, disableCachedResult, api.requestHandler(api.themes.browse));
server.get('/api/v0.1/settings', authAPI, disableCachedResult, api.requestHandler(api.settings.browse));
server.get('/api/v0.1/settings/:key', authAPI, disableCachedResult, api.requestHandler(api.settings.read));
server.put('/api/v0.1/settings', authAPI, disableCachedResult, api.requestHandler(api.settings.edit));
// #### Users
server.get('/api/v0.1/users', authAPI, disableCachedResult, api.requestHandler(api.users.browse));
server.get('/api/v0.1/users/:id', authAPI, disableCachedResult, api.requestHandler(api.users.read));

View File

@ -17,9 +17,9 @@ var Ghost = require('../ghost'),
settings,
themes,
requestHandler,
cachedSettingsRequestHandler,
settingsObject,
settingsCollection;
settingsCollection,
settingsFilter;
// ## Posts
posts = {
@ -195,6 +195,16 @@ notifications = {
// ### Helpers
// Turn a settings collection into a single object/hashmap
settingsObject = function (settings) {
if (_.isObject(settings)) {
return _.reduce(settings, function (res, item, key) {
if (_.isArray(item)) {
res[key] = item;
} else {
res[key] = item.value;
}
return res;
}, {});
}
return (settings.toJSON ? settings.toJSON() : settings).reduce(function (res, item) {
if (item.toJSON) { item = item.toJSON(); }
if (item.key) { res[item.key] = item.value; }
@ -208,13 +218,28 @@ settingsCollection = function (settings) {
});
};
settingsFilter = function (settings, filter) {
return _.object(_.filter(_.pairs(settings), function (setting) {
if (filter) {
return _.some(filter.split(","), function (f) {
return setting[1].type === f;
});
}
return true;
}));
};
settings = {
// #### Browse
// **takes:** options object
browse: function browse(options) {
// **returns:** a promise for a settings json object
return dataProvider.Settings.browse(options).then(settingsObject, errors.logAndThrowError);
// **returns:** a promise for a settings json object
if (ghost.settings()) {
return when(ghost.settings()).then(function (settings) {
return settingsObject(settingsFilter(settings, options.type));
}, errors.logAndThrowError);
}
},
// #### Read
@ -225,14 +250,17 @@ settings = {
options = { key: options };
}
// **returns:** a promise for a single key-value pair
return dataProvider.Settings.read(options.key).then(function (setting) {
if (!setting) {
return when.reject("Unable to find setting: " + options.key);
}
return _.pick(setting.toJSON(), 'key', 'value');
}, errors.logAndThrowError);
if (ghost.settings()) {
return when(ghost.settings()[options.key]).then(function (setting) {
if (!setting) {
return when.reject("Unable to find setting: " + options.key);
}
var res = {};
res.key = options.key;
res.value = setting.value;
return res;
}, errors.logAndThrowError);
}
},
// #### Edit
@ -241,61 +269,36 @@ settings = {
edit: function edit(key, value) {
// Check for passing a collection of settings first
if (_.isObject(key)) {
//clean data
var type = key.type;
delete key.type;
delete key.availableThemes;
key = settingsCollection(key);
return dataProvider.Settings.edit(key).then(settingsObject, errors.logAndThrowError);
return dataProvider.Settings.edit(key).then(function (result) {
result.models = result;
return when(ghost.readSettingsResult(result)).then(function (settings) {
ghost.updateSettingsCache(settings);
return settingsObject(settingsFilter(ghost.settings(), type));
});
}, errors.logAndThrowError);
}
// **returns:** a promise for a settings json object
return dataProvider.Settings.read(key).then(function (setting) {
if (!setting) {
return when.reject("Unable to find setting: " + key);
}
if (!_.isString(value)) {
value = JSON.stringify(value);
}
setting.set('value', value);
return dataProvider.Settings.edit(setting);
}, errors.logAndThrowError);
}
};
// ## Themes
themes = {
// #### Browse
// **takes:** options object
browse: function browse() {
// **returns:** a promise for a themes json object
return when(ghost.paths().availableThemes).then(function (themes) {
var themeKeys = Object.keys(themes),
res = [],
i,
activeTheme = ghost.paths().activeTheme.substring(ghost.paths().activeTheme.lastIndexOf('/') + 1),
item;
for (i = 0; i < themeKeys.length; i += 1) {
//do not include hidden files
if (themeKeys[i].indexOf('.') !== 0) {
item = {};
item.name = themeKeys[i];
item.details = themes[themeKeys[i]];
if (themeKeys[i] === activeTheme) {
item.active = true;
}
res.push(item);
}
}
return res;
return dataProvider.Settings.edit(setting).then(function (result) {
ghost.settings()[_.first(result).attributes.key].value = _.first(result).attributes.value;
return settingsObject(ghost.settings());
}, errors.logAndThrowError);
});
}
};
// ## Request Handlers
// ### requestHandler
@ -317,41 +320,6 @@ requestHandler = function (apiMethod) {
};
};
// ### cachedSettingsRequestHandler
// Special request handler for settings to access the internal cache version of the settings object
cachedSettingsRequestHandler = function (apiMethod) {
if (!ghost.settings()) {
return requestHandler(apiMethod);
}
return function (req, res) {
var options = _.extend(req.body, req.query, req.params),
promise;
switch (apiMethod.name) {
case 'browse':
promise = when(ghost.settings());
break;
case 'read':
promise = when(ghost.settings()[options.key]);
break;
case 'edit':
promise = apiMethod(options).then(function (result) {
ghost.updateSettingsCache(result);
return result;
});
break;
default:
errors.logAndThrowError(new Error('Unknown method name for settings API: ' + apiMethod.name));
}
return promise.then(function (result) {
res.json(result || {});
}, function (error) {
res.json(400, {error: error});
});
};
};
// Public API
module.exports.posts = posts;
module.exports.users = users;
@ -360,4 +328,3 @@ module.exports.notifications = notifications;
module.exports.settings = settings;
module.exports.themes = themes;
module.exports.requestHandler = requestHandler;
module.exports.cachedSettingsRequestHandler = cachedSettingsRequestHandler;

View File

@ -15,7 +15,7 @@ frontendControllers = {
'homepage': function (req, res) {
// Parse the page number
var pageParam = req.params.page !== undefined ? parseInt(req.params.page, 10) : 1,
postsPerPage = parseInt(ghost.settings().postsPerPage, 10),
postsPerPage = parseInt(ghost.settings().postsPerPage.value, 10),
options = {};
// No negative pages
@ -67,10 +67,10 @@ frontendControllers = {
// Initialize RSS
var siteUrl = ghost.config().url,
feed = new RSS({
title: ghost.settings().title,
description: ghost.settings().description,
title: ghost.settings().title.value,
description: ghost.settings().description.value,
generator: 'Ghost v' + res.locals.version,
author: ghost.settings().author,
author: ghost.settings().author.value,
feed_url: siteUrl + '/rss/',
site_url: siteUrl,
ttl: '60'

View File

@ -46,14 +46,14 @@
}
},
"theme": {
"activePlugins": {
"defaultValue": ""
},
"activeTheme": {
"defaultValue": "casper"
}
},
"plugin": {
"activePlugins": {
"defaultValue": "[]"
},
"installedPlugins": {
"defaultValue": "[]"
}

View File

@ -234,7 +234,7 @@ coreHelpers = function (ghost) {
ghost.registerThemeHelper('e', function (key, defaultString, options) {
var output;
if (ghost.settings().defaultLang === 'en' && _.isEmpty(options.hash) && !ghost.settings().forceI18n) {
if (ghost.settings().defaultLang.value === 'en' && _.isEmpty(options.hash) && !ghost.settings().forceI18n.value) {
output = defaultString;
} else {
output = ghost.polyglot().t(key, options.hash);

View File

@ -96,7 +96,7 @@ GhostMailer.prototype.send = function (message) {
}
var from = 'ghost-mailer@' + url.parse(this.ghost.config().url).hostname,
to = message.to || this.ghost.settings().email,
to = message.to || this.ghost.settings().email.value,
sendMail = nodefn.lift(this.transport.sendMail.bind(this.transport));
message = _.extend(message, {

View File

@ -80,7 +80,6 @@
<script src="/public/models/tag.js"></script>
<script src="/public/models/widget.js"></script>
<script src="/public/models/settings.js"></script>
<script src="/public/models/themes.js"></script>
<script src="/public/models/uploadModal.js"></script>
<!-- // require '/public/views/*' -->
<script src="/public/views/base.js"></script>

View File

@ -8,7 +8,7 @@ var fs = require('fs'),
I18n = function (ghost) {
// TODO: validate
var lang = ghost.settings().defaultLang,
var lang = ghost.settings().defaultLang.value,
path = ghost.paths().lang,
langFilePath = path + lang + '.json';