apostrophe-snippets
Advanced tools
Comparing version 0.0.31 to 0.0.32
279
index.js
@@ -139,2 +139,6 @@ var async = require('async'); | ||
extend(true, self._rendererGlobals, { | ||
type: _.pick(self, [ 'name', 'label', 'icon', '_instance', '_css', '_typeCss', '_menuName', '_action' ]) | ||
}); | ||
// Render a partial, looking for overrides in our preferred places | ||
@@ -204,2 +208,14 @@ self.render = function(name, data) { | ||
self.authorAsEditor = function(req, snippet) { | ||
if (req.user && (!req.user.permissions.admin)) { | ||
// Always add the creator as a permitted editor | ||
// so they retain the ability to manage their work, | ||
// regardless of other permissions that may exist | ||
if (!snippet.editPersonIds) { | ||
snippet.editPersonIds = []; | ||
} | ||
snippet.editPersonIds.push(req.user._id); | ||
} | ||
}; | ||
self.beforeInsert = function(req, data, snippet, callback) { | ||
@@ -319,3 +335,2 @@ return callback(null); | ||
snippet.slug = self._apos.slugify(snippet.title); | ||
snippet.sortTitle = self._apos.sortify(snippet.title); | ||
// Record when the import happened so that later we can offer a UI | ||
@@ -326,2 +341,3 @@ // to find these groups and remove them if desired | ||
function(callback) { | ||
self.authorAsEditor(req, snippet); | ||
self.beforeInsert(req, data, snippet, callback); | ||
@@ -370,3 +386,3 @@ }, | ||
tags = req.body.tags; | ||
tags = self._apos.sanitizeTags(req.body.tags); | ||
@@ -378,7 +394,10 @@ snippet = { title: title, published: published, type: self._instance, tags: tags, areas: {}, slug: slug, createdAt: new Date(), publishedAt: new Date() }; | ||
tags = req.body.tags; | ||
async.series([ permissions, beforeInsert, beforeSave, insert, afterInsert, afterSave ], send); | ||
async.series([ beforeInsert, beforeSave, insert, afterInsert, afterSave ], send); | ||
function permissions(callback) { | ||
self._apos.permissions(req, 'edit-' + self._css, null, callback); | ||
} | ||
function beforeInsert(callback) { | ||
self.authorAsEditor(req, snippet); | ||
return self.beforeInsert(req, req.body, snippet, callback); | ||
@@ -405,2 +424,3 @@ } | ||
if (err) { | ||
console.log(err); | ||
res.statusCode = 500; | ||
@@ -424,3 +444,3 @@ return res.send('error'); | ||
tags = req.body.tags; | ||
tags = self._apos.sanitizeTags(req.body.tags); | ||
@@ -498,3 +518,3 @@ originalSlug = self._apos.sanitizeString(req.body.originalSlug); | ||
self._app.post(self._action + '/trash', function(req, res) { | ||
async.series([ get, permissions, beforeTrash, trashSnippet], respond); | ||
async.series([ get, beforeTrash, trashSnippet], respond); | ||
@@ -507,10 +527,10 @@ var slug; | ||
slug = req.body.slug; | ||
return self._apos.pages.findOne({ slug: slug }, function(err, snippetArg) { | ||
snippet = snippetArg; | ||
return self.get(req, { slug: slug }, { editable: true }, function(err, results) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
snippet = results.snippets[0]; | ||
if(!snippet) { | ||
return callback('Not Found'); | ||
} | ||
if (snippet.type !== self._instance) { | ||
return callback('Not a ' + self._instance); | ||
} | ||
return callback(err); | ||
@@ -520,10 +540,2 @@ }); | ||
function permissions(callback) { | ||
return self._apos.permissions(req, 'edit-page', snippet, function(err) { | ||
// If there is no permissions error then we are cool | ||
// enough to trash the post | ||
return callback(err); | ||
}); | ||
} | ||
function beforeTrash(callback) { | ||
@@ -543,3 +555,3 @@ if (self.beforeTrash) { | ||
} | ||
self._apos.pages.update({slug: snippet.slug}, action, callback); | ||
self._apos.pages.update({ slug: snippet.slug }, action, callback); | ||
} | ||
@@ -632,5 +644,6 @@ | ||
self._app.get(self._action + '/get', function(req, res) { | ||
var criteria = {}; | ||
var options = {}; | ||
self.addApiCriteria(req.query, options); | ||
self.get(req, options, function(err, results) { | ||
self.addApiCriteria(req.query, criteria, options); | ||
self.get(req, criteria, options, function(err, results) { | ||
if (err) { | ||
@@ -645,5 +658,6 @@ res.statusCode = 500; | ||
self._app.get(self._action + '/get-one', function(req, res) { | ||
var criteria = {}; | ||
var options = {}; | ||
self.addApiCriteria(req.query, options); | ||
self.get(req, options, function(err, results) { | ||
self.addApiCriteria(req.query, criteria, options); | ||
self.get(req, criteria, options, function(err, results) { | ||
if (results && results.snippets.length) { | ||
@@ -659,12 +673,30 @@ res.send(JSON.stringify(results.snippets[0])); | ||
// get-one API calls used when managing content | ||
self.addApiCriteria = function(query, criteria) { | ||
extend(true, criteria, query); | ||
self.addApiCriteria = function(queryArg, criteria, options) { | ||
// Most of the "criteria" that come in via an API call belong in options | ||
// (skip, limit, titleSearch, published, etc). Handle any cases that should | ||
// go straight to the mongo criteria object | ||
var query = {}; | ||
extend(true, query, queryArg); | ||
var slug = self._apos.sanitizeString(query.slug); | ||
if (slug.length) { | ||
criteria.slug = query.slug; | ||
// Don't let it become an option too | ||
delete query.slug; | ||
} | ||
// Everything else is assumed to be an option | ||
extend(true, options, query); | ||
// Make sure these are converted to numbers, but only if they are present at all | ||
if (criteria.skip !== undefined) { | ||
criteria.skip = self._apos.sanitizeInteger(criteria.skip); | ||
if (options.skip !== undefined) { | ||
options.skip = self._apos.sanitizeInteger(options.skip); | ||
} | ||
if (criteria.limit !== undefined) { | ||
criteria.limit = self._apos.sanitizeInteger(criteria.limit); | ||
if (options.limit !== undefined) { | ||
options.limit = self._apos.sanitizeInteger(options.limit); | ||
} | ||
criteria.editable = true; | ||
options.editable = true; | ||
}; | ||
@@ -674,3 +706,3 @@ | ||
// publishedAt = 'any' | ||
self.addExtraAutocompleteCriteria = function(req, criteria) { | ||
self.addExtraAutocompleteCriteria = function(req, criteria, options) { | ||
}; | ||
@@ -683,9 +715,13 @@ | ||
// | ||
// Send either a term parameter, used for autocomplete search, | ||
// or an ids array parameter, used to fetch title information | ||
// Send either a `term` parameter, used for autocomplete search, | ||
// or a `values` array parameter, used to fetch title information | ||
// about an existing list of ids. If neither is present the | ||
// request is assumed to be for an empty array of ids and an | ||
// empty array is returned. | ||
// empty array is returned, not a 404. | ||
// | ||
// GET and POST are supported to allow for large `values` | ||
// arrays. | ||
self._app.get(self._action + '/autocomplete', function(req, res) { | ||
self._app.all(self._action + '/autocomplete', function(req, res) { | ||
var criteria = {}; | ||
var options = { | ||
@@ -695,6 +731,7 @@ fields: self.getAutocompleteFields(), | ||
}; | ||
if (req.query.term !== undefined) { | ||
options.titleSearch = req.query.term; | ||
} else if (req.query.ids !== undefined) { | ||
options._id = { $in: req.query.ids }; | ||
var data = (req.method === 'POST') ? req.body : req.query; | ||
if (data.term !== undefined) { | ||
options.titleSearch = data.term; | ||
} else if (data.values !== undefined) { | ||
criteria._id = { $in: data.values }; | ||
} else { | ||
@@ -706,5 +743,11 @@ // Since arrays in REST queries are ambiguous, | ||
} | ||
self.addExtraAutocompleteCriteria(req, options); | ||
// If requested, allow autocomplete to find unpublished | ||
// things (published === 'any'). Note that this is still | ||
// restricted by the permissions of the user making the request. | ||
if (data.published !== undefined) { | ||
options.published = data.published; | ||
} | ||
self.addExtraAutocompleteCriteria(req, criteria, options); | ||
// Format it as value & id properties for compatibility with jquery UI autocomplete | ||
self.get(req, options, function(err, results) { | ||
self.get(req, criteria, options, function(err, results) { | ||
if (err) { | ||
@@ -848,59 +891,62 @@ res.statusCode = 500; | ||
// Returns snippets the current user is permitted to read. If options.editable | ||
// is true, only snippets the current user can edit are returned. If options.sort is | ||
// present, it is passed to mongo's sort() method. All other properties of | ||
// options are merged with the MongoDB criteria object used to | ||
// select the relevant snippets. If options.sort is present, it is passed | ||
// as the argument to the MongoDB sort() function, replacing the | ||
// default alpha sort. optionsArg may be skipped. | ||
// Returns snippets the current user is permitted to read. | ||
// | ||
// options.limit indicates the maximum number of results. | ||
// CRITERIA | ||
// | ||
// If options.fields is present it is used to limit the fields returned | ||
// by MongoDB for performance reasons (the second argument to MongoDB's find()). | ||
// The criteria argument is combined with the standard MongoDB | ||
// criteria for fetching snippets via MongoDB's `$and` keyword. | ||
// This allows you to use any valid MongoDB criteria when | ||
// fetching snippets. | ||
// | ||
// options.titleSearch is used to search the titles of all snippets for a | ||
// particular string using a fairly tolerant algorithm. | ||
// OPTIONS | ||
// | ||
// The `options` argument provides *everything offered by | ||
// the `apos.get` method's `options` argument*, plus the following: | ||
// | ||
// PERMALINKING | ||
// | ||
// By default no ._url property is set on each item, as you often are rendering items | ||
// on a specific page and want to set the ._url property to match. If you set the | ||
// `permalink` option to true, the ._url property will be set for you, based on | ||
// the findBestPage algorithm. | ||
// By default no ._url property is set on each item, as you often | ||
// are rendering items on a specific page and want to set the ._url | ||
// property to match. If you set the `permalink` option to true, the | ||
// ._url property will be set for you, based on the findBestPage | ||
// algorithm. | ||
// | ||
// FETCHING METADATA FOR FILTERS | ||
// | ||
// If options.fetch is present, snippets.get will deliver an object | ||
// with a `snippets` property containing the array of snippets, rather | ||
// than delivering the array of snippets directly. | ||
// If `options.fetch.tags` is true, the `results` object will also | ||
// contain a `tags` property, containing all tags that are present on | ||
// the snippets when the criteria are taken into account | ||
// (ignoring limit and skip). This is useful to present a | ||
// "filter by tag" interface. | ||
// | ||
// If options.fetch.tags is true, snippets.get will also deliver a | ||
// `tags` property, containing all tags that are present on the snippets | ||
// (ignoring limit and skip). This is useful to present a "filter by tag" | ||
// interface. | ||
// | ||
// LIMITING METADATA RESULTS | ||
// | ||
// When you pass options.fetch.tags = true, the .tags property returned | ||
// is NOT restricted by any `tags` criteria present in `optionsArg`, so | ||
// that you may present alternatives to the tag you are currently filtering by. | ||
// When you set options.fetch.tags to `true`, the `.tags` property | ||
// returned is NOT restricted by any `tags` criteria present in | ||
// `optionsArg`, so that you may present alternatives to the tag you | ||
// are currently filtering by. | ||
// | ||
// However, you may still need to restrict the tags somewhat, for instance because | ||
// the entire page is locked down to show only things tagged red, green or blue. | ||
// You could do this after the fact but that would require MongoDB to do more | ||
// work up front. So for efficiency's sake, you can supply an object as the value | ||
// of options.fetch.tags, with an `only` property restricting the possible results: | ||
// However, you may still need to restrict the tags somewhat, for | ||
// instance because the entire page is locked down to show only things | ||
// tagged red, green or blue. | ||
// | ||
// You could do this after the fact but that would require MongoDB to | ||
// do more work up front. So for efficiency's sake, you can supply an | ||
// object as the value of options.fetch.tags, with an `only` property | ||
// restricting the possible results: | ||
// | ||
// options.fetch.tags = { only: [ 'red', 'green', 'blue' ] } | ||
// | ||
// Conversely, you may need to ensure a particular tag *does* appear in results.tags, | ||
// usually because it is the tag the user is manually filtering by right now: | ||
// Conversely, you may need to ensure a particular tag *does* appear | ||
// in `results.tags` even if it never appears in the snippets returned, | ||
// usually because it is the tag the user is manually filtering by | ||
// right now: | ||
// | ||
// Include 'blue' in the result even if it matches no snippets | ||
// | ||
// options.fetch.tags { only: [ 'red', 'green', 'blue' ], always: 'blue' } | ||
// options.fetch.tags = { only: [ 'red', 'green', 'blue' ], always: 'blue' } | ||
self.get = function(req, optionsArg, callback) { | ||
self.get = function(req, userCriteria, optionsArg, callback) { | ||
var options = {}; | ||
var filterCriteria = {}; | ||
var results = null; | ||
@@ -912,10 +958,17 @@ extend(true, options, optionsArg); | ||
} | ||
if (!options.type) { | ||
options.type = self._instance; | ||
} | ||
// filterCriteria is the right place to build up criteria | ||
// specific to this method; we'll $and it with the user's | ||
// criteria before passing it on to apos.get | ||
filterCriteria.type = self._instance; | ||
var fetch = options.fetch; | ||
delete options.fetch; | ||
var permalink = options.permalink; | ||
delete options.permalink; | ||
// Final criteria to pass to apos.get | ||
var criteria = { | ||
$and: [ | ||
userCriteria, | ||
filterCriteria | ||
] | ||
}; | ||
return async.series([ query, metadata, permalinker ], function(err) { | ||
@@ -926,3 +979,3 @@ return callback(err, results); | ||
function query(callback) { | ||
return self._apos.get(req, options, function(err, resultsArg) { | ||
return self._apos.get(req, criteria, options, function(err, resultsArg) { | ||
if (err) { | ||
@@ -1075,2 +1128,3 @@ return callback(err); | ||
var criteria = {}; | ||
var options = {}; | ||
var show = false; | ||
@@ -1082,5 +1136,5 @@ var slug = self.isShow(req); | ||
} else { | ||
self.addPager(req, criteria); | ||
self.addPager(req, options); | ||
} | ||
self.addCriteria(req, criteria); | ||
self.addCriteria(req, criteria, options); | ||
// If we are requesting a specific slug, remove the tags criterion. | ||
@@ -1093,5 +1147,5 @@ // In theory we should be strict about this, but in practice this is | ||
if (slug) { | ||
criteria.tags = undefined; | ||
delete criteria.tags; | ||
} | ||
return self.get(req, criteria, function(err, results) { | ||
return self.get(req, criteria, options, function(err, results) { | ||
if (err) { | ||
@@ -1125,3 +1179,3 @@ return callback(err); | ||
self.addPager = function(req, criteria) { | ||
self.addPager = function(req, options) { | ||
var pageNumber = self._apos.sanitizeInteger(req.query.page, 1, 1); | ||
@@ -1131,4 +1185,4 @@ req.extras.pager = { | ||
}; | ||
criteria.skip = self._perPage * (pageNumber - 1); | ||
criteria.limit = self._perPage; | ||
options.skip = self._perPage * (pageNumber - 1); | ||
options.limit = self._perPage; | ||
}; | ||
@@ -1184,4 +1238,4 @@ | ||
self.addCriteria = function(req, criteria) { | ||
criteria.fetch = { | ||
self.addCriteria = function(req, criteria, options) { | ||
options.fetch = { | ||
tags: {} | ||
@@ -1192,13 +1246,16 @@ }; | ||
// This restriction also applies when fetching distinct tags | ||
criteria.fetch.tags = { only: req.page.typeSettings.tags }; | ||
options.fetch.tags = { only: req.page.typeSettings.tags }; | ||
} | ||
if (req.query.tag) { | ||
// Override the criteria for fetching snippets but leave criteria.fetch.tags | ||
// Override the criteria for fetching snippets but leave options.fetch.tags | ||
// alone | ||
criteria.tags = { $in: [ req.query.tag ] }; | ||
// Always return the active tag as one of the filter choices even if | ||
// there are no results in this situation. Otherwise the user may not be | ||
// able to see the state of the filter (for instance if it is expressed | ||
// as a select element) | ||
criteria.fetch.tags.always = req.query.tag; | ||
var tag = self._apos.sanitizeString(req.query.tag); | ||
if (tag.length) { | ||
criteria.tags = { $in: [ tag ] }; | ||
// Always return the active tag as one of the filter choices even if | ||
// there are no results in this situation. Otherwise the user may not be | ||
// able to see the state of the filter (for instance if it is expressed | ||
// as a select element) | ||
options.fetch.tags.always = tag; | ||
} | ||
} | ||
@@ -1239,3 +1296,3 @@ }; | ||
// Pages in the trash are never good permalinks | ||
var pages = self._apos.pages.find({ trash: { $exists: false }, type: { $in: typeNames }, slug: /^\// }).toArray(function(err, pages) { | ||
return self._apos.get(req, { type: { $in: typeNames }, slug: /^\// }, {}, function(err, results) { | ||
if (err) { | ||
@@ -1246,20 +1303,8 @@ console.log('error is:'); | ||
} | ||
var pages = results.pages; | ||
if (!req.aposBestPageCache) { | ||
req.aposBestPageCache = {}; | ||
} | ||
var viewable = []; | ||
async.eachSeries(pages, function(page, callback) { | ||
self._apos.permissions(req, 'view-page', page, function(err) { | ||
if (!err) { | ||
viewable.push(page); | ||
} | ||
return callback(null); | ||
}); | ||
}, function(err) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
req.aposBestPageCache[snippet.type] = viewable; | ||
go(); | ||
}); | ||
req.aposBestPageCache[snippet.type] = pages; | ||
go(); | ||
}); | ||
@@ -1266,0 +1311,0 @@ |
{ | ||
"name": "apostrophe-snippets", | ||
"version": "0.0.31", | ||
"version": "0.0.32", | ||
"description": "Reusable content snippets for the Apostrophe content management system. The blog and events modules are built on this foundation, which is also useful in and of itself.", | ||
@@ -31,2 +31,2 @@ "main": "index.js", | ||
} | ||
} | ||
} |
@@ -147,3 +147,3 @@ // NOTES FOR REUSE: | ||
var $singleton = $editView.find('.apos-singleton:first'); | ||
$singleton.bind('apos-edited', function(e, data) { | ||
$singleton.bind('aposEdited', function(e, data) { | ||
refreshSingleton([data]); | ||
@@ -326,3 +326,3 @@ }); | ||
$el.on('apos-change-' + self._css, function(e, callback) { | ||
$el.on(apos.eventName('aposChange', self.name), function(e, callback) { | ||
var criteria = { editable: 1, skip: (page - 1) * self._managePerPage, limit: self._managePerPage }; | ||
@@ -452,3 +452,3 @@ $.extend(true, criteria, self.filters); | ||
// TODO: figure out how to kill them more definitively when they are done. | ||
$el.on('apos-change-revert', function() { | ||
$el.on('aposChangeRevert', function() { | ||
if (active) { | ||
@@ -519,3 +519,3 @@ relaunch = true; | ||
function triggerRefresh(callback) { | ||
$el.trigger('apos-change-' + self._css, callback); | ||
$el.trigger(apos.eventName('aposChange', self.name), callback); | ||
} | ||
@@ -522,0 +522,0 @@ // END MANAGER FUNCTIONALITY |
@@ -36,3 +36,3 @@ var _ = require('underscore'); | ||
self.addCriteria = function(item, criteria) { | ||
self.addCriteria = function(item, criteria, options) { | ||
if ((item.by === 'tag') && (item.tags)) { | ||
@@ -43,6 +43,6 @@ if (item.tags.length) { | ||
if (item.limit) { | ||
criteria.limit = item.limit; | ||
options.limit = item.limit; | ||
} else { | ||
// Always set an upper limit | ||
criteria.limit = 1000; | ||
options.limit = 1000; | ||
} | ||
@@ -84,6 +84,7 @@ } else if ((item.by === 'id') && (item.ids)) { | ||
var criteria = {}; | ||
var options = {}; | ||
self.addCriteria(item, criteria); | ||
self.addCriteria(item, criteria, options); | ||
self.snippets.get(req, criteria, function(err, results) { | ||
self.snippets.get(req, criteria, options, function(err, results) { | ||
if (err) { | ||
@@ -90,0 +91,0 @@ item._snippets = []; |
Sorry, the diff of this file is not supported yet
136119
22
1922