2007-06-23 14:53:39 +00:00
|
|
|
|
// The Antville Project
|
|
|
|
|
// http://code.google.com/p/antville
|
|
|
|
|
//
|
2011-02-23 16:24:32 +00:00
|
|
|
|
// Copyright 2007-2011 by Tobi Schäfer.
|
|
|
|
|
//
|
|
|
|
|
// Copyright 2001–2007 Robert Gaggl, Hannes Wallnöfer, Tobi Schäfer,
|
|
|
|
|
// Matthias & Michael Platzer, Christoph Lincke.
|
2007-06-23 14:53:39 +00:00
|
|
|
|
//
|
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the ``License'');
|
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
|
//
|
2014-07-04 15:32:18 +02:00
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
2007-06-23 14:53:39 +00:00
|
|
|
|
//
|
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
// distributed under the License is distributed on an ``AS IS'' BASIS,
|
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
|
// limitations under the License.
|
|
|
|
|
//
|
|
|
|
|
// $Revision$
|
2011-03-15 20:22:08 +00:00
|
|
|
|
// $Author$
|
|
|
|
|
// $Date$
|
2007-06-23 14:53:39 +00:00
|
|
|
|
// $URL$
|
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
2011-03-15 20:22:08 +00:00
|
|
|
|
* @fileOverview Defines the Story prototype.
|
2009-11-02 16:16:41 +00:00
|
|
|
|
*/
|
|
|
|
|
|
2014-07-04 15:44:41 +02:00
|
|
|
|
markgettext('Story');
|
|
|
|
|
markgettext('story');
|
2010-05-24 13:32:40 +00:00
|
|
|
|
|
2014-07-04 15:44:41 +02:00
|
|
|
|
this.handleMetadata('title');
|
|
|
|
|
this.handleMetadata('text');
|
2011-03-15 20:22:08 +00:00
|
|
|
|
|
2013-04-06 19:35:18 +00:00
|
|
|
|
Story.ALLOWED_MACROS = [
|
2014-07-04 15:44:41 +02:00
|
|
|
|
'file',
|
|
|
|
|
'image',
|
|
|
|
|
'link',
|
|
|
|
|
'poll',
|
|
|
|
|
'story.link'
|
2013-04-06 19:35:18 +00:00
|
|
|
|
];
|
|
|
|
|
|
2011-03-15 20:22:08 +00:00
|
|
|
|
/**
|
|
|
|
|
* @function
|
2011-03-15 22:07:24 +00:00
|
|
|
|
* @param {Object} data
|
|
|
|
|
* @param {Site} site
|
|
|
|
|
* @param {User} user
|
2011-03-15 20:22:08 +00:00
|
|
|
|
* @returns {Story}
|
|
|
|
|
*/
|
2011-03-15 22:07:24 +00:00
|
|
|
|
Story.add = function(data, site, user) {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
HopObject.confirmConstructor(Story);
|
|
|
|
|
site || (site = res.handlers.site);
|
|
|
|
|
user || (user = session.user);
|
|
|
|
|
var story = new Story;
|
|
|
|
|
story.name = String.EMPTY;
|
|
|
|
|
story.requests = 0;
|
|
|
|
|
story.created = story.modified = new Date;
|
|
|
|
|
story.site = site;
|
|
|
|
|
story.creator = story.modifier = user;
|
|
|
|
|
story.update(data);
|
|
|
|
|
site.stories.add(story);
|
|
|
|
|
return story;
|
2011-03-15 20:22:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @function
|
|
|
|
|
*/
|
|
|
|
|
Story.remove = function() {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
if (this.constructor === Story) {
|
|
|
|
|
HopObject.remove.call(this.comments);
|
|
|
|
|
this.setTags(null);
|
|
|
|
|
this.deleteMetadata();
|
|
|
|
|
this.remove();
|
|
|
|
|
}
|
|
|
|
|
return;
|
2011-03-15 20:22:08 +00:00
|
|
|
|
}
|
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
|
|
|
|
* @function
|
|
|
|
|
* @returns {String[]}
|
|
|
|
|
* @see defineConstants
|
|
|
|
|
*/
|
2014-07-04 15:44:41 +02:00
|
|
|
|
Story.getStatus = defineConstants(Story, markgettext('closed'),
|
|
|
|
|
markgettext('public'), markgettext('shared'), markgettext('open'));
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
|
|
|
|
* @function
|
|
|
|
|
* @returns {String[]}
|
|
|
|
|
* @see defineConstants
|
|
|
|
|
*/
|
2014-07-04 15:44:41 +02:00
|
|
|
|
Story.getModes = defineConstants(Story, markgettext('hidden'),
|
|
|
|
|
markgettext('featured'));
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
|
|
|
|
* @function
|
|
|
|
|
* @returns {String[]}
|
|
|
|
|
* @see defineConstants
|
|
|
|
|
*/
|
2014-07-04 15:44:41 +02:00
|
|
|
|
Story.getCommentModes = defineConstants(Story, markgettext('closed'),
|
|
|
|
|
/* markgettext('readonly'), markgettext('moderated'), */
|
|
|
|
|
markgettext('open'));
|
2007-10-11 23:03:17 +00:00
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
|
|
|
|
* @name Story
|
|
|
|
|
* @constructor
|
|
|
|
|
* @property {Comment[]} _children
|
|
|
|
|
* @property {String} commentMode
|
|
|
|
|
* @property {Comment[]} comments
|
|
|
|
|
* @property {Date} created
|
|
|
|
|
* @property {User} creator
|
|
|
|
|
* @property {Metadata} metadata
|
|
|
|
|
* @property {String} mode
|
|
|
|
|
* @property {Date} modified
|
|
|
|
|
* @property {User} modifier
|
|
|
|
|
* @property {String} name
|
|
|
|
|
* @property {Number} parent_id
|
|
|
|
|
* @property {String} parent_type
|
|
|
|
|
* @property {String} prototype
|
|
|
|
|
* @property {Number} requests
|
|
|
|
|
* @property {Site} site
|
|
|
|
|
* @property {String} status
|
|
|
|
|
* @property {TagHub[]} tags
|
|
|
|
|
* @property {String} text
|
|
|
|
|
* @property {String} title
|
|
|
|
|
* @extends HopObject
|
|
|
|
|
*/
|
2007-09-27 17:39:02 +00:00
|
|
|
|
Story.prototype.constructor = function() {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
HopObject.confirmConstructor(this);
|
|
|
|
|
return this;
|
2008-04-21 13:57:01 +00:00
|
|
|
|
}
|
2007-08-03 19:41:51 +00:00
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
2014-07-04 15:10:47 +02:00
|
|
|
|
*
|
2009-11-02 16:16:41 +00:00
|
|
|
|
* @param {String} action
|
|
|
|
|
* @returns {Boolean}
|
|
|
|
|
*/
|
2007-09-27 17:39:02 +00:00
|
|
|
|
Story.prototype.getPermission = function(action) {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
if (!this.site.getPermission('main')) {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
switch (action) {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
case '.':
|
|
|
|
|
case 'main':
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return this.status !== Story.CLOSED ||
|
|
|
|
|
this.creator === session.user ||
|
|
|
|
|
Membership.require(Membership.MANAGER) ||
|
|
|
|
|
User.require(User.PRIVILEGED);
|
2014-07-04 15:44:41 +02:00
|
|
|
|
case 'comment':
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return this.site.commentMode === Site.ENABLED &&
|
|
|
|
|
(this.commentMode === Story.OPEN ||
|
|
|
|
|
this.commentMode === Story.MODERATED);
|
2014-07-04 15:44:41 +02:00
|
|
|
|
case 'delete':
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return this.creator === session.user ||
|
|
|
|
|
Membership.require(Membership.MANAGER) ||
|
|
|
|
|
User.require(User.PRIVILEGED);
|
2014-07-04 15:44:41 +02:00
|
|
|
|
case 'edit':
|
|
|
|
|
case 'rotate':
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return this.creator === session.user ||
|
|
|
|
|
Membership.require(Membership.MANAGER) ||
|
|
|
|
|
(this.status === Story.SHARED &&
|
|
|
|
|
Membership.require(Membership.CONTRIBUTOR)) ||
|
|
|
|
|
(this.status === Story.OPEN &&
|
|
|
|
|
Membership.require(Membership.SUBSCRIBER)) ||
|
|
|
|
|
User.require(User.PRIVILEGED);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
2008-03-17 19:41:10 +00:00
|
|
|
|
}
|
|
|
|
|
|
2007-06-23 14:53:39 +00:00
|
|
|
|
Story.prototype.main_action = function() {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
res.data.title = this.getTitle(10);
|
2014-07-04 15:44:41 +02:00
|
|
|
|
res.data.body = this.renderSkinAsString('Story#main');
|
|
|
|
|
this.site.renderSkin('Site#page');
|
2014-07-04 15:32:18 +02:00
|
|
|
|
this.site.log();
|
|
|
|
|
this.count();
|
|
|
|
|
this.log();
|
|
|
|
|
return;
|
2008-04-21 13:57:01 +00:00
|
|
|
|
}
|
2007-06-23 14:53:39 +00:00
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
2014-07-04 15:10:47 +02:00
|
|
|
|
*
|
2009-11-02 16:16:41 +00:00
|
|
|
|
* @param {Number} limit
|
|
|
|
|
* @returns {String}
|
|
|
|
|
*/
|
2007-09-30 23:59:13 +00:00
|
|
|
|
Story.prototype.getTitle = function(limit) {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
var key = this + ':title:' + limit;
|
2014-07-04 15:32:18 +02:00
|
|
|
|
if (!res.meta[key]) {
|
|
|
|
|
if (this.title) {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
res.meta[key] = stripTags(this.title).clip(limit, '...', '\\s');
|
2014-07-04 15:32:18 +02:00
|
|
|
|
} else if (this.text) {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
var parts = stripTags(this.text).embody(limit, '...', '\\s');
|
2014-07-04 15:32:18 +02:00
|
|
|
|
res.meta[key] = parts.head;
|
2014-07-04 15:44:41 +02:00
|
|
|
|
res.meta[this + ':text:' + limit] = parts.tail;
|
2014-07-04 15:32:18 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2014-07-04 15:44:41 +02:00
|
|
|
|
return String(res.meta[key]) || '...';
|
2008-04-21 13:57:01 +00:00
|
|
|
|
}
|
2007-09-27 17:39:02 +00:00
|
|
|
|
|
2007-06-23 14:53:39 +00:00
|
|
|
|
Story.prototype.edit_action = function() {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
if (req.postParams.save) {
|
|
|
|
|
try {
|
|
|
|
|
this.update(req.postParams);
|
|
|
|
|
delete session.data.backup;
|
2014-07-04 15:44:41 +02:00
|
|
|
|
res.message = gettext('The story was successfully updated.');
|
2014-07-04 15:32:18 +02:00
|
|
|
|
res.redirect(this.href());
|
|
|
|
|
} catch (ex) {
|
|
|
|
|
res.message = ex;
|
|
|
|
|
app.log(ex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.data.action = this.href(req.action);
|
|
|
|
|
res.data.title = gettext('Edit Story');
|
2014-07-04 15:44:41 +02:00
|
|
|
|
res.data.body = this.renderSkinAsString('Story#edit');
|
|
|
|
|
this.site.renderSkin('Site#page');
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return;
|
2008-04-21 13:57:01 +00:00
|
|
|
|
}
|
2007-06-23 14:53:39 +00:00
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
2014-07-04 15:10:47 +02:00
|
|
|
|
*
|
2009-11-02 16:16:41 +00:00
|
|
|
|
* @param {Object} data
|
|
|
|
|
*/
|
2007-09-27 17:39:02 +00:00
|
|
|
|
Story.prototype.update = function(data) {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
var site = this.site || res.handlers.site;
|
|
|
|
|
|
|
|
|
|
if (!data.title && !data.text) {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
throw Error(gettext('Please enter at least something into the “title” or “text” field.'));
|
2014-07-04 15:32:18 +02:00
|
|
|
|
}
|
|
|
|
|
if (data.created) {
|
|
|
|
|
try {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
this.created = data.created.toDate('yyyy-MM-dd HH:mm', site.getTimeZone());
|
2014-07-04 15:32:18 +02:00
|
|
|
|
} catch (ex) {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
throw Error(gettext('Cannot parse timestamp {0} as a date.', data.created));
|
2014-07-04 15:32:18 +02:00
|
|
|
|
app.log(ex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get difference to current content before applying changes
|
|
|
|
|
var delta = this.getDelta(data);
|
|
|
|
|
this.title = data.title ? data.title.trim() : String.EMPTY;
|
|
|
|
|
this.text = data.text ? data.text.trim() : String.EMPTY;
|
|
|
|
|
this.status = data.status || Story.PUBLIC;
|
|
|
|
|
this.mode = data.mode || Story.FEATURED;
|
|
|
|
|
this.commentMode = data.commentMode || Story.OPEN;
|
|
|
|
|
this.setCustomContent(data);
|
|
|
|
|
|
|
|
|
|
// FIXME: To be removed resp. moved to Stories.create_action and
|
|
|
|
|
// Story.edit_action if work-around for Helma bug #607 fails
|
|
|
|
|
// We need persistence for setting the tags
|
|
|
|
|
this.isTransient() && this.persist();
|
|
|
|
|
this.setTags(data.tags || data.tag_array);
|
|
|
|
|
|
|
|
|
|
if (delta > 50) {
|
|
|
|
|
this.modified = new Date;
|
|
|
|
|
if (this.status !== Story.CLOSED) {
|
|
|
|
|
site.modified = this.modified;
|
|
|
|
|
}
|
|
|
|
|
site.callback(this);
|
|
|
|
|
// Notification is sent in Stories.create_action()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.clearCache();
|
|
|
|
|
this.modifier = session.user;
|
|
|
|
|
return;
|
2008-04-21 13:57:01 +00:00
|
|
|
|
}
|
2007-06-23 14:53:39 +00:00
|
|
|
|
|
2007-09-29 00:58:04 +00:00
|
|
|
|
Story.prototype.rotate_action = function() {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
if (this.status === Story.CLOSED) {
|
|
|
|
|
this.status = this.cache.status || Story.PUBLIC;
|
|
|
|
|
} else if (this.mode === Story.FEATURED) {
|
|
|
|
|
this.mode = Story.HIDDEN;
|
|
|
|
|
} else {
|
|
|
|
|
this.cache.status = this.status;
|
|
|
|
|
this.mode = Story.FEATURED;
|
|
|
|
|
this.status = Story.CLOSED;
|
|
|
|
|
}
|
|
|
|
|
return res.redirect(req.data.http_referer || this._parent.href());
|
2008-04-21 13:57:01 +00:00
|
|
|
|
}
|
2007-09-29 00:58:04 +00:00
|
|
|
|
|
2007-06-23 14:53:39 +00:00
|
|
|
|
Story.prototype.comment_action = function() {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
// Check if user is logged in since we allow linking here for any user
|
|
|
|
|
if (!User.require(User.REGULAR)) {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
User.setLocation(this.href(req.action) + '#form');
|
|
|
|
|
res.message = gettext('Please login first.');
|
|
|
|
|
res.redirect(this.site.members.href('login'));
|
2014-07-04 15:32:18 +02:00
|
|
|
|
}
|
|
|
|
|
if (req.postParams.save) {
|
|
|
|
|
try {
|
|
|
|
|
var comment = Comment.add(req.postParams, this);
|
|
|
|
|
comment.notify(req.action);
|
|
|
|
|
delete session.data.backup;
|
2014-07-04 15:44:41 +02:00
|
|
|
|
res.message = gettext('The comment was successfully created.');
|
2014-07-04 15:32:18 +02:00
|
|
|
|
res.redirect(comment.href());
|
|
|
|
|
} catch (ex) {
|
|
|
|
|
res.message = ex;
|
|
|
|
|
app.log(ex);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
res.handlers.parent = this;
|
|
|
|
|
res.data.action = this.href(req.action);
|
2014-07-04 15:44:41 +02:00
|
|
|
|
res.data.title = gettext('Add Comment');
|
2014-07-04 15:32:18 +02:00
|
|
|
|
HopObject.confirmConstructor(Comment);
|
2014-07-04 15:44:41 +02:00
|
|
|
|
res.data.body = (new Comment).renderSkinAsString('Comment#edit');
|
|
|
|
|
this.site.renderSkin('Site#page');
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return;
|
2008-04-21 13:57:01 +00:00
|
|
|
|
}
|
2007-09-26 22:22:37 +00:00
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
2014-07-04 15:10:47 +02:00
|
|
|
|
*
|
2009-11-02 16:16:41 +00:00
|
|
|
|
* @param {String} name
|
|
|
|
|
* @returns {Object}
|
|
|
|
|
*/
|
2008-05-15 12:58:26 +00:00
|
|
|
|
Story.prototype.getFormValue = function(name) {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
if (req.isPost()) {
|
|
|
|
|
return req.postParams[name];
|
|
|
|
|
}
|
|
|
|
|
switch (name) {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
case 'commentMode':
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return this.commentMode || Story.OPEN;
|
2014-07-04 15:44:41 +02:00
|
|
|
|
case 'mode':
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return this.mode || Story.FEATURED;
|
2014-07-04 15:44:41 +02:00
|
|
|
|
case 'status':
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return this.status || Story.PUBLIC;
|
2014-07-04 15:44:41 +02:00
|
|
|
|
case 'tags':
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return this.getTags().join(Tag.DELIMITER);
|
|
|
|
|
}
|
|
|
|
|
return this[name] || this.getMetadata(name);
|
2008-05-15 12:58:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
2014-07-04 15:10:47 +02:00
|
|
|
|
*
|
2009-11-02 16:16:41 +00:00
|
|
|
|
* @param {String} name
|
|
|
|
|
* @returns {String[]}
|
|
|
|
|
*/
|
2008-05-15 12:58:26 +00:00
|
|
|
|
Story.prototype.getFormOptions = function(name) {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
switch (name) {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
case 'commentMode':
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return Story.getCommentModes();
|
2014-07-04 15:44:41 +02:00
|
|
|
|
case 'mode':
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return Story.getModes();
|
2014-07-04 15:44:41 +02:00
|
|
|
|
case 'status':
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return Story.getStatus();
|
2014-07-04 15:44:41 +02:00
|
|
|
|
case 'tags':
|
2014-07-04 15:32:18 +02:00
|
|
|
|
// FIXME: This could become a huge select element...
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
return;
|
2008-05-15 12:58:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
2011-02-10 23:35:00 +00:00
|
|
|
|
*
|
2009-11-02 16:16:41 +00:00
|
|
|
|
* @param {Object} data
|
|
|
|
|
*/
|
2011-05-15 14:42:54 +00:00
|
|
|
|
Story.prototype.setCustomContent = function(data) {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
var metadata = {};
|
|
|
|
|
for (var key in data) {
|
|
|
|
|
if (this.isCustomContent(key)) {
|
|
|
|
|
metadata[key] = data[key];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return HopObject.prototype.setMetadata.call(this, metadata);
|
2008-05-15 12:58:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
2014-07-04 15:10:47 +02:00
|
|
|
|
*
|
2009-11-02 16:16:41 +00:00
|
|
|
|
* @param {String} name
|
|
|
|
|
*/
|
2011-05-15 14:42:54 +00:00
|
|
|
|
Story.prototype.isCustomContent = function(key) {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return this[key] === undefined && key !== 'save';
|
2008-05-15 12:58:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
|
|
|
|
* Increment the request counter
|
|
|
|
|
*/
|
2008-05-15 12:58:26 +00:00
|
|
|
|
Story.prototype.count = function() {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
if (session.user === this.creator) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
var story;
|
2014-07-04 15:44:41 +02:00
|
|
|
|
var key = 'Story#' + this._id;
|
2014-07-04 15:32:18 +02:00
|
|
|
|
if (story = app.data.requests[key]) {
|
|
|
|
|
story.requests += 1;
|
|
|
|
|
} else {
|
|
|
|
|
app.data.requests[key] = {
|
|
|
|
|
type: this.constructor,
|
|
|
|
|
id: this._id,
|
|
|
|
|
requests: this.requests + 1
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return;
|
2008-05-15 12:58:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
|
|
|
|
* Calculate the difference of a story’s current and its updated content
|
|
|
|
|
* @param {Object} data
|
|
|
|
|
* @returns {Number}
|
|
|
|
|
*/
|
2008-05-15 12:58:26 +00:00
|
|
|
|
Story.prototype.getDelta = function(data) {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
if (this.isTransient()) {
|
|
|
|
|
return Infinity;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var deltify = function(s1, s2) {
|
|
|
|
|
var len1 = s1 ? String(s1).length : 0;
|
|
|
|
|
var len2 = s2 ? String(s2).length : 0;
|
|
|
|
|
return Math.abs(len1 - len2);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var delta = 0;
|
|
|
|
|
delta += deltify(data.title, this.title);
|
|
|
|
|
delta += deltify(data.text, this.text);
|
|
|
|
|
for (var key in data) {
|
|
|
|
|
if (this.isCustomContent(key)) {
|
|
|
|
|
delta += deltify(data[key], this.getMetadata(key))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// In-between updates (1 hour) get zero delta
|
|
|
|
|
var timex = (new Date - this.modified) > Date.ONEHOUR ? 1 : 0;
|
|
|
|
|
return delta * timex;
|
2008-05-15 12:58:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
2014-07-04 15:10:47 +02:00
|
|
|
|
*
|
2009-11-02 16:16:41 +00:00
|
|
|
|
* @param {String} name
|
|
|
|
|
* @returns {HopObject}
|
|
|
|
|
*/
|
2008-05-15 12:58:26 +00:00
|
|
|
|
Story.prototype.getMacroHandler = function(name) {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
if (name === 'metadata') {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return this.getMetadata();
|
|
|
|
|
}
|
|
|
|
|
return null;
|
2008-05-15 12:58:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
2014-07-04 15:10:47 +02:00
|
|
|
|
*
|
2009-11-02 16:16:41 +00:00
|
|
|
|
* @param {Object} param
|
|
|
|
|
* @param {String} action
|
|
|
|
|
* @param {String} text
|
|
|
|
|
*/
|
2008-05-15 12:58:26 +00:00
|
|
|
|
Story.prototype.link_macro = function(param, action, text) {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
switch (action) {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
case 'rotate':
|
2014-07-04 15:32:18 +02:00
|
|
|
|
if (this.status === Story.CLOSED) {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
text = gettext('Publish');
|
2014-07-04 15:32:18 +02:00
|
|
|
|
} else if (this.mode === Story.FEATURED) {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
text = gettext('Hide');
|
2014-07-04 15:32:18 +02:00
|
|
|
|
} else {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
text = gettext('Close');
|
2014-07-04 15:32:18 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return HopObject.prototype.link_macro.call(this, param, action, text);
|
2008-05-15 12:58:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
2014-07-04 15:10:47 +02:00
|
|
|
|
*
|
2009-11-02 16:16:41 +00:00
|
|
|
|
* @param {Object} param
|
|
|
|
|
*/
|
2007-09-30 23:59:13 +00:00
|
|
|
|
Story.prototype.summary_macro = function(param) {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
param.limit || (param.limit = 15);
|
|
|
|
|
var keys, summary;
|
|
|
|
|
if (arguments.length > 1) {
|
|
|
|
|
res.push();
|
|
|
|
|
var content;
|
|
|
|
|
for (var i=1; i<arguments.length; i+=1) {
|
|
|
|
|
if (content = this.getMetadata(arguments[i])) {
|
|
|
|
|
res.write(content);
|
|
|
|
|
res.write(String.SPACE);
|
2014-07-04 15:10:47 +02:00
|
|
|
|
}
|
2014-07-04 15:32:18 +02:00
|
|
|
|
}
|
|
|
|
|
summary = res.pop();
|
|
|
|
|
}
|
|
|
|
|
if (!summary) {
|
|
|
|
|
summary = (this.title || String.EMPTY) + String.SPACE +
|
|
|
|
|
(this.text || String.EMPTY);
|
|
|
|
|
}
|
2014-07-04 15:44:41 +02:00
|
|
|
|
var clipped = stripTags(summary).clip(param.limit, param.clipping, '\\s');
|
2014-07-04 15:32:18 +02:00
|
|
|
|
var head = clipped.split(/(\s)/, param.limit * 0.6).join(String.EMPTY);
|
|
|
|
|
var tail = clipped.substring(head.length).trim();
|
|
|
|
|
head = head.trim();
|
|
|
|
|
if (!head && !tail) {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
head = '...';
|
2014-07-04 15:32:18 +02:00
|
|
|
|
}
|
|
|
|
|
html.link({href: this.href()}, head);
|
2014-07-04 15:44:41 +02:00
|
|
|
|
res.writeln('\n');
|
2014-07-04 15:32:18 +02:00
|
|
|
|
res.write(tail);
|
|
|
|
|
return;
|
2008-04-21 13:57:01 +00:00
|
|
|
|
}
|
2007-09-30 23:59:13 +00:00
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
2014-07-04 15:10:47 +02:00
|
|
|
|
*
|
2009-11-02 16:16:41 +00:00
|
|
|
|
* @param {Object} param
|
|
|
|
|
* @param {String} mode
|
|
|
|
|
*/
|
2007-09-29 00:58:04 +00:00
|
|
|
|
Story.prototype.comments_macro = function(param, mode) {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
var story = this.story || this;
|
|
|
|
|
if (story.site.commentMode === Site.DISABLED ||
|
|
|
|
|
story.commentMode === Site.CLOSED) {
|
|
|
|
|
return;
|
|
|
|
|
} else if (mode) {
|
|
|
|
|
var n = this.comments.size() || 0;
|
2014-07-04 15:44:41 +02:00
|
|
|
|
var text = ngettext('{0} comment', '{0} comments', n);
|
|
|
|
|
if (mode === 'count' || mode === 'size') {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
res.write(text);
|
2014-07-04 15:44:41 +02:00
|
|
|
|
} else if (mode === 'link') {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
n < 1 ? res.write(text) :
|
2014-07-04 15:44:41 +02:00
|
|
|
|
html.link({href: this.href() + '#comments'}, text);
|
2014-07-04 15:32:18 +02:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
this.prefetchChildren();
|
|
|
|
|
this.forEach(function() {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
html.openTag('a', {name: this._id});
|
|
|
|
|
html.closeTag('a');
|
2014-07-04 15:32:18 +02:00
|
|
|
|
this.renderSkin(this.parent.constructor === Story ?
|
2014-07-04 15:44:41 +02:00
|
|
|
|
'Comment#main' : 'Comment#reply');
|
2014-07-04 15:32:18 +02:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return;
|
2008-04-21 13:57:01 +00:00
|
|
|
|
}
|
2007-06-23 14:53:39 +00:00
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
2014-07-04 15:10:47 +02:00
|
|
|
|
*
|
2009-11-02 16:16:41 +00:00
|
|
|
|
* @param {Object} param
|
|
|
|
|
* @param {String} mode
|
|
|
|
|
*/
|
|
|
|
|
Story.prototype.tags_macro = function(param, mode) {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
if (mode === 'link') {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
var tags = [];
|
|
|
|
|
this.tags.list().forEach(function(item) {
|
|
|
|
|
res.push();
|
|
|
|
|
if (item.tag) {
|
|
|
|
|
renderLink(param, item.tag.href(), item.tag.name);
|
|
|
|
|
tags.push(res.pop());
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return res.write(tags.join(Tag.DELIMITER));
|
|
|
|
|
}
|
2014-07-04 15:44:41 +02:00
|
|
|
|
return res.write(this.getFormValue('tags'));
|
2008-04-21 13:57:01 +00:00
|
|
|
|
}
|
2007-06-23 14:53:39 +00:00
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
2014-07-04 15:10:47 +02:00
|
|
|
|
*
|
2009-11-02 16:16:41 +00:00
|
|
|
|
* @param {Object} param
|
|
|
|
|
* @param {Number} limit
|
|
|
|
|
*/
|
2008-05-15 12:58:26 +00:00
|
|
|
|
Story.prototype.referrers_macro = function(param, limit) {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
if (!User.require(User.PRIVILEGED) &&
|
|
|
|
|
!Membership.require(Membership.OWNER)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
limit = Math.min(limit || param.limit || 100, 100);
|
|
|
|
|
if (limit < 1) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var self = this;
|
|
|
|
|
var sql = new Sql;
|
2014-07-04 15:44:41 +02:00
|
|
|
|
sql.retrieve(Sql.REFERRERS, 'Story', this._id);
|
2014-07-04 15:32:18 +02:00
|
|
|
|
|
|
|
|
|
res.push();
|
|
|
|
|
var n = 0;
|
|
|
|
|
sql.traverse(function() {
|
|
|
|
|
if (n < limit && this.requests && this.referrer) {
|
|
|
|
|
this.text = encode(this.referrer.head(50));
|
|
|
|
|
this.referrer = encode(this.referrer);
|
2014-07-04 15:44:41 +02:00
|
|
|
|
self.renderSkin('$Story#referrer', this);
|
2014-07-04 15:32:18 +02:00
|
|
|
|
}
|
|
|
|
|
n += 1;
|
|
|
|
|
});
|
|
|
|
|
param.referrers = res.pop();
|
|
|
|
|
if (param.referrers) {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
this.renderSkin('$Story#referrers', param);
|
2014-07-04 15:32:18 +02:00
|
|
|
|
}
|
|
|
|
|
return;
|
2008-04-21 13:57:01 +00:00
|
|
|
|
}
|
2008-03-17 19:41:10 +00:00
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
2014-07-04 15:10:47 +02:00
|
|
|
|
*
|
2009-11-02 16:16:41 +00:00
|
|
|
|
* @param {Object} value
|
|
|
|
|
* @param {Object} param
|
|
|
|
|
* @param {String} mode
|
|
|
|
|
* @returns {String}
|
|
|
|
|
*/
|
2007-09-30 23:59:13 +00:00
|
|
|
|
Story.prototype.format_filter = function(value, param, mode) {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
if (value) {
|
|
|
|
|
switch (mode) {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
case 'plain':
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return this.url_filter(stripTags(value), param, mode);
|
|
|
|
|
|
2014-07-04 15:44:41 +02:00
|
|
|
|
case 'quotes':
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return stripTags(value).replace(/(?:\x22|\x27)/g, function(quote) {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
return '&#' + quote.charCodeAt(0) + ';';
|
2014-07-04 15:32:18 +02:00
|
|
|
|
});
|
|
|
|
|
|
2014-07-04 15:44:41 +02:00
|
|
|
|
case 'image':
|
|
|
|
|
var image = HopObject.getFromPath(value, 'images');
|
2014-07-04 15:32:18 +02:00
|
|
|
|
if (image) {
|
|
|
|
|
res.push();
|
|
|
|
|
image.render_macro(param);
|
|
|
|
|
return res.pop();
|
2007-09-30 23:59:13 +00:00
|
|
|
|
}
|
2014-07-04 15:32:18 +02:00
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
value = this.macro_filter(format(value), param);
|
|
|
|
|
return this.url_filter(value, param);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return String.EMTPY;
|
2008-04-21 13:57:01 +00:00
|
|
|
|
}
|
2007-09-30 23:59:13 +00:00
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
2013-04-06 19:35:18 +00:00
|
|
|
|
* Enables certain macros for being used in a story or comment – thus, any content object.
|
|
|
|
|
* @param {String|Skin} value A skin object or a string that is going to be turned into a skin object.
|
2009-11-02 16:16:41 +00:00
|
|
|
|
* @returns {String}
|
|
|
|
|
*/
|
2013-04-06 19:35:18 +00:00
|
|
|
|
Story.prototype.macro_filter = function(value) {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
var skin = value.constructor === String ? createSkin(value) : value;
|
|
|
|
|
|
|
|
|
|
Story.ALLOWED_MACROS.forEach(function(value, index) {
|
|
|
|
|
skin.allowMacro(value);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
var site;
|
|
|
|
|
if (this.site !== res.handlers.site) {
|
|
|
|
|
site = res.handlers.site;
|
|
|
|
|
res.handlers.site = this.site;
|
|
|
|
|
}
|
|
|
|
|
value = this.renderSkinAsString(skin);
|
|
|
|
|
site && (res.handlers.site = site);
|
|
|
|
|
return value;
|
2008-04-21 13:57:01 +00:00
|
|
|
|
}
|
2007-09-30 23:59:13 +00:00
|
|
|
|
|
2009-11-02 16:16:41 +00:00
|
|
|
|
/**
|
2014-07-04 15:10:47 +02:00
|
|
|
|
*
|
2009-11-02 16:16:41 +00:00
|
|
|
|
* @param {String} value
|
|
|
|
|
* @param {Object} param
|
|
|
|
|
* @param {String} mode
|
|
|
|
|
* @returns {String}
|
|
|
|
|
*/
|
2007-09-30 23:59:13 +00:00
|
|
|
|
Story.prototype.url_filter = function(value, param, mode) {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
param.limit || (param.limit = 50);
|
|
|
|
|
// FIXME: The first RegExp has troubles with <a href=http://... (no quotes)
|
|
|
|
|
//var re = /(^|\/>|\s+)([\w+-_]+:\/\/[^\s]+?)([\.,;:\)\]\"]?)(?=[\s<]|$)/gim;
|
|
|
|
|
var re = /(^|\/>|\s+)([!fhtpsr]+:\/\/[^\s]+?)([\.,;:\)\]\"]?)(?=[\s<]|$)/gim
|
|
|
|
|
return value.replace(re, function(str, head, url, tail) {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
if (url.startsWith('!')) {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return head + url.substring(1) + tail;
|
|
|
|
|
}
|
|
|
|
|
res.push();
|
|
|
|
|
res.write(head);
|
2014-07-04 15:44:41 +02:00
|
|
|
|
if (mode === 'plain') {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
res.write(url.clip(param.limit));
|
|
|
|
|
} else {
|
|
|
|
|
var text, location = /:\/\/([^\/]*)/.exec(url)[1];
|
|
|
|
|
text = location;
|
2014-07-04 15:44:41 +02:00
|
|
|
|
if (mode === 'extended') {
|
|
|
|
|
text = url.replace(/^.+\/([^\/]*)$/, '$1');
|
2008-05-13 14:48:54 +00:00
|
|
|
|
}
|
2014-07-04 15:32:18 +02:00
|
|
|
|
html.link({href: url, title: url}, text.clip(param.limit));
|
2014-07-04 15:44:41 +02:00
|
|
|
|
if (mode === 'extended' && text !== location) {
|
|
|
|
|
res.write(' <small>(' + location + ')</small>');
|
2007-09-30 23:59:13 +00:00
|
|
|
|
}
|
2014-07-04 15:32:18 +02:00
|
|
|
|
}
|
|
|
|
|
res.write(tail);
|
|
|
|
|
return res.pop();
|
|
|
|
|
});
|
2008-04-21 13:57:01 +00:00
|
|
|
|
}
|
2010-02-06 15:13:39 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @returns {String}
|
|
|
|
|
*/
|
|
|
|
|
Story.prototype.getConfirmText = function() {
|
2014-07-04 15:44:41 +02:00
|
|
|
|
return gettext('You are about to delete a story by user {0}.',
|
2014-07-04 15:32:18 +02:00
|
|
|
|
this.creator.name);
|
2010-02-06 15:13:39 +00:00
|
|
|
|
}
|