diff options
| author | Jules Laplace <jules@okfoc.us> | 2015-04-13 16:46:04 -0400 |
|---|---|---|
| committer | Jules Laplace <jules@okfoc.us> | 2015-04-13 16:46:04 -0400 |
| commit | 3e255208a9776ad377f3b62d79d982138e89539a (patch) | |
| tree | 45ed64ad10ea19eabf1a57b8a8b4d42ee6111e13 | |
| parent | e791ea8b3d6855b62962e4c89ad2c9f86126daf1 (diff) | |
| parent | c702d76549ead663daed0147f5c160c368a61c45 (diff) | |
Merge branch 'twohustlers' of github.com:okfocus/okcms into twohustlers
| -rw-r--r-- | app/index.js | 64 | ||||
| -rw-r--r-- | app/node_modules/okadminview/index.js | 129 | ||||
| -rw-r--r-- | app/node_modules/okdb/index.js | 2 | ||||
| -rw-r--r-- | app/node_modules/okserver/index.js | 7 | ||||
| -rw-r--r-- | app/node_modules/okview/index.js | 133 | ||||
| -rw-r--r-- | examples/index.js | 2 | ||||
| -rw-r--r-- | site/db.json | 6 | ||||
| -rw-r--r-- | themes/okadmin/templates/404.liquid | 54 | ||||
| -rw-r--r-- | themes/okadmin/templates/5xx.liquid | 54 | ||||
| -rw-r--r-- | themes/okadmin/templates/index.liquid | 4 |
10 files changed, 299 insertions, 156 deletions
diff --git a/app/index.js b/app/index.js index 719c424..419adfc 100644 --- a/app/index.js +++ b/app/index.js @@ -36,6 +36,7 @@ function OKCMS(options) { 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 2f31e1e..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.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/db.json b/site/db.json index d2e882a..b6fbf24 100644 --- a/site/db.json +++ b/site/db.json @@ -3,7 +3,7 @@ "project": [ { "id": "diesel-ss15", - "category": "retail", + "category": "advertising", "title": "DIESEL JOGG JEANS SS15 DENIM CAMPAIGN", "shortname": "DIESEL SS15", "description": "Sed posuere consectetur est at lobortis. Donec id elit non mi porta gravida at eget metus. Cras mattis consectetur purus sit amet fermentum. Vestibulum id ligula porta felis euismod semper. Donec sed odio dui. \r\n\r\nVestibulum id ligula porta felis euismod semper. Vestibulum id ligula porta felis euismod semper. Vestibulum id ligula porta felis euismod semper. Aenean lacinia bibendum nulla sed consectetur. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. \r\n\r\nCurabitur blandit tempus porttitor. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec ullamcorper nulla non metus auctor fringilla. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur blandit tempus porttitor. Maecenas sed diam eget risus varius blandit sit amet non magna.", @@ -26,6 +26,10 @@ { "uri": "http://twohustlers.com/bigimages/ss15_3.jpg", "caption": "CURABITUR BLANDIT TEMPUS PORTTITOR 4" + }, + { + "uri": "https://ltho.s3.amazonaws.com/65e2ee7c-4c44-435a-875e-ef635a7e0106.jpg", + "caption": "weeeezle" } ], "__index": "0" 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 10903b9..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> @@ -27,7 +27,7 @@ <button type="submit" class="btn save-btn" href="#">save</button> <a class="btn edit-btn active" href="#">sort</a> - <a class="btn add-btn active" href="{{resource.type}}/new/">+</a> + <a class="btn add-btn active" href="{{resource.type}}/__new__/">+</a> </nav> </footer> </form> |
