Last active
December 26, 2018 06:24
-
-
Save nikolowry/5083ecd1d0499ab5f44c to your computer and use it in GitHub Desktop.
Customize Keystone
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This GIST illustrates a hack to allow for configurable backend-url and admin styles in Keystone.js | |
New deps introduced: | |
- node-dir | |
- shelljs | |
New directory structure after running "yo keystone": | |
. | |
|____admin | |
| |____assets | |
| | |____styles.css | |
| | |____main.js | |
| |____routes | |
| | |____api.list.js | |
| | |____view.item.js | |
| | |____view.list.js | |
| |____render.js | |
| |____router.js | |
|____models | |
|____public | |
|____routes | |
|____templates | |
|____updates | |
|____.editorconfig | |
|____.env | |
|____.jshintrc | |
|____gulpfile.js | |
|____keystone.js | |
|____package.json | |
|____Procfile | |
NOTE: Gist does not allow "/" in filenames. The following files paths should be read as: | |
- admin.assets.main.js : /admin/assets/main.js | |
- admin.routes.api.list.js : /admin/routes/api.list.js | |
- admin.routes.view.item.js : /admin/routes/view.item.js | |
- admin.routes.view.list.js : /admin/routes/view.list.js | |
- admin.render.js : /admin/render.js | |
- admin.router.js : /admin/router.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
BACKEND_DIR=/admin | |
BACKEND_STATIC_PATH=/admin/assets | |
BACKEND_STATIC_MOUNT=/assets |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
if(window.jQuery){ | |
var custom_admin = (function(admin, $){ | |
$('a[href^="/keystone"], form[action^="/keystone"]').each(function(){ | |
var $el = $(this), | |
attr_type = $el.is('form') ? 'action' : 'href', | |
//catch all for the moment | |
new_url = $el.attr(attr_type).replace('/keystone', '/admin'); | |
$el.attr(attr_type, new_url); | |
}); | |
})(custom_admin || {}, jQuery); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var _ = require('underscore'), | |
fs = require('fs'), | |
jade = require('../node_modules/keystone/node_modules/jade'), | |
cloudinary = require('cloudinary'), | |
moment = require('moment'), | |
numeral = require('../node_modules/keystone/node_modules/numeral'), | |
utils = require('../node_modules/keystone/node_modules/keystone-utils'), | |
dir = require('node-dir'); | |
/** | |
* Renders a Keystone View | |
* | |
* @api private | |
*/ | |
var templateCache = {}; | |
function render(req, res, view, ext) { | |
var keystone = this; | |
var templatePath = 'node_modules/keystone/templates/views/' + view + '.jade'; | |
var jadeOptions = { | |
filename: templatePath, | |
pretty: keystone.get('env') !== 'production' | |
}; | |
// TODO: Allow custom basePath for extensions... like this or similar | |
// if (keystone.get('extensions')) { | |
// jadeOptions.basedir = keystone.getPath('extensions') + '/templates'; | |
// } | |
var compileTemplate = function() { | |
return jade.compile(fs.readFileSync(templatePath, 'utf8'), jadeOptions); | |
}; | |
var template = keystone.get('viewCache') | |
? templateCache[view] || (templateCache[view] = compileTemplate()) | |
: compileTemplate(); | |
var flashMessages = { | |
info: res.req.flash('info'), | |
success: res.req.flash('success'), | |
warning: res.req.flash('warning'), | |
error: res.req.flash('error'), | |
hilight: res.req.flash('hilight') | |
}; | |
var locals = { | |
_: _, | |
moment: moment, | |
numeral: numeral, | |
env: keystone.get('env'), | |
brand: keystone.get('brand'), | |
appversion : keystone.get('appversion'), | |
nav: keystone.nav, | |
messages: _.any(flashMessages, function(msgs) { return msgs.length; }) ? flashMessages : false, | |
lists: keystone.lists, | |
js: 'javascript:;', | |
utils: utils, | |
User: keystone.lists[keystone.get('user model')], | |
user: req.user, | |
title: 'Keystone', | |
signout: keystone.get('signout url'), | |
backUrl: keystone.get('back url') || '/', | |
section: {}, | |
version: keystone.version, | |
csrf_token_key: keystone.security.csrf.TOKEN_KEY, | |
csrf_token_value: keystone.security.csrf.getToken(req, res), | |
csrf_query: '&' + keystone.security.csrf.TOKEN_KEY + '=' + keystone.security.csrf.getToken(req, res), | |
ga: { | |
property: keystone.get('ga property'), | |
domain: keystone.get('ga domain') | |
}, | |
wysiwygOptions: { | |
enableImages: keystone.get('wysiwyg images') ? true : false, | |
enableCloudinaryUploads: keystone.get('wysiwyg cloudinary images') ? true : false, | |
additionalButtons: keystone.get('wysiwyg additional buttons') || '', | |
additionalPlugins: keystone.get('wysiwyg additional plugins') || '', | |
additionalOptions: keystone.get('wysiwyg additional options') || {}, | |
overrideToolbar: keystone.get('wysiwyg override toolbar'), | |
skin: keystone.get('wysiwyg skin') || 'keystone', | |
menubar: keystone.get('wysiwyg menubar') | |
} | |
}; | |
// optional extensions to the local scope | |
_.extend(locals, ext); | |
// add cloudinary locals if configured | |
if (keystone.get('cloudinary config')) { | |
try { | |
var cloudinaryUpload = cloudinary.uploader.direct_upload(); | |
locals.cloudinary = { | |
cloud_name: keystone.get('cloudinary config').cloud_name, | |
api_key: keystone.get('cloudinary config').api_key, | |
timestamp: cloudinaryUpload.hidden_fields.timestamp, | |
signature: cloudinaryUpload.hidden_fields.signature, | |
prefix: keystone.get('cloudinary prefix') || '', | |
folders: keystone.get('cloudinary folders'), | |
uploader: cloudinary.uploader | |
}; | |
locals.cloudinary_js_config = cloudinary.cloudinary_js_config(); | |
} catch(e) { | |
if (e === 'Must supply api_key') { | |
throw new Error('Invalid Cloudinary Config Provided\n\n' + | |
'See http://keystonejs.com/docs/configuration/#services-cloudinary for more information.'); | |
} else { | |
throw e; | |
} | |
} | |
} | |
// fieldLocals defines locals that are provided to each field's `render` method | |
locals.fieldLocals = _.pick(locals, '_', 'moment', 'numeral', 'env', 'js', 'utils', 'user', 'cloudinary'); | |
//build template | |
var html = template(_.extend(locals, ext)); | |
//Add custom admin tweaks | |
if(keystone.get('backend static mount') && keystone.get('backend static path')){ | |
//Env variables | |
var static_mount = keystone.get('backend static mount'), | |
static_path = keystone.get('backend static path'); | |
//Inject Admin Custom CSS | |
var head = html.split('</head>')[0], | |
body = html.split('</head>')[1].split('</body>'); | |
return dir.files(static_path, function(err, files) { | |
if (err) throw err; | |
files.forEach(function(file, i){ | |
var filename = file.split(static_path)[1], | |
isCSS = filename.match(/.css$/); | |
if(isCSS) { | |
head +='<link rel="stylesheet" href="'+static_mount+filename+'">'; | |
} else { | |
body +='<script src="'+static_mount+filename+'"></script>'; | |
} | |
if(i == files.length - 1){ | |
head += '</head>'; | |
body += '</body></html>'; | |
res.send(head+body); | |
} | |
}) | |
}) | |
} else { | |
res.send(html); | |
} | |
} | |
module.exports = render; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Adds bindings for the keystone routes | |
* | |
* ####Example: | |
* | |
* var app = express(); | |
* app.configure(...); // configuration settings | |
* app.use(...); // middleware, routes, etc. should come before keystone is initialised | |
* keystone.routes(app); | |
* | |
* @param {Express()} app | |
* @api public | |
*/ | |
function routes(app) { | |
this.app = app; | |
var keystone = this; | |
//Set backend url | |
var backend_dir = process.env.BACKEND_DIR ? process.env.BACKEND_DIR : '/keystone'; | |
this.set('backend dir', backend_dir); | |
//catch all for custom admin directoy url | |
if(backend_dir != '/keystone'){ | |
app.all('/keystone*', function(req, res, next){ | |
if(req.originalMethod == "GET") { | |
return res.redirect(backend_dir + req.params[0]); | |
} | |
if(req.originalMethod == "POST") { | |
//need to work this out | |
//just getting a POC from the existing codebase | |
//Will probably end up switching to headless and | |
//using resty-stone or keystone rest | |
//and will build out the admin UI custom. | |
} | |
}); | |
} | |
//Set backend static path for assets if config variable is declared | |
if(process.env.BACKEND_STATIC_MOUNT && process.env.BACKEND_STATIC_PATH) { | |
var express = this.express, | |
static_mount = process.env.BACKEND_STATIC_MOUNT, | |
static_path = require("shelljs").pwd() + process.env.BACKEND_STATIC_PATH; | |
this.set('backend static mount', static_mount); | |
this.set('backend static path', static_path); | |
app.use(static_mount, express.static(static_path)); | |
} | |
/// ensure keystone nav has been initialised | |
if (!this.nav) { | |
this.nav = this.initNav(); | |
} | |
// Cache compiled view templates if we are in Production mode | |
this.set('view cache', this.get('env') === 'production'); | |
// Bind auth middleware (generic or custom) to /backend_dir* routes, allowing | |
// access to the generic signin page if generic auth is used | |
if (this.get('auth') === true) { | |
if (!this.get('signout url')) { | |
this.set('signout url', backend_dir+'/signout'); | |
} | |
if (!this.get('signin url')) { | |
this.set('signin url', backend_dir+'/signin'); | |
} | |
if (!this.nativeApp || !this.get('session')) { | |
app.all(backend_dir+'*', this.session.persist); | |
} | |
app.all(backend_dir+'/signin', require('../node_modules/keystone/routes/views/signin')); | |
app.all(backend_dir+'/signout', require('../node_modules/keystone/routes/views/signout')); | |
app.all(backend_dir+'*', this.session.keystoneAuth); | |
} else if ('function' === typeof this.get('auth')) { | |
app.all(backend_dir+'*', this.get('auth')); | |
} | |
// Keystone Admin Route | |
app.all(backend_dir, require('../node_modules/keystone/routes/views/home.js')); | |
// Email test routes | |
if (this.get('email tests')) { | |
this.bindEmailTestRoutes(app, this.get('email tests')); | |
} | |
// Cloudinary API for image uploading (only if Cloudinary is configured) | |
if (keystone.get('wysiwyg cloudinary images')) { | |
if (!keystone.get('cloudinary config')) { | |
throw new Error('KeystoneJS Initialisaton Error:\n\nTo use wysiwyg cloudinary images, the \'cloudinary config\' setting must be configured.\n\n'); | |
} | |
app.post(backend_dir+'/api/cloudinary/upload', require('.../node_modules/keystone/routes/api/cloudinary').upload); | |
} | |
// Cloudinary API for selecting an existing image from the cloud | |
if (keystone.get('cloudinary config')) { | |
app.get(backend_dir+'/api/cloudinary/get', require('../node_modules/keystone/routes/api/cloudinary').get); | |
app.get(backend_dir+'/api/cloudinary/autocomplete', require('../node_modules/keystone/routes/api/cloudinary').autocomplete); | |
} | |
var initList = function(protect) { | |
return function(req, res, next) { | |
req.list = keystone.list(req.params.list); | |
if (!req.list || (protect && req.list.get('hidden'))) { | |
req.flash('error', 'List ' + req.params.list + ' could not be found.'); | |
return res.redirect(backend_dir+''); | |
} | |
next(); | |
}; | |
}; | |
// Generic Lists API | |
app.all(backend_dir+'/api/:list/:action', initList(), require('./routes/api.list')); | |
// Generic Lists Download Route | |
app.all(backend_dir+'/download/:list', initList(), require('../node_modules/keystone/routes/download/list')); | |
// List and Item Details Admin Routes | |
app.all(backend_dir+'/:list/:page([0-9]{1,5})?', initList(true), require('./routes/view.list')); | |
app.all(backend_dir+'/:list/:item', initList(true), require('./routes/view.item')); | |
return this; | |
} | |
module.exports = routes; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var _ = require('underscore'), | |
async = require('async'), | |
keystone = require('keystone'), | |
jade = require('jade'); | |
exports = module.exports = function(req, res) { | |
var sendResponse = function(status) { | |
res.json(status); | |
}; | |
var sendError = function(key, err, msg) { | |
msg = msg || 'API Error'; | |
key = key || 'unknown error'; | |
msg += ' (' + key + ')'; | |
console.log(msg + (err ? ':' : '')); | |
if (err) { | |
console.log(err); | |
} | |
res.status(500); | |
sendResponse({ error: key || 'error', detail: err ? err.message : '' }); | |
}; | |
switch (req.params.action) { | |
case 'autocomplete': | |
var limit = req.query.limit || 10, | |
page = req.query.page || 1, | |
skip = limit * (page - 1); | |
var filters = req.list.getSearchFilters(req.query.q); | |
var count = req.list.model.count(filters), | |
query = req.list.model.find(filters) | |
.limit(limit) | |
.skip(skip) | |
.sort(req.list.defaultSort); | |
var doQuery = function() { | |
count.exec(function(err, total) { | |
if (err) return sendError('database error', err); | |
query.exec(function(err, items) { | |
if (err) return sendError('database error', err); | |
sendResponse({ | |
total: total, | |
items: items.map(function(i) { | |
return { | |
name: req.list.getDocumentName(i, true) || '(' + i.id + ')', | |
id: i.id | |
}; | |
}) | |
}); | |
}); | |
}); | |
}; | |
if (req.query.context === 'relationship') { | |
var srcList = keystone.list(req.query.list); | |
if (!srcList) return sendError('invalid list provided'); | |
var field = srcList.fields[req.query.field]; | |
if (!field || field.type !== 'relationship') return sendError('invalid field provided'); | |
if (!field.hasFilters) { | |
return doQuery(); | |
} | |
_.each(req.query.filters, function(value, key) { | |
query.where(key).equals(value ? value : null); | |
count.where(key).equals(value ? value : null); | |
}); | |
return doQuery(); | |
} else { | |
return doQuery(); | |
} | |
break; | |
case 'get': | |
req.list.model.findById(req.query.id).exec(function(err, item) { | |
if (err) return sendError('database error', err); | |
if (!item) return sendResponse({ name: req.query.id, id: req.query.id }); | |
switch (req.query.dataset) { | |
case 'simple': | |
return sendResponse({ | |
name: req.list.getDocumentName(item, true), | |
id: item.id | |
}); | |
default: | |
return sendResponse(item); | |
} | |
}); | |
break; | |
case 'order': | |
if (!keystone.security.csrf.validate(req)) { | |
return sendError('invalid csrf'); | |
} | |
var order = req.query.order || req.body.order, | |
queue = []; | |
if ('string' === typeof order) { | |
order = order.split(','); | |
} | |
_.each(order, function(id, i) { | |
queue.push(function(done) { | |
req.list.model.update({ _id: id }, { $set: { sortOrder: i }}, done); | |
}); | |
}); | |
async.parallel(queue, function(err) { | |
if (err) return sendError('database error', err); | |
return sendResponse({ | |
success: true | |
}); | |
}); | |
break; | |
case 'create': | |
console.log('creaate') | |
if (!keystone.security.csrf.validate(req)) { | |
return sendError('invalid csrf'); | |
} | |
var item = new req.list.model(), | |
updateHandler = item.getUpdateHandler(req), | |
data = (req.method === 'POST') ? req.body : req.query; | |
if (req.list.nameIsInitial) { | |
if (req.list.nameField.validateInput(data)) { | |
req.list.nameField.updateItem(item, data); | |
} else { | |
updateHandler.addValidationError(req.list.nameField.path, 'Name is required.'); | |
} | |
} | |
updateHandler.process(data, { | |
flashErrors: true, | |
logErrors: true, | |
fields: req.list.initialFields | |
}, function(err) { | |
if (err) { | |
return sendResponse({ | |
success: false, | |
err: err | |
}); | |
} else { | |
return sendResponse({ | |
success: true, | |
name: req.list.getDocumentName(item, true), | |
id: item.id | |
}); | |
} | |
}); | |
break; | |
case 'delete': | |
if (!keystone.security.csrf.validate(req)) { | |
return sendError('invalid csrf'); | |
} | |
if (req.list.get('nodelete')) { | |
return sendError('nodelete'); | |
} | |
var id = req.body.id || req.query.id; | |
if (id === req.user.id) { | |
return sendError('You can not delete yourself'); | |
} | |
req.list.model.findById(id).exec(function (err, item) { | |
if (err) return sendError('database error', err); | |
if (!item) return sendError('not found'); | |
item.remove(function (err) { | |
if (err) return sendError('database error', err); | |
return sendResponse({ | |
success: true, | |
count: 1 | |
}); | |
}); | |
}); | |
break; | |
case 'fetch': | |
if (!keystone.security.csrf.validate(req)) { | |
return sendError('invalid csrf'); | |
} | |
(function() { | |
var queryFilters = req.list.getSearchFilters(req.query.search, req.query.filters), | |
skip = parseInt(req.query.items.last) - 1, | |
querystring = require('../../node_modules/keystone/node_modules/querystring'), | |
link_to = function(params) { | |
var p = params.page || ''; | |
delete params.page; | |
var queryParams = _.clone(req.query.q); | |
for (var i in params) { | |
if (params[i] === undefined) { | |
delete params[i]; | |
delete queryParams[i]; | |
} | |
} | |
params = querystring.stringify(_.defaults(params, queryParams)); | |
return keystone.get('backend dir') + '/' + req.list.path + (p ? '/' + p : '') + (params ? '?' + params : ''); | |
}; | |
var query = req.list.model.find(queryFilters).sort(req.query.sort).skip(skip).limit(1), | |
columns = req.list.expandColumns(req.query.cols); | |
req.list.selectColumns(query, columns); | |
query.exec(function(err, items) { | |
if (err) return sendError('database error', err); | |
if (!items) return sendError('not found'); | |
var locals, row, pagination; | |
req.list.getPages(req.query.items, req.list.pagination.maxPages); | |
locals = { list: req.list, columns: columns, item: items[0], csrf_query: req.query.csrf_query, _:_ }; | |
row = jade.renderFile(__dirname + '/../../templates/partials/row.jade', locals); | |
pagination = jade.renderFile(__dirname + '/../../templates/partials/pagination.jade', {items: req.query.items, link_to: link_to }); | |
return sendResponse({ | |
item: items[0], | |
row: row, | |
pagination: pagination, | |
success: true, | |
count: 1 | |
}); | |
}); | |
})(); | |
break; | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var keystone = require('keystone'), | |
_ = require('underscore'), | |
async = require('async'); | |
exports = module.exports = function(req, res) { | |
console.log('item request') | |
var itemQuery = req.list.model.findById(req.params.item); | |
if (req.list.tracking && req.list.tracking.createdBy) { | |
console.log('first conditional') | |
itemQuery.populate(req.list.tracking.createdBy); | |
} | |
if (req.list.tracking && req.list.tracking.updatedBy) { | |
console.log('second conditional') | |
itemQuery.populate(req.list.tracking.updatedBy); | |
} | |
itemQuery.exec(function(err, item) { | |
if (!item) { | |
req.flash('error', 'Item ' + req.params.item + ' could not be found.'); | |
return res.redirect(keystone.get('backend dir') + '/' + req.list.path); | |
} | |
var viewLocals = { | |
validationErrors: {} | |
}; | |
var renderView = function() { | |
var relationships = _.values(_.compact(_.map(req.list.relationships, function(i) { | |
if (i.isValid) { | |
return _.clone(i); | |
} else { | |
keystone.console.err('Relationship Configuration Error', 'Relationship: ' + i.path + ' on list: ' + req.list.key + ' links to an invalid list: ' + i.ref); | |
return null; | |
} | |
}))); | |
var drilldown = { | |
def: req.list.get('drilldown'), | |
data: {}, | |
items: [] | |
}; | |
var loadDrilldown = function(cb) { | |
if (!drilldown.def) | |
return cb(); | |
// step back through the drilldown list and load in reverse order to support nested relationships | |
// TODO: proper support for nested relationships in drilldown | |
drilldown.def = drilldown.def.split(' ').reverse(); | |
async.eachSeries(drilldown.def, function(path, done) { | |
var field = req.list.fields[path]; | |
if (!field || field.type !== 'relationship') | |
throw new Error('Drilldown for ' + req.list.key + ' is invalid: field at path ' + path + ' is not a relationship.'); | |
var refList = field.refList; | |
if (field.many) { | |
if (!item.get(field.path).length) { | |
return done(); | |
} | |
refList.model.find().where('_id').in(item.get(field.path)).limit(4).exec(function(err, results) { | |
if (err || !results) { | |
done(err); | |
} | |
var more = (results.length === 4) ? results.pop() : false; | |
if (results.length) { | |
drilldown.data[path] = results; | |
drilldown.items.push({ | |
list: refList, | |
items: _.map(results, function(i) { return { | |
label: refList.getDocumentName(i), | |
href: keystone.get('backend dir') + '/' + refList.path + '/' + i.id | |
};}), | |
more: (more) ? true : false | |
}); | |
} | |
done(); | |
}); | |
} else { | |
if (!item.get(field.path)) { | |
return done(); | |
} | |
refList.model.findById(item.get(field.path)).exec(function(err, result) { | |
if (result) { | |
drilldown.data[path] = result; | |
drilldown.items.push({ | |
list: refList, | |
label: refList.getDocumentName(result), | |
href: keystone.get('backend dir') + '/' + refList.path + '/' + result.id | |
}); | |
} | |
done(); | |
}); | |
} | |
}, function(err) { | |
// put the drilldown list back in the right order | |
drilldown.def.reverse(); | |
drilldown.items.reverse(); | |
cb(err); | |
}); | |
}; | |
var loadRelationships = function(cb) { | |
async.each(relationships, function(rel, done) { | |
// TODO: Handle invalid relationship config | |
rel.list = keystone.list(rel.ref); | |
rel.sortable = (rel.list.get('sortable') && rel.list.get('sortContext') === req.list.key + ':' + rel.path); | |
// TODO: Handle relationships with more than 1 page of results | |
var q = rel.list.paginate({ page: 1, perPage: 100 }) | |
.where(rel.refPath).equals(item.id) | |
.sort(rel.list.defaultSort); | |
// rel.columns = _.reject(rel.list.defaultColumns, function(col) { return (col.type == 'relationship' && col.refList == req.list) }); | |
rel.columns = rel.list.defaultColumns; | |
rel.list.selectColumns(q, rel.columns); | |
q.exec(function(err, results) { | |
rel.items = results; | |
done(err); | |
}); | |
}, cb); | |
}; | |
var loadFormFieldTemplates = function(cb){ | |
var onlyFields = function(item) { return item.type === 'field'; }; | |
var compile = function(item, callback) { item.field.compile('form',callback); }; | |
async.eachSeries(req.list.uiElements.filter(onlyFields), compile , cb); | |
}; | |
/** Render View */ | |
async.parallel([ | |
loadDrilldown, | |
loadRelationships, | |
loadFormFieldTemplates | |
], function(err) { | |
// TODO: Handle err | |
var showRelationships = _.some(relationships, function(rel) { | |
return rel.items.results.length; | |
}); | |
keystone.render(req, res, 'item', _.extend(viewLocals, { | |
section: keystone.nav.by.list[req.list.key] || {}, | |
title: 'Keystone: ' + req.list.singular + ': ' + req.list.getDocumentName(item), | |
page: 'item', | |
list: req.list, | |
item: item, | |
relationships: relationships, | |
showRelationships: showRelationships, | |
drilldown: drilldown | |
})); | |
}); | |
}; | |
if (req.method === 'POST' && req.body.action === 'updateItem' && !req.list.get('noedit')) { | |
if (!keystone.security.csrf.validate(req)) { | |
req.flash('error', 'There was a problem with your request, please try again.'); | |
return renderView(); | |
} | |
item.getUpdateHandler(req).process(req.body, { flashErrors: true, logErrors: true }, function(err) { | |
if (err) { | |
return renderView(); | |
} | |
req.flash('success', 'Your changes have been saved.'); | |
return res.redirect(keystone.get('backend dir') + '/' + req.list.path + '/' + item.id); | |
}); | |
} else { | |
renderView(); | |
} | |
}); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var keystone = require('keystone'), | |
_ = require('underscore'), | |
querystring = require('querystring'), | |
async = require('async'); | |
exports = module.exports = function(req, res) { | |
var viewLocals = { | |
validationErrors: {}, | |
showCreateForm: _.has(req.query, 'new') | |
}; | |
var sort = { by: req.query.sort || req.list.defaultSort }, | |
filters = req.list.processFilters(req.query.q), | |
cleanFilters = {}, | |
queryFilters = req.list.getSearchFilters(req.query.search, filters), | |
columns = (req.query.cols) ? req.list.expandColumns(req.query.cols) : req.list.defaultColumns; | |
_.each(filters, function(filter, path) { | |
cleanFilters[path] = _.omit(filter, 'field'); | |
}); | |
if (sort.by) { | |
sort.inv = sort.by.charAt(0) === '-'; | |
sort.path = (sort.inv) ? sort.by.substr(1) : sort.by; | |
sort.field = req.list.fields[sort.path]; | |
var clearSort = function() { | |
delete req.query.sort; | |
var qs = querystring.stringify(req.query); | |
return res.redirect(req.path + ((qs) ? '?' + qs : '')); | |
}; | |
// clear the sort query value if it is the default sort value for the list | |
if (req.query.sort === req.list.defaultSort) { | |
return clearSort(); | |
} | |
if (sort.field) { | |
// the sort is set to a field, use its label | |
sort.label = sort.field.label; | |
// some fields have custom sort paths | |
if (sort.field.type === 'name') { | |
sort.by = sort.by + '.first ' + sort.by + '.last'; | |
} | |
} else if (req.list.get('sortable') && (sort.by === 'sortOrder' || sort.by === '-sortOrder')) { | |
// the sort is set to the built-in sort order, set the label correctly | |
sort.label = 'display order'; | |
} else if (req.query.sort) { | |
// it looks like an invalid path has been specified (no matching field), so clear the sort | |
return clearSort(); | |
} | |
} | |
var renderView = function() { | |
var query = req.list.paginate({ filters: queryFilters, page: req.params.page, perPage: req.list.get('perPage') }).sort(sort.by); | |
req.list.selectColumns(query, columns); | |
var link_to = function(params) { | |
var p = params.page || ''; | |
delete params.page; | |
var queryParams = _.clone(req.query); | |
for (var i in params) { | |
if (params[i] === undefined) { | |
delete params[i]; | |
delete queryParams[i]; | |
} | |
} | |
params = querystring.stringify(_.defaults(params, queryParams)); | |
return keystone.get('backend dir') + '/' + req.list.path + (p ? '/' + p : '') + (params ? '?' + params : ''); | |
}; | |
query.exec(function(err, items) { | |
if (err) { | |
console.log(err); | |
return res.status(500).send('Error querying items:<br><br>' + JSON.stringify(err)); | |
} | |
// if there were results but not on this page, reset the page | |
if (req.params.page && items.total && !items.results.length) { | |
return res.redirect(keystone.get('backend dir') + '/' + req.list.path); | |
} | |
// go straight to the result if there was a search, and only one result | |
if (req.query.search && items.total === 1 && items.results.length === 1) { | |
return res.redirect(keystone.get('backend dir') + '/' + req.list.path + '/' + items.results[0].id); | |
} | |
var download_link = keystone.get('backend dir') + '/' + 'download/' + req.list.path, | |
downloadParams = {}; | |
if (req.query.q) { | |
downloadParams.q = req.query.q; | |
} | |
if (req.query.search) { | |
downloadParams.search = req.query.search; | |
} | |
if (req.query.cols) { | |
downloadParams.cols = req.query.cols; | |
} | |
downloadParams = querystring.stringify(downloadParams); | |
if (downloadParams) { | |
download_link += '?' + downloadParams; | |
} | |
var compileFields = function(item, callback) { item.compile('initial', callback); }; | |
async.eachSeries(req.list.initialFields, compileFields , function() { | |
keystone.render(req, res, 'list', _.extend(viewLocals, { | |
section: keystone.nav.by.list[req.list.key] || {}, | |
title: 'Keystone: ' + req.list.plural, | |
page: 'list', | |
link_to: link_to, | |
download_link: download_link, | |
list: req.list, | |
sort: sort, | |
filters: cleanFilters, | |
search: req.query.search, | |
columns: columns, | |
colPaths: _.pluck(columns, 'path'), | |
items: items, | |
submitted: req.body || {}, | |
query: req.query | |
})); | |
}); | |
}); | |
}; | |
var checkCSRF = function() { | |
var pass = keystone.security.csrf.validate(req); | |
if (!pass) { | |
req.flash('error', 'There was a problem with your request, please try again.'); | |
} | |
return pass; | |
}; | |
var item; | |
if ('update' in req.query) { | |
if (!checkCSRF()) return renderView(); | |
(function() { | |
var data = null; | |
if (req.query.update) { | |
try { | |
data = JSON.parse(req.query.update); | |
} catch(e) { | |
req.flash('error', 'There was an error parsing the update data.'); | |
return renderView(); | |
} | |
} | |
req.list.updateAll(data, function(err) { | |
if (err) { | |
console.log('Error updating all ' + req.list.plural); | |
console.log(err); | |
req.flash('error', 'There was an error updating all ' + req.list.plural + ' (logged to console)'); | |
} else { | |
req.flash('success', 'All ' + req.list.plural + ' updated successfully.'); | |
} | |
res.redirect(keystone.get('backend dir') + '/' + req.list.path); | |
}); | |
})(); | |
} else if (!req.list.get('nodelete') && req.query['delete']) { | |
if (!checkCSRF()) return renderView(); | |
if (req.query['delete'] === req.user.id) { | |
req.flash('error', 'You can\'t delete your own ' + req.list.singular + '.'); | |
return renderView(); | |
} | |
req.list.model.findById(req.query['delete']).exec(function (err, item) { | |
if (err || !item) return res.redirect(keystone.get('backend dir') + '/' + req.list.path); | |
item.remove(function (err) { | |
if (err) { | |
console.log('Error deleting ' + req.list.singular); | |
console.log(err); | |
req.flash('error', 'Error deleting the ' + req.list.singular + ': ' + err.message); | |
} else { | |
req.flash('success', req.list.singular + ' deleted successfully.'); | |
} | |
res.redirect(keystone.get('backend dir') + '/' + req.list.path); | |
}); | |
}); | |
return; | |
} else if (!req.list.get('nocreate') && req.list.get('autocreate') && _.has(req.query, 'new')) { | |
if (!checkCSRF()) return renderView(); | |
item = new req.list.model(); | |
item.save(function(err) { | |
if (err) { | |
console.log('There was an error creating the new ' + req.list.singular + ':'); | |
console.log(err); | |
req.flash('error', 'There was an error creating the new ' + req.list.singular + '.'); | |
renderView(); | |
} else { | |
req.flash('success', 'New ' + req.list.singular + ' ' + req.list.getDocumentName(item) + ' created.'); | |
return res.redirect(keystone.get('backend dir') + '/' + req.list.path + '/' + item.id); | |
} | |
}); | |
} else if (!req.list.get('nocreate') && req.method === 'POST' && req.body.action === 'create') { | |
if (!checkCSRF()) return renderView(); | |
item = new req.list.model(); | |
var updateHandler = item.getUpdateHandler(req); | |
viewLocals.showCreateForm = true; // always show the create form after a create. success will redirect. | |
if (req.list.nameIsInitial) { | |
if (!req.list.nameField.validateInput(req.body, true, item)) { | |
updateHandler.addValidationError(req.list.nameField.path, req.list.nameField.label + ' is required.'); | |
} | |
req.list.nameField.updateItem(item, req.body); | |
} | |
updateHandler.process(req.body, { | |
flashErrors: true, | |
logErrors: true, | |
fields: req.list.initialFields | |
}, function(err) { | |
if (err) { | |
return renderView(); | |
} | |
req.flash('success', 'New ' + req.list.singular + ' ' + req.list.getDocumentName(item) + ' created.'); | |
return res.redirect(keystone.get('backend dir') + '/' + req.list.path + '/' + item.id); | |
}); | |
} else { | |
renderView(); | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Simulate config options from your production environment by | |
// customising the .env file in your project's root folder. | |
require('dotenv').load(); | |
// Require keystone | |
var keystone = require('keystone'), | |
handlebars = require('express-handlebars'); | |
// Overrides to non-configurable keystone options | |
keystone.routes = require('./admin/router'); | |
keystone.render = require('./admin/render'); | |
// Initialise Keystone with your project's configuration. | |
// See http://keystonejs.com/guide/config for available options | |
// and documentation. | |
keystone.init({ | |
'name': 'SapientCMS', | |
'brand': 'SapientCMS', | |
'sass': 'public', | |
'static': 'public', | |
'favicon': 'public/favicon.ico', | |
'views': 'templates/views', | |
'view engine': 'hbs', | |
'custom engine': handlebars.create({ | |
layoutsDir: 'templates/views/layouts', | |
partialsDir: 'templates/views/partials', | |
defaultLayout: 'default', | |
helpers: new require('./templates/views/helpers')(), | |
extname: '.hbs' | |
}).engine, | |
'auto update': true, | |
'session': true, | |
'auth': true, | |
'user model': 'User', | |
'cookie secret': '*frh":IcIak-*CTLKEU7MB[WN"a?/Q",Zrs@;y6PK6W2b?gl8,M_>vbEJ`XCH?Ro', | |
'signin redirect': process.env.BACKEND_DIR | |
}); | |
// Load your project's Models | |
keystone.import('models'); | |
// Setup common locals for your templates. The following are required for the | |
// bundled templates and layouts. Any runtime locals (that should be set uniquely | |
// for each request) should be added to ./routes/middleware.js | |
keystone.set('locals', { | |
_: require('underscore'), | |
env: keystone.get('env'), | |
utils: keystone.utils, | |
editable: keystone.content.editable | |
}); | |
keystone.set('routes', require('./routes')); | |
// Configure the navigation bar in Keystone's Admin UI | |
keystone.set('nav', { | |
'posts': ['posts', 'post-categories'], | |
'galleries': 'galleries', | |
'enquiries': 'enquiries', | |
'users': 'users' | |
}); | |
// Start Keystone to connect to your database and initialise the web server | |
keystone.start(); |
incase anyone wants that code
files.forEach(function(file, i){
static_path = static_path.replace(new RegExp('/', 'g'), "\\");
var filename = file.split(static_path)[1],
isCSS = filename.match(/.css$/);
Hey, I'm getting below error when i'm trying to override keystonejs with above code mentioned
Cannot read property 'split' of undefined...
can you resolve this issue?
Hey, I'm getting below error when i'm trying to override keystonejs with above code mentioned in view.list.js
TypeError: item.compile is not a function
i.e.., in view.list.js
can you resolve this issue?
Hi
After "yo keystone", i can't see admin folder in my structure.
Please help me.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey Niko,
I had an issue in render.js -- getting a "Cannot call method 'match' of undefined" when trying to test during the file loop in the "Assets" is a css document.
I am on a PC and I think that the static_path is being reported differently for me.
For example during the loop
so the split is coming back undefined. I can just do a replace on "/" to "" but I wanted you to know this was happening.