diff --git a/lib/app.js b/lib/app.js
index 65ef3de4..7b0f585f 100644
--- a/lib/app.js
+++ b/lib/app.js
@@ -7,25 +7,17 @@ var express = require('express'),
app = express(),
options = require('./config'),
store = require('./store')(options.store),
+ models = require('./models'),
handlers = require('./handlers'),
middleware = require('./middleware'),
flattened;
-app.store = store;
+app.store = store;
+app.models = models.createModels(store);
app.templates = {};
app.PRODUCTION = 'production';
app.DEVELOPMENT = 'development';
-Object.defineProperties(app, {
- production: {
- get: function () {
- return this.set('env') === this.PRODUCTION;
- }
- }
-});
-
-// TODO: Refactor this!
-handlers = handlers(app);
// Apply the keys from the config file. All nested properties are
// space delimited to match the express style.
@@ -48,6 +40,7 @@ app.use(express.errorHandler({showStack: true, dumpExceptions: true}));
app.use(middleware.noslashes());
app.use(middleware.ajax());
app.use(middleware.jsonp());
+app.use(middleware.helpers(app));
// Create a Hogan/Mustache handler for templates.
app.engine('html', function (path, options, fn) {
diff --git a/lib/handlers.js b/lib/handlers.js
index b3e20563..af987804 100644
--- a/lib/handlers.js
+++ b/lib/handlers.js
@@ -1,7 +1,7 @@
var async = require('asyncjs'),
path = require('path'),
utils = require('./utils'),
- createBinModel = require('./models/bin');
+ handlers;
// Create a not found error object.
function NotFound() {
@@ -9,259 +9,245 @@ function NotFound() {
}
NotFound.prototype = Object.create(Error.prototype);
-module.exports = function (app) {
- var binModel = createBinModel(app.store), handlers;
-
- handlers = {
- getDefault: function (req, res) {
- handlers.renderFiles(req, res);
- },
- getBin: function (req, res, next) {
- handlers.render(req, res, req.bin);
- },
- getBinPreview: function (req, res, next) {
- var options = {edit: !req.param('quiet')};
- handlers.formatPreview(req.bin, options, function (err, formatted) {
- if (err) {
- next(err);
- }
-
- if (formatted) {
- res.send(formatted);
- } else {
- res.contentType('js');
- res.send(req.bin.javascript);
- }
- });
- },
- getBinSource: function (req, res) {
- res.contentType('json');
- var output = JSON.stringify(handlers.templateFromBin(req.bin));
- if (!req.ajax) {
- res.contentType('js');
- output = 'var template = ' + output;
+module.exports = handlers = {
+ getDefault: function (req, res) {
+ handlers.renderFiles(req, res);
+ },
+ getBin: function (req, res, next) {
+ handlers.render(req, res, req.bin);
+ },
+ getBinPreview: function (req, res, next) {
+ var options = {edit: !req.param('quiet')};
+ handlers.formatPreview(req.bin, req.helpers, options, function (err, formatted) {
+ if (err) {
+ next(err);
}
- res.send(output);
- },
- getBinSourceFile: function (req, res) {
- var format = req.params.format;
- res.contentType(format);
- if (format !== 'html') {
- if (format === 'js' || format === 'json') {
- format = 'javascript';
- }
- res.send(req.bin[format]);
+ if (formatted) {
+ res.send(formatted);
} else {
- handlers.getBinPreview(req, res);
+ res.contentType('js');
+ res.send(req.bin.javascript);
}
- },
- redirectToLatest: function (req, res) {
- var path = req.path.replace('latest', req.bin.revision);
- res.redirect(303, path);
- },
- createBin: function (req, res, next) {
- var data = utils.extract(req.body, 'html', 'css', 'javascript');
+ });
+ },
+ getBinSource: function (req, res) {
+ res.contentType('json');
+ var output = JSON.stringify(handlers.templateFromBin(req.bin));
+ if (!req.ajax) {
+ res.contentType('js');
+ output = 'var template = ' + output;
+ }
+ res.send(output);
+ },
+ getBinSourceFile: function (req, res) {
+ var format = req.params.format;
- binModel.create(data, function (err, result) {
+ res.contentType(format);
+ if (format !== 'html') {
+ if (format === 'js' || format === 'json') {
+ format = 'javascript';
+ }
+ res.send(req.bin[format]);
+ } else {
+ handlers.getBinPreview(req, res);
+ }
+ },
+ redirectToLatest: function (req, res) {
+ var path = req.path.replace('latest', req.bin.revision);
+ res.redirect(303, path);
+ },
+ createBin: function (req, res, next) {
+ var data = utils.extract(req.body, 'html', 'css', 'javascript');
+
+ req.models.bin.create(data, function (err, result) {
+ if (err) {
+ return next(err);
+ }
+ handlers.renderCreated(req, res, result);
+ });
+ },
+ createRevision: function (req, res, next) {
+ var panel = req.param('panel'),
+ params = {};
+
+ if (req.param('method') === 'save') {
+ params = utils.extract(req.body, 'html', 'css', 'javascript');
+ params.url = req.bin.url;
+ params.revision = req.bin.revision + 1;
+ req.models.bin.createRevision(params, function (err, result) {
if (err) {
return next(err);
}
handlers.renderCreated(req, res, result);
});
- },
- createRevision: function (req, res, next) {
- var panel = req.param('panel'),
- params = {};
+ } else if (req.param('method') === 'update') {
+ params[panel] = req.param('content');
+ params.streamingKey = req.param('checksum');
+ params.revision = req.param('revision');
+ params.url = req.param('code');
- if (req.param('method') === 'save') {
- params = utils.extract(req.body, 'html', 'css', 'javascript');
- params.url = req.bin.url;
- params.revision = req.bin.revision + 1;
- binModel.createRevision(params, function (err, result) {
- if (err) {
- return next(err);
- }
- handlers.renderCreated(req, res, result);
- });
- } else if (req.param('method') === 'update') {
- params[panel] = req.param('content');
- params.streamingKey = req.param('checksum');
- params.revision = req.param('revision');
- params.url = req.param('code');
+ req.models.bin.updatePanel(panel, params, function (err, result) {
+ if (err) {
+ return next(err);
+ }
+ res.json({ok: true, error: false});
+ });
+ } else {
+ next();
+ }
+ },
+ notFound: function (req, res) {
+ var files = handlers.defaultFiles();
+ files[2] = 'not_found.js';
+ handlers.renderFiles(req, res, files);
+ },
+ loadBin: function (req, res, next) {
+ var rev = parseInt(req.params.rev, 10) || 1,
+ query = {id: req.params.bin, revision: rev};
- binModel.updatePanel(panel, params, function (err, result) {
- if (err) {
- return next(err);
- }
- res.json({ok: true, error: false});
- });
+ function complete(err, result) {
+ if (err) {
+ return next(new NotFound('Could not find bin: ' + req.params.bin));
} else {
+ req.bin = result;
next();
}
- },
- notFound: function (req, res) {
- var files = handlers.defaultFiles();
- files[2] = 'not_found.js';
- handlers.renderFiles(req, res, files);
- },
- loadBin: function (req, res, next) {
- var rev = parseInt(req.params.rev, 10) || 1,
- query = {id: req.params.bin, revision: rev};
+ }
- function complete(err, result) {
+ // TODO: Re-factor this logic.
+ if ((req.params.rev || req.path.indexOf('latest') === -1) && req.path.indexOf('save') === -1) {
+ req.models.bin.load(query, complete);
+ } else {
+ req.models.bin.latest(query, complete);
+ }
+ },
+ render: function (req, res, bin) {
+ var template = handlers.templateFromBin(bin),
+ jsbin = handlers.jsbin(bin, req.helpers.production ? req.helpers.set('version') : 'debug');
+
+ req.helpers.analytics(function (err, analytics) {
+ res.render('index', {
+ tips: '{}',
+ revision: bin.revision || 1,
+ jsbin: JSON.stringify(jsbin),
+ json_template: JSON.stringify(template),
+ version: jsbin.version,
+ analytics: analytics,
+ 'production?': req.helpers.production
+ });
+ });
+ },
+ renderFiles: function (req, res, files) {
+ files = files || handlers.defaultFiles();
+ async.files(files, req.helpers.set('views')).readFile("utf8").toArray(function (err, results) {
+ if (!err) {
+ handlers.render(req, res, {
+ html: results[0].data,
+ css: results[1].data,
+ javascript: results[2].data
+ });
+ }
+ });
+ },
+ renderCreated: function (req, res, bin) {
+ var permalink = req.helpers.urlForBin(bin),
+ editPermalink = req.helpers.editUrlForBin(bin);
+
+ if (req.ajax) {
+ res.json({
+ code: bin.url,
+ root: req.helpers.set('url full'),
+ created: (new Date()).toISOString(), // Should be part of bin.
+ revision: bin.revision,
+ url: permalink,
+ edit: editPermalink,
+ html: editPermalink,
+ js: editPermalink,
+ title: utils.titleForBin(bin),
+ allowUpdate: false,
+ checksum: req.streamingKey
+ });
+ } else {
+ res.redirect(303, '/' + bin.url + '/' + bin.revision + '/edit');
+ }
+ },
+ jsbin: function (bin, version) {
+ return {
+ root: '',
+ version: version,
+ state: {
+ stream: false,
+ code: bin.url || null,
+ revision: bin.revision || 1
+ }
+ };
+ },
+ templateFromBin: function (bin) {
+ return utils.extract(bin, 'html', 'css', 'javascript');
+ },
+ defaultFiles: function () {
+ return ['html', 'css', 'js'].map(function (ext) {
+ return 'default.' + ext;
+ });
+ },
+ formatPreview: function (bin, helpers, options, fn) {
+ var formatted = bin.html || '',
+ insert = [], parts, last, context;
+
+ // TODO: Re implement this entire block with an HTML parser.
+ if (formatted) {
+ helpers.analytics(function (err, analytics) {
if (err) {
- return next(new NotFound('Could not find bin: ' + req.params.bin));
+ return fn(err);
+ }
+
+ if (formatted.indexOf('%code%') > -1) {
+ formatted = formatted.replace(/%code%/g, bin.javascript);
} else {
- req.bin = result;
- next();
+ insert.push('');
}
- }
- // TODO: Re-factor this logic.
- if ((req.params.rev || req.path.indexOf('latest') === -1) && req.path.indexOf('save') === -1) {
- binModel.load(query, complete);
- } else {
- binModel.latest(query, complete);
- }
- },
- render: function (req, res, bin) {
- var template = handlers.templateFromBin(bin),
- jsbin = handlers.jsbin(bin);
+ if (!options || options.edit !== false) {
+ insert.push('');
+ }
- handlers.analytics(function (err, analytics) {
- res.render('index', {
- tips: '{}',
- revision: bin.revision || 1,
- jsbin: JSON.stringify(jsbin),
- json_template: JSON.stringify(template),
- version: jsbin.version,
- analytics: analytics,
- 'production?': app.production
- });
- });
- },
- renderFiles: function (req, res, files) {
- files = files || handlers.defaultFiles();
- async.files(files, app.set('views')).readFile("utf8").toArray(function (err, results) {
- if (!err) {
- handlers.render(req, res, {
- html: results[0].data,
- css: results[1].data,
- javascript: results[2].data
+ if (helpers.production) {
+ insert.push(analytics);
+ }
+
+ // Append scripts to the bottom of the page.
+ if (insert.length) {
+ parts = formatted.split('