apostrophe-snippets
Advanced tools
Comparing version 0.0.1 to 0.0.2
179
index.js
@@ -7,2 +7,3 @@ var async = require('async'); | ||
var async = require('async'); | ||
var csv = require('csv'); | ||
@@ -42,6 +43,9 @@ // GUIDE TO USE | ||
// | ||
// `beforeInsert` receives the req object and a snippet about to be inserted for the | ||
// first time, and a callback. Modify it to add additional fields, then invoke the | ||
// callback with a fatal error if any. (Always sanitize rather than failing | ||
// if it is in any way possible.) | ||
// `beforeInsert` receives the req object, the data source (req.body for the | ||
// common case, but from elsewhere in the importer), a snippet about to be inserted for | ||
// the first time, and a callback. Modify it to add additional fields, then invoke the | ||
// callback with a fatal error if any. Always sanitize rather than failing | ||
// if it is in any way possible. You receive both the req object and the data | ||
// source because the data source is not req.body in every case (for instance, a | ||
// bulk import uploaded as a file). | ||
// | ||
@@ -94,3 +98,3 @@ // `beforeUpdate` performs the same function for updates. | ||
self._dirs = (options.dirs || []).concat([ __dirname ]); | ||
self._webAssetDir = options.webAssetDir || __dirname + '/public'; | ||
self._webAssetDir = options.webAssetDir || __dirname; | ||
// The type property of the page object used to store the snippet, also | ||
@@ -103,2 +107,4 @@ // passed to views for use in CSS classes etc. Should be camel case. These | ||
self._menuName = options.menuName; | ||
// All partials generated via self.renderer can see these properties | ||
self._rendererGlobals = options.rendererGlobals || {}; | ||
@@ -121,3 +127,3 @@ if (!typesByInstanceType[self._instance]) { | ||
} else { | ||
return self._apos.pushAsset(type, name, self._dirs, self._action); | ||
return self._apos.pushAsset(type, name, self._webAssetDir, self._action); | ||
} | ||
@@ -173,6 +179,3 @@ }; | ||
snippet.sortTitle = self._apos.sortify(snippet.title); | ||
if (self.beforeInsert) { | ||
return self.beforeInsert(req, snippet, callback); | ||
} | ||
return callback(null); | ||
return self.beforeInsert(req, req.body, snippet, callback); | ||
} | ||
@@ -193,2 +196,10 @@ | ||
self.beforeInsert = function(req, data, snippet, callback) { | ||
return callback(null); | ||
}; | ||
self.beforeUpdate = function(req, data, snippet, callback) { | ||
return callback(null); | ||
}; | ||
self._app.post(self._action + '/update', function(req, res) { | ||
@@ -251,6 +262,3 @@ var snippet; | ||
snippet.areas = { body: { items: content } }; | ||
if (self.beforeUpdate) { | ||
return self.beforeUpdate(req, snippet, callback); | ||
} | ||
return callback(null); | ||
return self.beforeUpdate(req, req.body, snippet, callback); | ||
} | ||
@@ -328,3 +336,112 @@ | ||
self._app.post(self._action + '/import', function(req, res) { | ||
var file = req.files.file; | ||
var rows = 0; | ||
var headings = []; | ||
var s = csv().from.stream(fs.createReadStream(file.path)); | ||
var active = 0; | ||
s.on('record', function(row, index) { | ||
active++; | ||
// s.pause() avoids an explosion of rows being processed simultaneously | ||
// by async mongo calls, etc. However note this does not | ||
// stop more events from coming in because the parser will | ||
// keep going with its current block of raw data. So we still | ||
// have to track the number of still-active async handleRow calls ): | ||
// Also there is no guarantee imports are in order, however that shouldn't | ||
// matter since we always rely on some index such as title or publication date | ||
s.pause(); | ||
if (!index) { | ||
handleHeadings(row, afterRow); | ||
} else { | ||
handleRow(row, function(err) { | ||
if (!err) { | ||
rows++; | ||
return afterRow(); | ||
} else { | ||
console.log(err); | ||
s.end(); | ||
} | ||
}); | ||
} | ||
function afterRow() { | ||
s.resume(); | ||
active--; | ||
} | ||
}) | ||
.on('error', function(count) { | ||
respondWhenDone('error'); | ||
}) | ||
.on('end', function(count) { | ||
respondWhenDone('ok'); | ||
}); | ||
function handleHeadings(row, callback) { | ||
headings = row; | ||
var i; | ||
for (i = 0; (i < headings.length); i++) { | ||
headings[i] = self._apos.camelName(headings[i]); | ||
} | ||
return callback(); | ||
} | ||
function handleRow(row, callback) { | ||
var data = {}; | ||
var i; | ||
for (i = 0; (i < headings.length); i++) { | ||
data[headings[i]] = row[i]; | ||
} | ||
self.importCreateItem(req, data, callback); | ||
} | ||
function respondWhenDone(status) { | ||
if (active) { | ||
return setTimeout(function() { respondWhenDone(status); }, 100); | ||
} | ||
res.send({ status: status, rows: rows }); | ||
} | ||
}); | ||
self.importCreateItem = function(req, data, callback) { | ||
var tags = []; | ||
if (Array.isArray(data.tags)) { | ||
tags.concat(data.tags); | ||
} | ||
if (Array.isArray(data.categories)) { | ||
tags.concat(data.categories); | ||
} | ||
var snippet = { | ||
type: self._instance, | ||
areas: { | ||
body: { | ||
items: [ | ||
{ | ||
type: 'richText', | ||
content: data.richText || (data.text ? self._apos.escapeHtml(data.text) : '') | ||
} | ||
] | ||
} | ||
}, | ||
title: data.title || self.getDefaultTitle(), | ||
tags: self._apos.tagsToArray(tags) | ||
}; | ||
snippet.slug = self._apos.slugify(snippet.title); | ||
snippet.sortTitle = self._apos.sortify(snippet.title); | ||
// Something is eating error messages here if I don't log them myself | ||
try { | ||
self.beforeInsert(req, data, snippet, function(err) { | ||
if (err) { | ||
return callback(err); | ||
} | ||
return self.importSaveItem(snippet, callback); | ||
}); | ||
} catch (e) { | ||
console.log(e); | ||
throw e; | ||
} | ||
}; | ||
self.importSaveItem = function(snippet, callback) { | ||
self._apos.putPage(snippet.slug, snippet, callback); | ||
}; | ||
self._app.get(self._action + '/get', function(req, res) { | ||
@@ -359,3 +476,2 @@ self.get(req, req.query, function(err, snippets) { | ||
} | ||
console.log(options); | ||
// Format it as value & id properties for compatibility with jquery UI autocomplete | ||
@@ -372,4 +488,7 @@ self.get(req, options, function(err, snippets) { | ||
// Serve our assets. This is the final route so it doesn't | ||
// beat out the rest. Note we allow overrides for assets too | ||
self._app.get(self._action + '/*', self._apos.static(self._webAssetDir)); | ||
// beat out the rest. | ||
// | ||
// You don't override js and stylesheet assets, rather you serve more of them | ||
// from your own module and enhance what's already in browserland | ||
self._app.get(self._action + '/*', self._apos.static(self._webAssetDir + '/public')); | ||
@@ -387,5 +506,11 @@ self._apos.addLocal(self._menuName, function(args) { | ||
// Return a function that will render a particular partial looking for overrides in our | ||
// preferred places | ||
// preferred places. Also merge in any properties of self._rendererGlobals, which can | ||
// be set via the rendererGlobals option when the module is configured | ||
self.renderer = function(name) { | ||
return function(data) { | ||
if (!data) { | ||
data = {}; | ||
} | ||
_.defaults(data, self._rendererGlobals); | ||
return self._apos.partial(name, data, _.map(self._dirs, function(dir) { return dir + '/views'; })); | ||
@@ -418,5 +543,2 @@ }; | ||
console.log('options passed are:'); | ||
console.log(optionsArg); | ||
var options = {}; | ||
@@ -474,7 +596,5 @@ extend(options, optionsArg, true); | ||
if (limit !== undefined) { | ||
console.log("Limiting to " + limit); | ||
q.limit(limit); | ||
} | ||
if (skip !== undefined) { | ||
console.log("Skipping " + skip); | ||
q.skip(skip); | ||
@@ -548,3 +668,2 @@ } | ||
req.extras[permissionName] = !err; | ||
console.log('added permission for ' + permissionName + ' set to ' + (!err)); | ||
return callback(null); | ||
@@ -584,6 +703,4 @@ }); | ||
var permalink = false; | ||
console.log('in dispatch'); | ||
var criteria = {}; | ||
if (req.remainder.length) { | ||
console.log('remainder is: ' + req.remainder); | ||
// Perhaps it's a snippet permalink | ||
@@ -647,4 +764,2 @@ criteria.slug = req.remainder.substr(1); | ||
var typeNames = _.map(typesByInstanceType[snippet.type] || [], function(type) { return type.name; }); | ||
console.log('type names'); | ||
console.log(typeNames); | ||
var pages = self._apos.pages.find({ type: { $in: typeNames }, slug: /^\// }).toArray(function(err, pages) { | ||
@@ -656,3 +771,2 @@ if (err) { | ||
} | ||
console.log('Matched ' + pages.length + ' pages'); | ||
// Play nice with invocations of findBestPage for other types | ||
@@ -689,10 +803,5 @@ // as part of the same request | ||
var best = null; | ||
console.log('our tags:'); | ||
console.log(tags); | ||
_.each(viewable, function(page) { | ||
var score = 0; | ||
console.log(page); | ||
var pageTags = (page.typeSettings && page.typeSettings.tags) ? page.typeSettings.tags : []; | ||
console.log('page tags:'); | ||
console.log(pageTags); | ||
if (!pageTags.length) { | ||
@@ -704,3 +813,2 @@ score = 1; | ||
score += intersect.length * 2 - diff.length; | ||
console.log(snippet.slug + ': ' + page.slug + ': ' + score); | ||
if ((!best) || (score > bestScore)) { | ||
@@ -735,2 +843,3 @@ bestScore = score; | ||
self.pushAsset('template', 'manage'); | ||
self.pushAsset('template', 'import'); | ||
@@ -737,0 +846,0 @@ // It's possible to show a collection of recent snippets publicly |
{ | ||
"name": "apostrophe-snippets", | ||
"version": "0.0.1", | ||
"version": "0.0.2", | ||
"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.", | ||
@@ -27,4 +27,5 @@ "main": "index.js", | ||
"underscore": "~1.4.4", | ||
"extend": "~1.1.3" | ||
"extend": "~1.1.3", | ||
"csv": "~0.2.9" | ||
} | ||
} |
@@ -136,4 +136,2 @@ // NOTES FOR REUSE: | ||
// Manage all snippets | ||
apos.log('configuring click for ' + '[data-manage-' + self._css + ']'); | ||
apos.log('length is: ' + $('[data-manage-' + self._css + ']').length); | ||
$('body').on('click', '[data-manage-' + self._css + ']', function() { | ||
@@ -230,2 +228,37 @@ var snippets; | ||
}); | ||
// Import snippets | ||
$('body').on('click', '[data-import-' + self._css + ']', function() { | ||
apos.log('import clicked for ' + self._css); | ||
var valid = false; | ||
$el = apos.modalFromTemplate('.apos-import-' + self._css, { | ||
init: function(callback) { | ||
// The file upload's completion will trigger the import operation | ||
$el.find('[data-action="save"]').remove(); | ||
$el.find('[name="file"]').fileupload({ | ||
maxNumberOfFiles: 1, | ||
dataType: 'json', | ||
start: function (e) { | ||
$('[data-progress]').show(); | ||
$('[data-finished]').hide(); | ||
}, | ||
stop: function (e) { | ||
$('[data-progress]').hide(); | ||
$('[data-finished]').show(); | ||
}, | ||
done: function (e, data) { | ||
var data = data.result; | ||
apos.log(data); | ||
if (data.status === 'ok') { | ||
alert('Successful import. Imported ' + data.rows + ' items.'); | ||
} else { | ||
alert('An error occurred during import. Imported ' + data.rows + ' items.'); | ||
} | ||
$el.trigger('aposModalHide'); | ||
} | ||
}); | ||
return callback(null); | ||
} | ||
}); | ||
}); | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
56831
20
1197
4
+ Addedcsv@~0.2.9
+ Addedcsv@0.2.9(transitive)