Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

apostrophe

Package Overview
Dependencies
Maintainers
1
Versions
1081
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

apostrophe - npm Package Compare versions

Comparing version 0.3.11 to 0.3.12

views/singleton.html

60

apostrophe.js

@@ -190,2 +190,23 @@ var oembed = require('oembed');

aposLocals.aposSingleton = function(options) {
console.log('aposSingleton');
if (!self.itemTypes[options.type]) {
console.log("Unknown item type: " + options.type);
return;
}
// If someone transforms an existing area into a singleton, do a reasonable thing by
// taking the first existing item of the proper type
var item = _.find(options.area.items, function(item) {
return item.type === options.type;
});
if (!item) {
item = { type: options.type };
}
options.itemType = self.itemTypes[item.type];
options.item = item;
console.log('Rendering!');
console.log(options);
return partial('singleton.html', options);
}
aposLocals.aposAreaContent = function(items, options) {

@@ -430,2 +451,41 @@ var result = '';

app.post('/apos/edit-singleton', function(req, res) {
var slug = req.body.slug;
self.permissions(req, 'edit-area', slug, function(err) {
if (err) {
return forbid(res);
}
var content = JSON.parse(req.body.content);
// "OMG, what if they cheat and use a type not allowed for this singleton?"
// When they refresh the page they will discover they can't see their hack.
// aposSingleton only shows the first item of the specified type, regardless
// of what is kicking around in the area.
var type = content.type;
var itemType = self.itemTypes[type];
if (!itemType) {
return fail(req, res);
}
if (itemType.sanitize) {
itemType.sanitize(content);
}
var area = {
slug: req.body.slug,
items: [ content ]
};
self.putArea(slug, area, updated);
function updated(err) {
if (err) {
console.log(err);
return notfound(req, res);
}
return callLoadersForArea(area, function() {
return res.send(aposLocals.aposAreaContent(area.items));
});
}
});
});
// Used to render newly created, as yet unsaved widgets to be displayed in

@@ -432,0 +492,0 @@ // the main apos editor. We're not really changing anything in the database

2

package.json
{
"name": "apostrophe",
"version": "0.3.11",
"version": "0.3.12",
"description": "Apostrophe is a user-friendly content management system. This core module of Apostrophe provides rich content editing and essential facilities to integrate Apostrophe into your Express project. Apostrophe also includes simple facilities for storing your rich content areas in MongoDB and fetching them back again. Additional functionality is available in modules like apostrophe-twitter and apostrophe-rss and forthcoming modules that address page trees, blog posts, events and the like.",

@@ -5,0 +5,0 @@ "main": "apostrophe.js",

@@ -583,4 +583,5 @@ if (!window.apos) {

// When present in the context of a rich text editor, we interrogate
// our placeholder div in that editor to get our current attributes
if (options.widgetId) {
self.exists = true;
self.$widget = options.editor.$editable.find('.apos-widget[data-id="' + options.widgetId + '"]');

@@ -590,10 +591,25 @@ self.data = self.$widget.data();

self.widgetId = options.widgetId ? options.widgetId : apos.generateId();
self.data.id = self.widgetId;
// When displayed as a singleton or an area that does not involve a
// rich text editor as the larger context for all widgets, our data is passed in
if (options.data) {
self.data = options.data;
apos.log(self.data);
}
// Make sure the selection we return to
// is actually on the editor
self.editor.$editable.focus();
// Make our own instance of the image editor template
// so we don't have to fuss over old event handlers
if (self.data.id) {
self.exists = true;
}
if (!self.data.id) {
self.data.id = apos.generateId();
}
// Careful, relevant only when we are in a rich text editor context
if (self.editor) {
// Make sure the selection we return to
// is actually on the editor
self.editor.$editable.focus();
}
// Make our own instance of the editor template
self.$el = $(options.template + '.apos-template').clone();

@@ -625,3 +641,5 @@ self.$el.removeClass('.apos-template');

// Return focus to the main editor
self.editor.$editable.focus();
if (self.editor) {
self.editor.$editable.focus();
}
},

@@ -665,2 +683,5 @@

createWidget: function() {
if (!self.editor) {
return;
}
self.$widget = $('<div></div>');

@@ -671,3 +692,2 @@ // self.$widget.attr('unselectable', 'on');

self.$widget.attr('data-type', self.type);
self.$widget.attr('data-id', self.widgetId);
},

@@ -681,2 +701,5 @@

updateWidget: function(callback) {
if (!self.editor) {
return callback(null);
}
var sizeAndPosition = self.getSizeAndPosition();

@@ -707,2 +730,5 @@ self.$widget.attr({

updateWidgetData: function() {
if (!self.editor) {
return;
}
_.each(self.data, function(val, key) {

@@ -716,2 +742,5 @@ apos.log(key + ': ' + val);

renderWidget: function(callback) {
if (!self.editor) {
return callback(null);
}
// Get all the data attributes

@@ -742,2 +771,5 @@ var info = self.$widget.data();

insertWidget: function() {
if (!self.editor) {
return;
}
var markup = '';

@@ -820,3 +852,2 @@

self.preSave(function() {
self.editor.undoPoint();
if (!self.exists) {

@@ -826,6 +857,9 @@ alert(options.messages.missing);

}
var _new = false;
if (!self.$widget) {
self.createWidget();
_new = true;
if (self.editor) {
self.editor.undoPoint();
var _new = false;
if (!self.$widget) {
self.createWidget();
_new = true;
}
}

@@ -840,3 +874,16 @@ self.updateWidget(function(err) {

// self.editor.updateWidgetBackup(self.widgetId, self.$widget);
self.destroy();
if (options.save) {
// Used to implement save for singletons and non-rich-text-based areas.
// Note that in this case options.data was passed in by reference,
// so the end result can be read there. Pay attention to the callback so
// we can allow the user a second chance
options.save(function(err) {
if (!err) {
self.destroy();
}
});
} else {
self.destroy();
}
});

@@ -861,4 +908,4 @@ });

if (self.exists) {
sizeAndPosition.size = self.$widget.attr('data-size');
sizeAndPosition.position = self.$widget.attr('data-position');
sizeAndPosition.size = self.data.size;
sizeAndPosition.position = self.data.position;
}

@@ -890,6 +937,6 @@ self.$el.find('input[name="size"]').prop('checked', false);

self.afterCreatingEl = function() {
self.$el.find('[data-iframe-placeholder]').replaceWith($('<iframe id="iframe-' + self.widgetId + '" name="iframe-' + self.widgetId + '" class="apos-file-iframe" src="/apos/file-iframe/' + self.widgetId + '"></iframe>'));
self.$el.find('[data-iframe-placeholder]').replaceWith($('<iframe id="iframe-' + self.data.id + '" name="iframe-' + self.data.id + '" class="apos-file-iframe" src="/apos/file-iframe/' + self.data.id + '"></iframe>'));
self.$el.bind('uploaded', function(e, id) {
// Only react to events intended for us
if (id === self.widgetId) {
if (id === self.data.id) {
self.exists = true;

@@ -906,3 +953,3 @@ self.preview();

if (self.exists) {
$.getJSON('/apos/file-info/' + self.widgetId, function(info) {
$.getJSON('/apos/file-info/' + self.data.id, function(info) {
self.data.extension = info.extension;

@@ -1206,8 +1253,8 @@ callback();

apos.enableAreas = function() {
$('.apos-edit-area').click(function() {
$('body').on('click', '.apos-edit-area', function() {
var area = $(this).closest('.apos-area');
var slug = area.attr('data-apos-slug');
var slug = area.attr('data-slug');
$.get('/apos/edit-area', { slug: slug, controls: area.attr('data-apos-controls') }, function(data) {
$.get('/apos/edit-area', { slug: slug, controls: area.attr('data-controls') }, function(data) {
area.find('.apos-edit-view').remove();

@@ -1225,3 +1272,3 @@ var editView = $('<div class="apos-edit-view"></div>');

area.find('[data-save-area]').click(function() {
var slug = area.attr('data-apos-slug');
var slug = area.attr('data-slug');
$.post('/apos/edit-area',

@@ -1251,2 +1298,37 @@ {

});
$('body').on('click', '.apos-edit-singleton', function() {
var $singleton = $(this).closest('.apos-singleton');
var slug = $singleton.attr('data-slug');
var type = $singleton.attr('data-type');
var itemData = {};
var $item = $singleton.find('.apos-content .apos-widget :first');
if ($item.length) {
itemData = $item.data();
}
new apos.widgetTypes[type].editor({
data: itemData,
save: function(callback) {
apos.log(itemData);
$.post('/apos/edit-singleton',
{
slug: slug,
// By now itemData has been updated (we passed it
// into the widget and JavaScript passes objects by reference)
content: JSON.stringify(itemData)
},
function(markup) {
$singleton.find('.apos-content').html(markup);
apos.enablePlayers($singleton);
callback(null);
}
).fail(function() {
alert('Server error, please try again.');
callback('error');
});
}
});
return false;
});
};

@@ -1253,0 +1335,0 @@

# Apostrophe
## This is an early alpha quality version of Apostrophe 2, for node.js developers. Most frontend design work has not happened yet. Page trees, blogs events, etc. are not part of Apostrophe 2 yet. See [Apostrophe 1.5](http://apostrophenow.org) for the current stable and mature release of Apostrophe for PHP and Symfony.
## This is an early alpha quality version of Apostrophe 2, for node.js developers. Most frontend design work has not happened yet. Blogs, events, etc. are not part of Apostrophe 2 yet. See [Apostrophe 1.5](http://apostrophenow.org) for the current stable and mature release of Apostrophe for PHP and Symfony.

@@ -9,3 +9,3 @@ Apostrophe is a content management system. This core module provides rich content editing as well as essential services to tie Apostrophe to your Express application.

[You can try a live demo of the Apostrophe 2 Wiki sample app here.](http://demowiki.apostrophenow.com/) (Note: the demo site resets at the top of the hour.) See also the [apostrophe-wiki github project](http://github.com/punkave/apostrophe-wiki).
[You can try a live demo of the Apostrophe 2 sandbox app here.](http://demo2.apostrophenow.com/) (Note: the demo site resets at the top of the hour.) See also the [apostrophe-sandbox github project](http://github.com/punkave/apostrophe-sandbox).

@@ -41,3 +41,3 @@ Apostrophe introduces "widgets," separate editors for rich media items like photos, videos, pullquotes and code samples. Apostrophe's widgets handle these items much better than a rich text editor on its own.

node, of course
mongodb, on your local machine (or edit wiki.js to point somewhere else)
mongodb, on your local machine (or edit app.js to point somewhere else)
imagemagick, to resize uploaded images (specifically the `convert` command line tool)

@@ -53,24 +53,37 @@

Here's the `initApos` function of the sample application [http://github.com/punkave/aposwiki](aposwiki). Notice this function invokes a callback when it's done. `wiki.js` makes good use of the `async` module to carry out its initialization tasks elegantly.
Here's the `initApos` function of the sample application [http://github.com/punkave/apostrophe-sandbox](apostrophe-sandbox). Notice this function invokes a callback when it's done. `app.js` makes good use of the `async` module to carry out its initialization tasks elegantly. Here we also initialize other modules that snap into Apostrophe:
function initApos(callback) {
return apos.init({
files: appy.files,
areas: appy.areas,
pages: appy.pages,
app: app,
uploadfs: uploadfs,
permissions: aposPermissions,
}, callback);
require('apostrophe-twitter')({ apos: apos, app: app });
require('apostrophe-rss')({ apos: apos, app: app });
async.series([initAposMain, initAposPages], callback);
function initAposMain(callback) {
console.log('initAposMain');
return apos.init({
db: db,
app: app,
uploadfs: uploadfs,
permissions: aposPermissions,
// Allows us to extend shared layouts
partialPaths: [ __dirname + '/views/global' ]
}, callback);
}
function initAposPages(callback) {
console.log('initAposPages');
pages = require('apostrophe-pages')({ apos: apos, app: app }, callback);
}
}
"What are `appy.files`, `appy.areas` and `appy.pages`?" MongoDB collections. You are responsible for connecting to MongoDB and creating these three collection objects, then providing them to Apostrophe. (Hint: it's pretty convenient with Appy.) For best results, `areas` and `pages` should both have a unique index on the `slug` property. The `areas` collection is used for independent areas, while the `pages` collection is handy when areas should be logically grouped together (consider the "main" and "sidebar" content areas of a webpage, for instance). Apostrophe's getArea, putArea and getPage methods make all that easy for you as you'll see below.
"Where does db come from?" It's a MongoDB native database connection. (Hint: convenient to set up with Appy, or just use mongodb-native yourself.) Apostrophe's getArea, putArea and getPage methods utilize these.
"What is `app`?" `app` is your Express 3.0 app object. See the Express documentation for how to create an application. Again, Appy helps here.
"What is `uploadfs`?" [http://github.com/punkave/uploadfs](uploadfs) is a module that conveniently stores uploaded files in either the local filesystem or S3, whichever you like. See `wiki.js` for an example of configuration. You'll create an `uploadfs` instance, initialize it and then pass it in here.
"What is `uploadfs`?" [http://github.com/punkave/uploadfs](uploadfs) is a module that conveniently stores uploaded files in either the local filesystem or S3, whichever you like. See `app.js` in the `apostrophe-sandbox` project for an example of configuration. You'll create an `uploadfs` instance, initialize it and then pass it in here.
"What is `aposPermissions`?" A function you define to decide who is allowed to edit content. If you skip this parameter, Apostrophe allows everyone to edit everything - not safe in production of course, but convenient in the early development stages.
To understand configuration in detail, you should really check out `wiki.js`. Please don't suffer without reading that simple and well-commented example.
To understand configuration in detail, you should really check out `app.js`. Please don't suffer without reading that simple and well-commented example.

@@ -114,3 +127,3 @@ ### Making Sure Apostrophe Is In The Browser

The easiest way to add Apostrophe-powered editable rich content areas to your Node Express 3.0 project is to use Apostrophe's `aposArea` function, which is made available to your Express templates when you configure Apostrophe. Here's a simple example taken from the aposwiki sample application:
The easiest way to add Apostrophe-powered editable rich content areas to your Node Express 3.0 project is to use Apostrophe's `aposArea` function, which is made available to your Express templates when you configure Apostrophe. Here's a simple example taken from the apostrophe-sandbox sample application:

@@ -131,3 +144,3 @@ {{ aposArea({ slug: 'main', items: main, edit: true }) }}

"Where does `items` come from?" Good question. You are responsible for fetching the content as part of the Express route code that renders your template. You do this with Apostrophe's `getArea` and `getPage` methods.
"Where does `items` come from?" Good question. You are responsible for fetching the content as part of the Express route code that renders your template. You do this with Apostrophe's `getArea` and `getPage` methods. [Note: if you just want a tree of editable pages, use the apostrophe-pages module to do most of this work.](http://github.com/punkave/apostrophe-pages)

@@ -144,4 +157,16 @@ Naturally `getArea` is asynchronous:

Also note that there is an `err` parameter to the callback. Real-world applications should check for errors (and the `wiki.js` sample application does).
Also note that there is an `err` parameter to the callback. Real-world applications should check for errors (and the `app.js` sample application does).
## Displaying Single Widgets ("Singletons")
Of course, sometimes you want to enforce a more specific design for an editable page. You might, for instance, want to require the user to pick a video for the upper right corner. You can do that with `aposSingleton`:
{{ aposSingleton({ slug: slug + ':sidebarVideo', type: 'video', area: page.areas.sidebarVideo, edit: edit }) }}
Note that singletons are stored as areas. The only difference is that the interface only displays and edits the first item of the specified type found in the area. There is no rich text editor "wrapped around" the widget, so clicking "edit" for a video immediately displays the video dialog box.
Only widgets (images, videos and the like) may be specified as types for singletons. For a standalone rich-text editor that doesn't allow any widgets, just limit the set of controls to those that are not widgets:
{{ aposArea({ slug: 'main', items: main, edit: true, controls: [ 'style', 'bold', 'italic', 'createLink' ] }) }}
## Grouping Areas Into "Pages"

@@ -183,61 +208,4 @@

The [apostrophe-wiki sample application](http://github.com/punkave/apostrophe-wiki) uses this method to deliver complete Wiki pages:
The [apostrophe-pages module](http://github.com/punkave/apostrophe-pages) uses this method to deliver complete pages automatically for you. In most cases this is what you'll want to do. In rarer cases you'll write your own routes that need to deliver content. See the sandbox project and the `apostrophe-pages` module for examples.
app.get('*', function(req, res) {
var slug = req.params[0];
apos.getPage(slug, function(e, info) {
return res.render('page.html', {
slug: info.slug,
main: info.areas.main ? info.areas.main.items : [],
sidebar: info.areas.sidebar ? info.areas.sidebar.items : []
});
});
});
This is a simplified example. The actual code in `wiki.js` uses middleware to summon the page and footer information into the `req` object. There are two middleware functions, one for the page contents and one for a globally shared footer. It's a good strategy to consider. Perhaps we'll add some standard middleware functions like this soon:
app.get('*',
function(req, res, next) {
// Get content for this page
req.slug = req.params[0];
apos.getPage(req.slug, function(e, info) {
if (e) {
console.log(e);
return fail(req, res);
}
if (!info) {
info = { slug: slug, areas: {} };
}
req.page = info;
return next();
});
},
function(req, res, next) {
// Get the shared footer
apos.getArea('footer', function(e, info) {
if (e) {
console.log(e);
return fail(req, res);
}
req.footer = info;
return next();
});
},
function (req, res) {
return res.render('page.html', {
slug: req.slug,
main: req.page.areas.main ? req.page.areas.main.items : [],
sidebar: req.page.areas.sidebar ? req.page.areas.sidebar.items : [],
user: req.user,
edit: req.user && req.user.username === 'admin',
footer: req.footer ? req.footer.content : ''
});
}
);
Now `page.jade` can call `aposArea` to render the areas:
{{ aposArea({ slug: slug + ':main', items: main, edit: true }) }}
{{ aposArea({ slug: slug + ':sidebar', items: sidebar, edit: true }) }}
## Enforcing Permissions

@@ -255,8 +223,8 @@

Currently the possible actions are `edit-area` and `edit-media`. `edit-area` calls will include the slug of the area as the third parameter. `edit-media` calls for existing files may include a `file` object retrieved from Apostrophe's database, with an "owner" property set to the _id, id or username property of `req.user` at the time the file was last edited. `edit-media` calls with no existing file parameter also occur, for new file uploads.
Currently the possible actions are `edit-area`, `edit-media`, `edit-page` and `view-page` (the latter two are added by the `apostrophe-pages` module). `edit-area` calls will include the slug of the area as the third parameter. `edit-media` calls for existing files may include a `file` object retrieved from Apostrophe's database, with an "owner" property set to the _id, id or username property of `req.user` at the time the file was last edited. `edit-media` calls with no existing file parameter also occur, for new file uploads.
A common case is to restrict editing to a single user:
A common case is to restrict editing to a single user but let view actions sail through:
function permissions(req, action, fileOrSlug, callback) {
if (req.user && (req.user.username === 'admin')) {
if (req.user && (!action.match(/^view-/)) && (req.user.username === 'admin')) {
// OK

@@ -269,3 +237,3 @@ return callback(null);

You can see an example of this pattern in `wiki.js`.
You can see an example of this pattern in `app.js` in the sandbox project.

@@ -276,2 +244,4 @@ ## Extending Apostrophe

The [apostrophe-pages](http://github.com/punkave/apostrophe-pages) module extends Apostrophe with full blown support for trees of editable web pages (like having a static site, except of course that your users can edit it gorgeously and intuitively).
## Roadmap

@@ -288,4 +258,2 @@

* It should be possible to fetch just certain rich media from areas conveniently and quickly (technically possible now, see above).
* Server-side renders should be cached, for a minimum lifetime equal to that of the widget with the shortest cache lifetime.
* A separate module that complements Apostrophe by managing "pages" in a traditional page tree should be developed.

@@ -292,0 +260,0 @@ ## Conclusion and Contact Information

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc