Directory scanning on contents/themes and plugins

This implements #106.
* Added require-tree which is based off of @ricardobeat's module. Fully async.
* I've moved active theme and active directory to settings from config as well.
* Modified settings.hbs and settings.js to display the raw json under Settings/Appearance
This commit is contained in:
Gabor Javorszky 2013-06-09 17:45:17 +01:00 committed by Hannah Wolfe
parent 1df9b6e90a
commit e271c6402f
6 changed files with 143 additions and 18 deletions

24
app.js
View File

@ -45,13 +45,7 @@
ghost.app().use(ghost.initTheme(ghost.app()));
ghost.app().use(flash());
// bind locals - options which appear in every view - perhaps this should be admin only
ghost.app().use(function (req, res, next) {
res.locals.messages = req.flash();
res.locals.siteTitle = ghostGlobals.title;
res.locals.siteDescription = ghostGlobals.description;
res.locals.siteUrl = ghostGlobals.url;
next();
});
});
/**
@ -88,6 +82,8 @@
// Make sure we have a locals value.
res.locals = res.locals || {};
// Extend it with nav data and ghostGlobals
_.extend(res.locals, navData, {
ghostGlobals: ghost.globals()
@ -101,10 +97,22 @@
ghost.loaded = loading.promise;
when.all([ghost.init(), filters.loadCoreFilters(ghost), helpers.loadCoreHelpers(ghost)]).then(function () {
// Assign the globals we have loaded
ghostGlobals = ghost.globals();
ghost.app().use(function (req, res, next) {
res.locals.messages = req.flash();
res.locals.siteTitle = ghostGlobals.title;
res.locals.siteDescription = ghostGlobals.description;
res.locals.siteUrl = ghostGlobals.url;
res.locals.activeTheme = ghostGlobals.activeTheme;
res.locals.activePlugin = ghostGlobals.activePlugin;
res.locals.availableThemes = ghost.paths().availableThemes;
res.locals.availablePlugins = ghost.paths().availablePlugins;
next();
});
/**
* API routes..
* @todo auth should be public auth not user auth

View File

@ -13,7 +13,7 @@
<li class="general active"><a href="#general">General</a></li>
<li class="publishing"><a href="#content">Content</a></li>
<li class="users"><a href="#users">Users</a></li>
<li class="appearance"><a href="#">Appearance</a></li>
<li class="appearance"><a href="#appearance">Appearance</a></li>
<li class="services"><a href="#">Connected Services</a></li>
<li class="plugins"><a href="#">Plugins</a></li>
</ul>
@ -200,4 +200,17 @@
</ul>
</section>
</section>
<section id="appearance" class="settings-content">
<header>
<h2 class="title">Appearance</h2>
</header>
<section class="content">
<h6 class="sub">Raw json be here</h6>
<p>Active theme: {{json settings.activeTheme}}</p>
<p>Available themes: {{json availableThemes}}</p>
<p>Available plugins: {{json availablePlugins}}</p>
</section>
</section>
</div>

View File

@ -40,6 +40,10 @@
return output;
});
ghost.registerThemeHelper('json', function (object, options) {
return JSON.stringify(object);
});
return when.all([
// Just one async helper for now, but could be more in the future
navHelper.registerWithGhost(ghost)

View File

@ -16,6 +16,11 @@
models = require('./shared/models'),
ExampleFilter = require('../content/plugins/exampleFilters'),
requireTree = require('./shared/require-tree'),
themeDirectories = requireTree('content/themes'),
pluginDirectories = requireTree('content/plugins'),
Ghost,
instance,
defaults,
@ -59,10 +64,17 @@
instance = this;
// Holds the filters
this.filterCallbacks = [];
instance.filterCallbacks = [];
// Holds the filter hooks (that are built in to Ghost Core)
this.filters = [];
instance.filters = [];
// Holds the theme directories temporarily
instance.themeDirectories = {};
// Holds the plugin directories temporarily
instance.pluginDirectories = {};
plugin = new ExampleFilter(instance).init();
@ -74,32 +86,44 @@
// load Plugins...
// var f = new FancyFirstChar(ghost).init();
_.extend(instance, {
app: function () { return app; },
config: function () { return config; },
globals: function () { return instance.globalConfig; }, // there's no management here to be sure this has loaded
// there's no management here to be sure this has loaded
globals: function () { return instance.globalConfig; },
dataProvider: models,
statuses: function () { return statuses; },
polyglot: function () { return polyglot; },
plugin: function () { return plugin; },
getPaths: function () {
return when.all([themeDirectories, pluginDirectories]).then(function (paths) {
instance.themeDirectories = paths[0];
instance.pluginDirectories = paths[1];
return;
});
},
paths: function () {
return {
'activeTheme': __dirname + '/../content/' + config.themeDir + '/' + config.activeTheme + '/',
'adminViews': __dirname + '/admin/views/',
'frontendViews': __dirname + '/frontend/views/',
'lang': __dirname + '/lang/'
'lang': __dirname + '/lang/',
'availableThemes': instance.themeDirectories,
'availablePlugins': instance.pluginDirectories
};
}
});
}
return instance;
};
Ghost.prototype.init = function () {
this.globalConfig = config.blogData;
return when.all([instance.dataProvider.init()]);
return when.all([instance.dataProvider.init(), instance.getPaths()]);
};
/**
@ -186,7 +210,6 @@
var self = this;
return function initTheme(req, res, next) {
app.set('view engine', 'hbs');
if (/(^\/ghost$|^\/ghost\/)/.test(req.url) === false) {
app.engine('hbs', hbs.express3(
{partialsDir: self.paths().activeTheme + 'partials'}
@ -199,7 +222,6 @@
}
app.use(express['static'](self.paths().activeTheme));
app.use('/content/images', express['static'](path.join(__dirname, '/../content/images')));
next();
};
};

View File

@ -60,6 +60,20 @@ module.exports = {
"created_by": 1,
"updated_by": 1,
"type": "general"
},
{
"key": "activePlugins",
"value": "",
"created_by": 1,
"updated_by": 1,
"type": "general"
},
{
"key": "activeTheme",
"value": "content/themes/casper",
"created_by": 1,
"updated_by": 1,
"type": "general"
}
],

View File

@ -0,0 +1,64 @@
(function() {
"use strict";
var when = require('when'),
keys = require('when/keys'),
fs = require('fs'),
path = require('path'),
extend = function (obj, source) {
var key;
for (key in source) {
if (source.hasOwnProperty(key)) {
obj[key] = source[key];
}
}
return obj;
},
readDir = function (dir, options, depth) {
depth = depth || 0;
options = extend({
index: true
}, options);
if (depth > 1) {
return null;
}
var subtree = {},
treeDeferred = when.defer(),
treePromise = treeDeferred.promise;
fs.readdir(dir, function (error, files) {
files.forEach(function (file) {
var fileDeferred = when.defer(),
filePromise = fileDeferred.promise,
ext = path.extname(file),
name = path.basename(file, ext),
fpath = path.join(dir, file);
subtree[name] = filePromise;
fs.lstat(fpath, function (error, result) {
if (result.isDirectory()) {
fileDeferred.resolve(readDir(fpath, options, depth + 1));
} else {
fileDeferred.resolve(fpath);
}
});
});
return keys.all(subtree).then(function(theFiles) {
return treeDeferred.resolve(theFiles);
});
});
return when(treePromise).then(function (prom) {
return prom;
});
},
readAll = function (dir, options, depth) {
return when(readDir(dir, options, depth)).then(function (paths) {
return paths;
});
};
module.exports = readAll;
}());