summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjulie lala <jules@okfoc.us>2015-04-13 16:45:41 -0400
committerjulie lala <jules@okfoc.us>2015-04-13 16:45:41 -0400
commitc702d76549ead663daed0147f5c160c368a61c45 (patch)
treecad22d834c0acd4c871a8bdaa148d1f1f19caf34
parentedcc3c497f05e14bdd5655a0bc154cee04784a60 (diff)
parentebb17679c64723dc1378079f9da0697673b21f5d (diff)
Merge branch 'twohustlers' of github.com:okfocus/okcms into twohustlers
-rw-r--r--Readme.md1
-rw-r--r--app/index.js66
-rw-r--r--app/node_modules/okadminview/index.js131
-rw-r--r--app/node_modules/okdb/index.js2
-rw-r--r--app/node_modules/okserver/index.js7
-rw-r--r--app/node_modules/okview/index.js133
-rw-r--r--examples/index.js2
-rw-r--r--site/index.js8
-rw-r--r--themes/okadmin/public/css/main.css39
-rw-r--r--themes/okadmin/public/js/app.js11
-rw-r--r--themes/okadmin/templates/404.liquid54
-rw-r--r--themes/okadmin/templates/5xx.liquid54
-rw-r--r--themes/okadmin/templates/index.liquid6
-rw-r--r--themes/okadmin/templates/partials/flash.liquid9
-rw-r--r--themes/okadmin/templates/partials/inputs.liquid4
-rw-r--r--themes/okadmin/templates/partials/tail.liquid8
-rw-r--r--themes/okadmin/templates/resource.liquid2
17 files changed, 357 insertions, 180 deletions
diff --git a/Readme.md b/Readme.md
index a96860f..b9e5b2d 100644
--- a/Readme.md
+++ b/Readme.md
@@ -10,4 +10,5 @@
* node index
Server will be running on http://lvh.me:1337/
+Admin area available on http://lvh.me:1337/admin/
diff --git a/app/index.js b/app/index.js
index 708943c..419adfc 100644
--- a/app/index.js
+++ b/app/index.js
@@ -32,10 +32,11 @@ function OKCMS(options) {
var adminConfig = options.admin || {};
var adminRoot = this._adminRoot = adminConfig.root ||
path.join(__dirname, '../themes/okadmin/public');
- var adminPath = this._adminPath = '/_admin';
+ var adminPath = this._adminPath = '/admin';
var templateRoot = options.templateRoot || 'templates';
var adminTemplateRoot = options.templateRoot ||
path.join(__dirname, '../themes/okadmin/templates');
+ var debug = options.debug || false;
// Set metadata defaults
// TODO Abstract this out somewhere else
@@ -84,13 +85,17 @@ function OKCMS(options) {
});
var resourceCache = this._resourceCache =
this._createResources(resourceConfig, db, schemas);
+ var errorHandler = createErrorHandlerProducer(
+ templateProvider, adminTemplateProvider, debug);
// Create view instances from config
var views = this._views =
- this._createViews(viewConfig, db, meta, resourceCache, templateProvider);
+ this._createViews(viewConfig, db, meta, resourceCache, templateProvider,
+ errorHandler);
var adminViews = this._adminViews =
this._createAdminViews(adminPath, app, express, resourceConfig,
- resourceCache, adminTemplateProvider, adminMeta);
+ resourceCache, adminTemplateProvider, adminMeta,
+ errorHandler);
// Create services
var imageService = OKImageService({
@@ -109,7 +114,8 @@ function OKCMS(options) {
adminPath: adminPath,
services: {
image: imageService
- }
+ },
+ errorHandler: errorHandler
});
}
@@ -152,7 +158,7 @@ OKCMS.prototype._createResources = function(resourceConfig, db, schemaCache) {
};
OKCMS.prototype._createViews = function(viewConfig, db,
- meta, resourceCache, templateProvider) {
+ meta, resourceCache, templateProvider, errorHandler) {
viewConfig = viewConfig || {};
var self = this;
var createQueries = this._createQueries.bind(this);
@@ -171,7 +177,8 @@ OKCMS.prototype._createViews = function(viewConfig, db,
route: route,
template: template,
queries: queries,
- meta: meta
+ meta: meta,
+ errorHandler: errorHandler
});
return cache;
}, {});
@@ -193,7 +200,8 @@ OKCMS.prototype._createViews = function(viewConfig, db,
};
OKCMS.prototype._createAdminViews = function(path, app, express,
- resourceConfig, resourceCache, templateProvider, meta) {
+ resourceConfig, resourceCache, templateProvider, meta,
+ errorHandler) {
var views = {};
var withTrail = withTrailingSlash(path);
var withoutTrail = withoutTrailingSlash(path);
@@ -217,7 +225,8 @@ OKCMS.prototype._createAdminViews = function(path, app, express,
resourceConfig: resourceConfig,
resourceCache: resourceCache,
templateProvider: templateProvider,
- meta: meta
+ meta: meta,
+ errorHandler: errorHandler
});
return views;
};
@@ -269,6 +278,47 @@ ResourceCache.prototype.get = function(type, id) {
}
};
+/**
+ * Higher order function implementing customizable error handling
+ */
+function createErrorHandlerProducer(templateProvider, adminTemplateProvider, debugMode) {
+
+ var template404 = templateProvider.getTemplate('404') ||
+ adminTemplateProvider.getTemplate('404');
+ var template5xx = templateProvider.getTemplate('5xx') ||
+ adminTemplateProvider.getTemplate('5xx');
+
+ if (!template404 || !template5xx)
+ throw new Error('No error templates provided by admin theme or user')
+
+ return debugMode ? createDebugHandler : createErrorHandler;
+
+ function createErrorHandler(req, res, status) {
+ return function handleError(err) {
+ switch (status) {
+ case 404:
+ template404.render().then(function(rendered) {
+ res.status(status);
+ res.send(rendered);
+ });
+ break;
+ default:
+ template5xx.render().then(function(rendered) {
+ res.status(status);
+ res.send(rendered);
+ });
+ }
+
+ };
+ }
+
+ function createDebugHandler(req, res, status) {
+ return function handleError(err) {
+ res.send(err.stack);
+ };
+ }
+}
+
module.exports = {
createApp: function(options) {
diff --git a/app/node_modules/okadminview/index.js b/app/node_modules/okadminview/index.js
index ac633e8..161a195 100644
--- a/app/node_modules/okadminview/index.js
+++ b/app/node_modules/okadminview/index.js
@@ -37,6 +37,8 @@ function OKAdminView(options) {
throw new Error('No templateProvider provided to OKAdminView');
if (!options.meta)
throw new Error('No meta query provided to OKAdminView');
+ if (!options.errorHandler)
+ throw new Error('No error handler provided to OKAdminView');
var app = options.app;
var express = options.express;
@@ -44,6 +46,7 @@ function OKAdminView(options) {
var resourceCache = this._resourceCache = options.resourceCache;
var resourceConfig = this._resourceConfig = cloneDeep(options.resourceConfig);
var provider = options.templateProvider;
+ var error = this._error = options.errorHandler;
// Load templates
var templates = this._templates =
@@ -112,12 +115,10 @@ function OKAdminView(options) {
}
}));
- var auth = passport.authenticate('digest', {session: false});
-
// This should really be mounted on the router, but can't be due to
// https://github.com/jaredhanson/passport-http/pull/16
- app.use('/_admin/', passport.initialize());
- app.all('/_admin/:path*', auth);
+ app.use('/admin/', passport.initialize());
+ app.all('/admin/:path*', passport.authenticate('digest', {session: false}));
router.get('/', function readIndex(req, res, next) {
fetchIndexTemplateData(meta, indexQueries).then(function(data) {
@@ -125,22 +126,22 @@ function OKAdminView(options) {
success: req.flash('success'),
errors: req.flash('errors')
}));
- }).fail(errorHandler(req, res));
+ }).fail(error(req, res, 500));
});
- router.get('/:type/new/', function createResourceView(req, res, next) {
+ router.get('/:type/__new__/', function createResourceView(req, res, next) {
var type = req.params.type || '';
var resource = resourceCache.get(type);
if (!resource) {
- errorHandler(req, res)(new Error('No such resource ' + type));
+ error(req, res, 404)(new Error('No such resource ' + type));
} else {
meta.get().then(function(metadata) {
- var templateData = getResourceTemplateData(metadata, resource, {});
+ var templateData = transformData(metadata, resource, {});
view.renderResourceNew(req, res, assign(templateData, {
success: req.flash('success'),
errors: req.flash('errors'),
}));
- }).fail(errorHandler(req, res));
+ }).fail(error(req, res, 500));
}
});
@@ -148,26 +149,27 @@ function OKAdminView(options) {
var type = req.params.type || '';
var id = req.params.id || '';
var resource = resourceCache.get(type, id);
- if (!resource) {
- errorHandler(req, res)(new Error('No such resource'));
- } else {
- var query = OKQuery({
- resource: resource,
- query: id
- });
- fetchResourceTemplateData(meta, query, getResourceTemplateData)
- .then(function(data) {
- if (!data) {
- resourceMissingHandler(req, res)()
- } else {
- view.renderResource(req, res, assign(data, {
- success: req.flash('success'),
- errors: req.flash('errors')
- }));
- }
- }).fail(errorHandler(req, res));
- }
-
+ var query = OKQuery({
+ resource: resource,
+ query: id
+ });
+ fetchResourceTemplateData(meta, query, transformData)
+ .then(function(data) {
+ if (!data) {
+ resourceMissingHandler(req, res)()
+ } else {
+ view.renderResource(req, res, assign(data, {
+ success: req.flash('success'),
+ errors: req.flash('errors')
+ }));
+ }
+ }).fail(function(err) {
+ if (err.message === 'No resource data') {
+ error(req, res, 404)(new Error('No such resource'));
+ } else {
+ error(req, res, 500)(err);
+ }
+ });
});
router.post('/:type/', function createResource(req, res, next) {
@@ -175,7 +177,7 @@ function OKAdminView(options) {
var resource = resourceCache.get(type);
var data = req.body;
if (!resource) {
- errorHandler(req, res)(new Error('No such resource ' + type));
+ error(req, res, 400)(new Error('No such resource ' + type));
} else {
meta.get().then(function(metadata) {
try {
@@ -183,26 +185,24 @@ function OKAdminView(options) {
resource.create(data).then(function(created) {
req.flash('success', {action: 'create'});
res.redirect(303, resource.getID(data));
- }).fail(errorHandler(req, res));
+ }).fail(error(req, res, 500));
} catch (errors) {
- var templateData = getResourceTemplateData(metadata, resource, data);
+ var templateData = transformData(metadata, resource, data);
view.renderResource(req, res, assign(templateData, {errors: errors}));
}
- }).fail(errorHandler(req, res));;
+ }).fail(error(req, res, 500));;
}
});
- router.put('/:type/__batch/', function putBatch(req, res, next) {
+ router.put('/:type/__batch__/', function putBatch(req, res, next) {
var type = req.params.type;
var body = req.body || {};
var resourcesJSON = body[type];
var resource = resourceCache.get(type);
if (!resourcesJSON || !resourcesJSON.length) {
- res.status(400);
- errorHandler(req, res)(new Error('Bad request'));
+ error(req, res, 400)(new Error('Bad request'));
} else if (!resource) {
- res.status(404);
- errorHandler(req, res)(new Error('No such resource'));
+ error(req, res, 404)(new Error('No such resource'));
} else {
try {
var ids = [];
@@ -212,7 +212,7 @@ function OKAdminView(options) {
return data;
});
} catch (e) {
- errorHandler(req, res)(new Error('Resource batch contains invalid JSON'));
+ error(req, res, 500)(new Error('Resource batch contains invalid JSON'));
return;
}
Q.all([
@@ -222,7 +222,7 @@ function OKAdminView(options) {
var metadata = results.shift();
req.flash('success', {action: 'batch_update'});
res.redirect(303, '../..');
- }).fail(errorHandler(req, res));
+ }).fail(error(req, res, 500));
}
});
@@ -232,7 +232,7 @@ function OKAdminView(options) {
var data = req.body;
var resource = resourceCache.get(type, id);
if (!resource) {
- errorHandler(req, res)(new Error('No such resource ' + type));
+ error(req, res, 400)(new Error('No such resource ' + type));
} else {
// TODO Prob should make metadata synchronous...
meta.get().then(function(metadata) {
@@ -241,12 +241,12 @@ function OKAdminView(options) {
resource.update(id, data).then(function(updated) {
req.flash('success', {action: 'update'});
res.redirect(303, '../' + resource.getID(updated));
- }).fail(errorHandler(req, res));
+ }).fail(error(req, res, 500));
} catch (errors) {
- var templateData = getResourceTemplateData(metadata, resource, data);
+ var templateData = transformData(metadata, resource, data);
view.renderResource(req, res, assign(templateData, {errors: errors}));
}
- }).fail(errorHandler(req, res));
+ }).fail(error(req, res, 500));
}
});
@@ -255,14 +255,14 @@ function OKAdminView(options) {
var id = req.params.id;
var resource = resourceCache.get(type, id);
if (!resource) {
- errorHandler(req, res)(new Error('No such resource ' + type));
+ error(req, res, 500)(new Error('No such resource ' + type));
} else {
meta.get().then(function(metadata) {
resource.destroy(id).then(function() {
req.flash('success', {action: 'delete'});
res.redirect(303, '../..');
- }).fail(errorHandler(req, res));
- }).fail(errorHandler(req, res));
+ }).fail(error(req, res, 500));
+ }).fail(error(req, res, 500));
}
});
@@ -271,9 +271,9 @@ function OKAdminView(options) {
}
/**
- * Get template data for a single resource
+ * Yields formatted template data for a single resource
*/
-function getResourceTemplateData(meta, resource, data) {
+function transformData(meta, resource, data) {
meta = meta || {};
resource = resource || {};
data = data || {};
@@ -306,21 +306,21 @@ OKAdminView.prototype.renderIndex = function(req, res, data) {
data = data || {};
this._templates['index'].render(data).then(function(rendered) {
res.send(rendered);
- }).fail(errorHandler(req, res));
+ }).fail(this._error(req, res, 500));
};
OKAdminView.prototype.renderResource = function(req, res, data) {
data = data || {};
this._templates['resource'].render(data).then(function(rendered) {
res.send(rendered);
- }).fail(errorHandler(req, res));
+ }).fail(this._error(req, res, 500));
};
OKAdminView.prototype.renderResourceNew = function(req, res, data) {
data = data || {meta: {}, resource: {}};
this._templates['resource_new'].render(data).then(function(rendered) {
res.send(rendered);
- }).fail(errorHandler(req, res));
+ }).fail(this._error(req, res, 500));
};
/**
@@ -378,30 +378,15 @@ function fetchResourceTemplateData(meta, query, fn) {
return Q.promise(function(resolve, reject) {
meta.get().then(function(metadata) {
query.get().then(function(data) {
- var resource = query.resource;
- resolve(fn(metadata, resource, data));
+ if (!data) {
+ reject(new Error('No resource data'));
+ } else {
+ var resource = query.resource;
+ resolve(fn(metadata, resource, data));
+ }
}).fail(reject);
}).fail(reject)
});
}
-/**
- * TODO Real error handling
- */
-function errorHandler(req, res) {
- return function(err) {
- res.send(err.stack);
- };
-}
-
-/**
- * TODO Real 404 handling
- */
-function resourceMissingHandler(req, res) {
- return function() {
- res.status(404);
- res.send('404');
- }
-}
-
module.exports = OKAdminView;
diff --git a/app/node_modules/okdb/index.js b/app/node_modules/okdb/index.js
index 85156d1..5aa22a2 100644
--- a/app/node_modules/okdb/index.js
+++ b/app/node_modules/okdb/index.js
@@ -180,7 +180,7 @@ FSDB.prototype.getMeta = function() {
*/
function autoincrement(wrapper, schema, data) {
return schema.autoIncrementFields.reduce(function(data, field) {
- var last = wrapper.chain().sort(field).first().value();
+ var last = wrapper.chain().sortByOrder([field], [true]).last().value();
var index = last ? last[field] : -1;
var incremented = {};
incremented[field] = (parseInt(index) + 1);
diff --git a/app/node_modules/okserver/index.js b/app/node_modules/okserver/index.js
index cf06b3c..abca8b5 100644
--- a/app/node_modules/okserver/index.js
+++ b/app/node_modules/okserver/index.js
@@ -16,6 +16,8 @@ function OKServer(options) {
throw new Error('No admin root directory provided to OKServer');
if (!options.adminPath)
throw new Error('No admin path provided to OKServer');
+ if (!options.errorHandler)
+ throw new Error('No error handler provided to OKServer');
var root = options.root;
var adminRoot = options.adminRoot;
var adminPath = options.adminPath;
@@ -25,6 +27,7 @@ function OKServer(options) {
var router = express.Router({
strict: app.get('strict routing')
});
+ var error = options.errorHandler;
var services = options.services || {};
Object.keys(views)
// Sort such that more general routes are matched last
@@ -67,6 +70,10 @@ function OKServer(options) {
// Make sure this lady is last. Checks whether the desired
// route has a trailing-slash counterpart and redirects there
app.use(slash());
+ // Otherwise it's a 404
+ app.use(function(req, res) {
+ error(req, res, 404)(new Error('No matching route'));
+ });
}
OKServer.prototype.listen = function listen(port) {
diff --git a/app/node_modules/okview/index.js b/app/node_modules/okview/index.js
index 2d1c0a0..63f22b5 100644
--- a/app/node_modules/okview/index.js
+++ b/app/node_modules/okview/index.js
@@ -24,8 +24,11 @@ function OKView(options) {
throw new Error('No meta resource provided to OKView');
if (!options.route)
throw new Error('No route provided to OKView');
+ if (!options.errorHandler)
+ throw new Error('No error handler provided to OKView');
var route = options.route;
var mount = options.mount || 'get';
+ var error = this._error = options.errorHandler;
this._template = options.template;
var meta = this._meta = options.meta;
var queries = this._queries = options.queries || [];
@@ -47,16 +50,9 @@ function OKView(options) {
enumerable: true
});
- this._middleware = createMiddleware(this);
- this._fetchTemplateData = unbound ? fetchUnbound : fetchBound;
-
- function fetchUnbound(id) {
- return fetchTemplateData(meta, queries, id)
- }
-
- function fetchBound() {
- return fetchTemplateData(meta, queries);
- }
+ this._middleware = unbound
+ ? unboundMiddleware(this, meta, queries, error)
+ : boundMiddleware(this, meta, queries, error);
}
OKView.prototype.middleware = function() {
@@ -66,39 +62,20 @@ OKView.prototype.middleware = function() {
OKView.prototype.render = function(req, res, data) {
this._template.render(data).then(function(html) {
res.send(html);
- }).fail(errorHandler(req, res, data));
-};
-
-OKView.prototype.fetchTemplateData = function() {
- return this._fetchTemplateData.apply(this, arguments);
+ }).fail(this._error(req, res, 500));
};
/**
- * Unbound views need different middleware to resolve requests
- */
-function createMiddleware(view) {
- if (view.unbound) {
- return unboundMiddleware(view);
- } else {
- return boundMiddleware(view);
- }
-}
-
-// Note that these middleware do not call next
-// and should thus always be added at the end of the
-// middleware chain.
-
-/**
* Creates middleware for a view which does not
* yet have a resource id associated with it
*/
-function unboundMiddleware(view) {
+function unboundMiddleware(view, meta, queries, error) {
var paramName = getParamName(view.route);
return function(req, res, next) {
var id = req.params[paramName];
- view.fetchTemplateData(id).then(function(data) {
+ fetchTemplateData(meta, queries, id).then(function(data) {
view.render(req, res, data);
- }).fail(errorHandler(req, res, next));
+ }).fail(failHandler(req, res, error));
};
}
@@ -106,20 +83,22 @@ function unboundMiddleware(view) {
* Creates middleware for a view which already
* has a resource id associated with it
*/
-function boundMiddleware(view) {
+function boundMiddleware(view, meta, queries, error) {
return function(req, res, next) {
- view.fetchTemplateData().then(function(data) {
+ fetchTemplateData(meta, queries).then(function(data) {
view.render(req, res, data);
- }).fail(errorHandler(req, res, next));
+ }).fail(failHandler(req, res, error));
};
}
-/**
- * TODO BS error handling for now
- */
-function errorHandler(req, res, next) {
- return function(err) {
- res.send(err.stack);
+function failHandler(req, res, error) {
+ return function (err) {
+ // TODO Use custom exception type
+ if (err.message === 'No resource found') {
+ error(req, res, 404)(err);
+ } else {
+ error(req, res, 500)(err);
+ }
}
}
@@ -138,9 +117,12 @@ function getParamName(route) {
* and returns a promise for an object merging all queried
* data, pluralizing keys where necessary.
*
- * Lil bit convoluted, sorry.
+ * Pretty convoluted, sorry.
*/
function fetchTemplateData(meta, queries, id) {
+ // If there's only one query, we assume it is for a single
+ // resource and will resolve errors if no data is found
+ var single = queries && queries.length === 1;
return Q.promise(function(resolve, reject) {
return Q.all(
[meta.get()].concat(queries.map(function(query) {
@@ -148,39 +130,44 @@ function fetchTemplateData(meta, queries, id) {
})))
.then(function(results) {
var metadata = results.shift();
- var normalized = results.reduce(function(cache, result, i) {
- // Could be just some rogue request
- if (!result) {
- return cache;
- }
- var resource = queries[i].resource;
- var type = queries[i].type;
- var manyResult = isarray(result);
- // Inform template of ID in generic field
- if (manyResult) {
- result = result.map(function(data) {
- return assign({}, data, {id: resource.getID(data)})
- });
- } else {
- result = assign({}, result, {id: resource.getID(result)});
- }
- // If we have a lot of results for a certain type,
- // we pluralize the key and yield an array of results
- if (cache[type] || manyResult) {
- var plural = pluralize(type);
- delete cache[type];
- cache[plural] = [];
+ if (single && !results[0]) {
+ reject(new Error('No resource found'));
+ } else {
+ var normalized = results.reduce(function(cache, result, i) {
+ // Could be just some rogue request
+ if (!result) {
+ return cache;
+ }
+ var resource = queries[i].resource;
+ var type = queries[i].type;
+ var manyResult = isarray(result);
+ // Inform template of ID in generic field
if (manyResult) {
- cache[plural] = cache[plural].concat(result);
+ result = result.map(function(data) {
+ return assign({}, data, {id: resource.getID(data)})
+ });
+ } else {
+ result = assign({}, result, {id: resource.getID(result)});
+ }
+ // If we have a lot of results for a certain type,
+ // we pluralize the key and yield an array of results
+ if (cache[type] || manyResult) {
+ var plural = pluralize(type);
+ delete cache[type];
+ cache[plural] = [];
+ if (manyResult) {
+ cache[plural] = cache[plural].concat(result);
+ } else {
+ cache[plural].push(result);
+ }
} else {
- cache[plural].push(result);
+ cache[type] = result;
}
- } else {
- cache[type] = result;
- }
- return cache;
- }, {meta: metadata});
- resolve(normalized);
+ return cache;
+ }, {meta: metadata});
+
+ resolve(normalized);
+ }
}).fail(reject);
});
}
diff --git a/examples/index.js b/examples/index.js
index 95d2bcf..3e9f509 100644
--- a/examples/index.js
+++ b/examples/index.js
@@ -4,6 +4,8 @@ var app = okcms.createApp({
root: 'public',
+ debug: false,
+
schemas: {
page: {
id: {type: 'string'},
diff --git a/site/index.js b/site/index.js
index a59e74e..dace900 100644
--- a/site/index.js
+++ b/site/index.js
@@ -15,15 +15,15 @@ var app = okcms.createApp({
id: {type: 'string', id: true},
title: {type: 'string'},
shortname: {type: 'string'},
- description: {type: 'text'},
- video: {type: 'video'},
- images: {type: 'captioned-image-list'},
category: {type: 'enum', options: [
'retail',
'advertising',
'experiential',
'content']
- }
+ },
+ description: {type: 'text'},
+ video: {type: 'video'},
+ images: {type: 'captioned-image-list'},
}
},
diff --git a/themes/okadmin/public/css/main.css b/themes/okadmin/public/css/main.css
index 3762fd4..67271bc 100644
--- a/themes/okadmin/public/css/main.css
+++ b/themes/okadmin/public/css/main.css
@@ -109,7 +109,7 @@ h2 {
pointer-events: none;
}
- /* Makes the button look like a link */
+/* Makes the button look like a link */
.main.index .resource-category button {
background: none !important;
height: 1.5em;
@@ -117,6 +117,8 @@ h2 {
padding: 0 !important;
font: inherit;
cursor: pointer;
+ font-family: Monaco, monospace;
+ text-transform: uppercase;
}
.main.index .resource-category .btn {
@@ -134,7 +136,7 @@ h2 {
}
.main.index .resource-category .btn:hover {
- border-bottom: 3px solid rgba(0, 0, 0, 0.25);
+ border-bottom: 1px solid rgba(0, 0, 0, 0.25);
}
.main.index .resource-category .btn {
@@ -203,6 +205,12 @@ label {
padding: 0 0.5em;
margin-bottom: 1em;
}
+.main.resource form input[name=id] {
+ width: 15em;
+}
+button, input[type=submit] {
+ cursor: pointer;
+}
.main.resource form .group {
display: block;
float: left;
@@ -280,18 +288,26 @@ label {
background: #ddd;
clear: left;
text-align: left;
- padding: 10px;
- width: 15em;
+ float: left;
+ margin-right: 1em;
position: relative;
+ overflow: hidden;
+ cursor: pointer;
}
.add-image-button:hover {
background: #def;
}
+.main.resource form .add-image-button button {
+ margin: 0;
+ pointer-events: none;
+ width: 100%; height: 100%;
+}
.add-image-button input[type=file] {
opacity: 0;
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
+ margin: 0; padding: 0;
cursor: pointer;
}
li.image-element:hover .remove-image {
@@ -314,13 +330,20 @@ li.image-element .remove-image:hover {
}
-.errors {
+.success, .errors {
background: white;
- padding: 10px;
- width: 100%;
+ padding: 9px 8px 7px;
+ width: 50%;
line-height: 1.4em;
+ border: 1px solid;
+ margin: 1em;
+ border-radius: 2px;
+}
+
+.success {
+ color: green;
}
-.errors .message {
+.errors {
color: red;
}
diff --git a/themes/okadmin/public/js/app.js b/themes/okadmin/public/js/app.js
index 1ab9956..91a8e1a 100644
--- a/themes/okadmin/public/js/app.js
+++ b/themes/okadmin/public/js/app.js
@@ -28,7 +28,7 @@ var OKAdmin = function(){
$(".captioned-image-list ol").disableSelection()
// delete image
- $(document).on("click", ".remove-image", function(){
+ $(document).on("mousedown", ".remove-image", function(){
if (confirm("Delete this image?")) {
$(this).parent().remove()
}
@@ -50,7 +50,14 @@ var OKAdmin = function(){
}))
// fix post indexing in list-driven inputs
- $(".main.resource form").submit(function(){
+ $(".main.resource form").submit(function(e){
+ var $id = $("[name=id]")
+ if ($id.length && ! $id.val()) {
+ alert("Please enter an ID")
+ $id.focus()
+ e.preventDefault()
+ return
+ }
$(".image-element").each(function(index){
$(this).find("input,textarea").each(function(){
var field = $(this).attr("name").replace(/\[[0-9]*\]/, "[" + index + "]")
diff --git a/themes/okadmin/templates/404.liquid b/themes/okadmin/templates/404.liquid
new file mode 100644
index 0000000..87f5342
--- /dev/null
+++ b/themes/okadmin/templates/404.liquid
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>404</title>
+ <style type="text/css">
+ html, body {
+ margin: 0;
+ padding: 0;
+ font-family: "Helvetica", sans-serif;
+ background-image: url('http://okfoc.us/assets/images/photocopy.png');
+ background-position: bottom center;
+ background-repeat: repeat;
+ background-attachment: scroll;
+ height: 100%;
+ font-size: 1.75em;
+ font-weight: bold;
+ color: #FFFFFF;
+ }
+
+ a {
+ color: #8888FF;
+ text-decoration: none;
+ }
+
+ a:hover {
+ border-bottom: 3px solid #8888FF;
+ }
+
+ a:visited {
+ color: #8888FF;
+ }
+
+ .message {
+ width: 700px;
+ padding: 1em 1em 1em 1em;
+ background-color: #0000FF;
+ margin: 0 auto;
+ margin-top: 1em;
+ }
+
+ .message p:first-child {
+ margin-top: 0;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="message">
+ <p>¯\_(ツ)_/¯</p>
+ <p>We couldn't find that page.</p>
+ <p>Sure you have the right URL?</p>
+ <a href="javascript:history.back()">Back</a>
+ </div>
+ </body>
+</html>
diff --git a/themes/okadmin/templates/5xx.liquid b/themes/okadmin/templates/5xx.liquid
new file mode 100644
index 0000000..f245545
--- /dev/null
+++ b/themes/okadmin/templates/5xx.liquid
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>404</title>
+ <style type="text/css">
+ html, body {
+ margin: 0;
+ padding: 0;
+ font-family: "Helvetica", sans-serif;
+ background-image: url('http://okfoc.us/assets/images/photocopy.png');
+ background-position: bottom center;
+ background-repeat: repeat;
+ background-attachment: scroll;
+ height: 100%;
+ font-size: 1.75em;
+ font-weight: bold;
+ color: #FFFFFF;
+ }
+
+ a {
+ color: #8888FF;
+ text-decoration: none;
+ }
+
+ a:hover {
+ border-bottom: 3px solid #8888FF;
+ }
+
+ a:visited {
+ color: #8888FF;
+ }
+
+ .message {
+ width: 700px;
+ padding: 0 1em 1em 1em;
+ background-color: #0000FF;
+ margin: 0 auto;
+ margin-top: 1em;
+ }
+
+ .message p:first-child {
+ margin-top: 0;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="message">
+ <p>(;一_一)</p>
+ <p>Looks like we experienced an error.</p>
+ <p>Sorry about that. Maybe try again later.</p>
+ <a href="javascript:history.back()">Back</a>
+ </div>
+ </body>
+</html>
diff --git a/themes/okadmin/templates/index.liquid b/themes/okadmin/templates/index.liquid
index 0672613..330ed89 100644
--- a/themes/okadmin/templates/index.liquid
+++ b/themes/okadmin/templates/index.liquid
@@ -8,7 +8,7 @@
{% assign resource = pair[1] %}
<section class="resource-category {{name}}">
- <form action="{{resource.type}}/__batch/" method="POST">
+ <form action="{{resource.type}}/__batch__/" method="POST">
<header>
<h2>{{name | capitalize}}</h2>
</header>
@@ -26,8 +26,8 @@
<a class="btn cancel-btn" href="#">cancel</a>
<button type="submit"
class="btn save-btn" href="#">save</button>
- <a class="btn edit-btn active" href="#">edit</a>
- <a class="btn add-btn active" href="{{resource.type}}/new/">+</a>
+ <a class="btn edit-btn active" href="#">sort</a>
+ <a class="btn add-btn active" href="{{resource.type}}/__new__/">+</a>
</nav>
</footer>
</form>
diff --git a/themes/okadmin/templates/partials/flash.liquid b/themes/okadmin/templates/partials/flash.liquid
index 1980ab5..e51a86b 100644
--- a/themes/okadmin/templates/partials/flash.liquid
+++ b/themes/okadmin/templates/partials/flash.liquid
@@ -1,8 +1,15 @@
+{% if success.length > 0 %}
<div class="success">
+ <div class="message">Changes saved.</div>
+ <!--
{% for info in success %}
<div class="message">{{info.action}}</div>
{% endfor %}
+ -->
</div>
+{% endif %}
+
+{% if errors.length > 0 %}
<div class="errors">
{% for error in errors %}
<div class="error">
@@ -10,4 +17,4 @@
</div>
{% endfor %}
</div>
-
+{% endif %} \ No newline at end of file
diff --git a/themes/okadmin/templates/partials/inputs.liquid b/themes/okadmin/templates/partials/inputs.liquid
index 99258f3..b9cf7a3 100644
--- a/themes/okadmin/templates/partials/inputs.liquid
+++ b/themes/okadmin/templates/partials/inputs.liquid
@@ -29,7 +29,7 @@
{% endif %}
name="{{name}}">
{% for option in spec.options %}
- <option value="{{option}}" {% if option == spec.value %}selected{% endif %}>{{option}}</option>
+ <option value="{{option}}" {% if option == spec.value %}selected{% endif %}>{{option | capitalize}}</option>
{% endfor %}
</select>
{% elsif type == 'video' %}
@@ -56,7 +56,7 @@
</ol>
<div class="add-image-button">
<input id="file" type="file" accept="image/*" multiple>
- <span>+ Add images</span>
+ <button>+ Add images</button>
</div>
<input id="add-image-url" type="text" placeholder="+ Add URL">
<script type="text/html" id="captioned-image-template">
diff --git a/themes/okadmin/templates/partials/tail.liquid b/themes/okadmin/templates/partials/tail.liquid
index 88764a6..b3c575d 100644
--- a/themes/okadmin/templates/partials/tail.liquid
+++ b/themes/okadmin/templates/partials/tail.liquid
@@ -2,8 +2,8 @@
</body>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/3.6.0/lodash.min.js"></script>
- <script src="/_admin/js/jqueryui-draggable.js"></script>
- <script src="/_admin/js/upload.js"></script>
- <script src="/_admin/js/parser.js"></script>
- <script src="/_admin/js/app.js"></script>
+ <script src="/admin/js/jqueryui-draggable.js"></script>
+ <script src="/admin/js/upload.js"></script>
+ <script src="/admin/js/parser.js"></script>
+ <script src="/admin/js/app.js"></script>
</html>
diff --git a/themes/okadmin/templates/resource.liquid b/themes/okadmin/templates/resource.liquid
index 8078778..abc59e9 100644
--- a/themes/okadmin/templates/resource.liquid
+++ b/themes/okadmin/templates/resource.liquid
@@ -16,7 +16,7 @@
</form>
<form action="." method="POST" id="delete_form">
<input type="hidden" name="_method" value="DELETE">
- <button type="submit">Delete</button>
+ <button type="submit">Delete Record</button>
</form>
</section>