2007-06-23 14:53:39 +00:00
|
|
|
|
// The Antville Project
|
|
|
|
|
// http://code.google.com/p/antville
|
|
|
|
|
//
|
2014-07-04 17:16:51 +02:00
|
|
|
|
// Copyright 2001–2014 by the Workers of Antville.
|
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.
|
|
|
|
|
|
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-12-21 17:20:22 +01:00
|
|
|
|
|
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-12-21 17:20:22 +01:00
|
|
|
|
|
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-12-21 17:20:22 +01:00
|
|
|
|
|
2014-07-04 15:44:41 +02:00
|
|
|
|
case 'edit':
|
2014-12-16 23:07:59 +01:00
|
|
|
|
case 'mode':
|
|
|
|
|
case 'rotate': // FIXME: Action moved to compat layer
|
|
|
|
|
case 'status':
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return this.creator === session.user ||
|
|
|
|
|
Membership.require(Membership.MANAGER) ||
|
2014-12-21 17:20:22 +01:00
|
|
|
|
(this.status === Story.SHARED && Membership.require(Membership.CONTRIBUTOR)) ||
|
|
|
|
|
(this.status === Story.OPEN && Membership.require(Membership.SUBSCRIBER)) ||
|
2014-07-04 15:32:18 +02:00
|
|
|
|
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) {
|
2015-01-21 14:23:22 +01:00
|
|
|
|
res.meta[key] = stripTags(this.title).clip(limit, String.ELLIPSIS, '\\s');
|
2014-07-04 15:32:18 +02:00
|
|
|
|
} else if (this.text) {
|
2015-01-21 14:23:22 +01:00
|
|
|
|
var parts = stripTags(this.text).embody(limit, String.ELLIPSIS, '\\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
|
|
|
|
}
|
|
|
|
|
}
|
2015-01-21 14:23:22 +01:00
|
|
|
|
return String(res.meta[key]) || String.ELLIPSIS;
|
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.');
|
2015-01-01 20:32:22 +01:00
|
|
|
|
res.redirect(this.href());
|
2014-07-04 15:32:18 +02:00
|
|
|
|
} 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);
|
2014-12-17 23:59:32 +01:00
|
|
|
|
this.title = data.title ? stripTags(data.title.trim()) : String.EMPTY;
|
2014-07-04 15:32:18 +02:00
|
|
|
|
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
|
|
|
|
|
2014-12-16 23:07:59 +01:00
|
|
|
|
Story.prototype.status_action = function () {
|
|
|
|
|
this.status = (this.status === Story.CLOSED ? Story.PUBLIC : Story.CLOSED);
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return res.redirect(req.data.http_referer || this._parent.href());
|
2014-12-16 23:07:59 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Story.prototype.mode_action = function () {
|
|
|
|
|
this.mode = (this.mode === Story.HIDDEN ? Story.FEATURED : Story.HIDDEN);
|
|
|
|
|
return res.redirect(req.data.http_referer || this._parent.href());
|
|
|
|
|
};
|
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);
|
|
|
|
|
}
|
2015-01-04 19:21:13 +01:00
|
|
|
|
return this[name] || this.getMetadata(name) || req.queryParams[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();
|
2015-01-01 20:41:28 +01:00
|
|
|
|
} else if (name === 'site') {
|
|
|
|
|
return this.site;
|
2014-07-04 15:32:18 +02:00
|
|
|
|
}
|
|
|
|
|
return null;
|
2008-05-15 12:58:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-02-06 11:11:08 +01:00
|
|
|
|
Story.prototype.getAbstract = function (param) {
|
2015-01-24 20:25:36 +01:00
|
|
|
|
param || (param = {});
|
2015-02-06 10:49:26 +01:00
|
|
|
|
var limit = param.limit || 10;
|
2015-02-06 11:11:08 +01:00
|
|
|
|
var ratio = 0.5; // Use title and text equivalently
|
2015-01-23 18:46:00 +01:00
|
|
|
|
var result = [], raw = [];
|
2015-01-21 14:23:22 +01:00
|
|
|
|
raw.push(this.title, this.text);
|
2015-02-06 11:11:08 +01:00
|
|
|
|
var titleLimit = Math[ratio >= 0.5 ? 'ceil' : 'floor'](limit * ratio);
|
2015-02-06 10:49:26 +01:00
|
|
|
|
var title = this.title && stripTags(this.title).clip(titleLimit, null, '\\s');
|
|
|
|
|
var titleLength = title ? title.split(/\s/).length : 0;
|
|
|
|
|
if (titleLength < titleLimit) {
|
2015-02-06 11:11:08 +01:00
|
|
|
|
ratio = titleLength / limit;
|
2015-02-06 10:49:26 +01:00
|
|
|
|
}
|
2015-02-06 11:11:08 +01:00
|
|
|
|
var textLimit = Math[ratio < 0.5 ? 'ceil' : 'floor'](limit * (1 - ratio));
|
2015-02-06 10:49:26 +01:00
|
|
|
|
var text = this.text && stripTags(this.text).clip(textLimit, null, '\\s');
|
2015-01-23 18:46:00 +01:00
|
|
|
|
title && result.push('<b>' + title + '</b> ');
|
|
|
|
|
text && result.push(text);
|
2015-02-06 11:11:08 +01:00
|
|
|
|
var contentArgs = Array.prototype.slice.call(arguments, 1); // Remove first argument (param)
|
|
|
|
|
if (result.length < 1 && contentArgs.length) {
|
|
|
|
|
ratio = 1 / contentArgs.length;
|
|
|
|
|
for (var i = 0, buffer, key = contentArgs[i]; i < contentArgs.length; i += 1) {
|
|
|
|
|
if (key && (buffer = this.getMetadata(key))) {
|
2015-01-21 14:23:22 +01:00
|
|
|
|
raw.push(buffer);
|
2015-02-06 11:11:08 +01:00
|
|
|
|
buffer = stripTags(buffer).clip(limit * ratio, null, '\\s');
|
2015-01-23 18:46:00 +01:00
|
|
|
|
buffer && result.push(buffer);
|
2014-07-04 15:10:47 +02:00
|
|
|
|
}
|
2014-07-04 15:32:18 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2015-01-30 17:37:55 +01:00
|
|
|
|
if (result.length < 1 && param['default'] === null) {
|
2015-01-21 14:23:22 +01:00
|
|
|
|
return '<i>' + ngettext('{0} character', '{0} characters', raw.join(String.EMPTY).length) + '</i>';
|
|
|
|
|
}
|
2015-01-23 18:46:00 +01:00
|
|
|
|
return result.join(String.SPACE);
|
2015-01-05 22:59:57 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} param
|
|
|
|
|
*/
|
2015-02-06 11:11:08 +01:00
|
|
|
|
Story.prototype.abstract_macro = function(param) {
|
|
|
|
|
return res.write(this.getAbstract.call(this, param));
|
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;
|
2014-12-16 23:07:59 +01:00
|
|
|
|
var comments = this.story ? this : this.comments;
|
2014-12-21 17:20:22 +01:00
|
|
|
|
if (story.isTransient() || story.site.commentMode === Site.DISABLED || story.commentMode === Site.CLOSED) {
|
2014-07-04 15:32:18 +02:00
|
|
|
|
return;
|
|
|
|
|
} else if (mode) {
|
2014-12-16 23:07:59 +01:00
|
|
|
|
var n = comments.size() || 0;
|
2014-07-04 15:44:41 +02:00
|
|
|
|
if (mode === 'count' || mode === 'size') {
|
2014-12-16 23:07:59 +01:00
|
|
|
|
res.write(n);
|
2014-12-17 23:59:54 +01:00
|
|
|
|
} else if (mode === 'link' || mode === 'summary') {
|
2014-12-16 23:07:59 +01:00
|
|
|
|
var text = ngettext('{0} comment', '{0} comments', n);
|
2014-12-17 23:59:54 +01:00
|
|
|
|
if (n < 1 || mode === 'summary') {
|
|
|
|
|
res.write(text);
|
|
|
|
|
} else {
|
|
|
|
|
html.link({href: this.href() + '#comments'}, text);
|
|
|
|
|
}
|
2014-07-04 15:32:18 +02:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
this.prefetchChildren();
|
|
|
|
|
this.forEach(function() {
|
2015-01-01 21:35:49 +01:00
|
|
|
|
// FIXME: This interferes with (UIKit comment) lists because the <a> element is
|
|
|
|
|
// between <ul> and <li> elements. Must be added to the skin from now on…?
|
|
|
|
|
//html.openTag('a', {name: this._id});
|
|
|
|
|
//html.closeTag('a');
|
|
|
|
|
this.renderSkin(this.parent.constructor === Story ? '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-12-16 23:07:59 +01:00
|
|
|
|
} else if (mode === 'count') {
|
|
|
|
|
return this.tags.count();
|
2014-07-04 15:32:18 +02:00
|
|
|
|
}
|
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);
|
2015-01-09 21:02:46 +01:00
|
|
|
|
self.site.renderSkin('$Site#referrer', this);
|
2014-07-04 15:32:18 +02:00
|
|
|
|
}
|
|
|
|
|
n += 1;
|
|
|
|
|
});
|
2015-01-09 21:02:46 +01:00
|
|
|
|
res.data.list = res.pop();
|
|
|
|
|
if (res.data.list) {
|
|
|
|
|
this.site.renderSkin('$Site#referrerTable', 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) {
|
2015-01-07 17:17:20 +01:00
|
|
|
|
res.write(' <span class="uk-text-muted">(' + location + ')</span>');
|
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 17:23:28 +02:00
|
|
|
|
return gettext("You are about to delete a story by user {0}.",
|
|
|
|
|
this.creator ? this.creator.name : 'null');
|
2010-02-06 15:13:39 +00:00
|
|
|
|
}
|