/*
Jappix - An open social platform
These are the microblog JS scripts for Jappix
-------------------------------------------------
License: AGPL
Authors: Valérian Saliou, Maranda
*/
// Bundle
var Microblog = (function () {
/**
* Alias of this
* @private
*/
var self = {};
/**
* Completes arrays of an entry's attached files
* @public
* @param {string} selector
* @param {object} tFName
* @param {object} tFURL
* @param {object} tFThumb
* @param {object} tFSource
* @param {object} tFLength
* @param {object} tFEComments
* @param {object} tFNComments
* @return {undefined}
*/
self.attached = function(selector, tFName, tFURL, tFThumb, tFSource, tFType, tFLength, tFEComments, tFNComments) {
try {
tFName.push($(selector).attr('title') || '');
tFURL.push($(selector).attr('href') || '');
tFThumb.push($(selector).find('link[rel="self"][title="thumb"]:first').attr('href') || '');
tFSource.push($(selector).attr('source') || '');
tFType.push($(selector).attr('type') || '');
tFLength.push($(selector).attr('length') || '');
// Comments?
var comments_href_c = $(selector).find('link[rel="replies"][title="comments_file"]:first').attr('href');
if(comments_href_c && comments_href_c.match(/^xmpp:(.+)\?;node=(.+)/)) {
tFEComments.push(RegExp.$1);
tFNComments.push(decodeURIComponent(RegExp.$2));
} else {
tFEComments.push('');
tFNComments.push('');
}
} catch(e) {
Console.error('Microblog.attached', e);
}
};
/**
* Displays a given microblog item
* @public
* @param {object} packet
* @param {string} from
* @param {string} hash
* @param {string} mode
* @param {string} way
* @return {undefined}
*/
self.display = function(packet, from, hash, mode, way) {
try {
// Get some values
var iParse = $(packet.getNode()).find('items item');
iParse.each(function() {
var this_sel = $(this);
// Initialize
var tContent, tFiltered, tTime, tDate, tStamp, tBody, tName, tID, tHash, tIndividual, tFEClick;
var tHTMLEscape = false;
// Arrays
var tFName = [];
var tFURL = [];
var tFThumb = [];
var tFSource = [];
var tFType = [];
var tFLength = [];
var tFEComments = [];
var tFNComments = [];
var aFURL = [];
var aFCat = [];
// Get the values
tDate = this_sel.find('published').text();
tBody = this_sel.find('body').text();
tID = this_sel.attr('id');
tName = Name.getBuddy(from);
tHash = 'update-' + hex_md5(tName + tDate + tID);
// Read attached files with a thumb (place them at first)
this_sel.find('link[rel="enclosure"]:has(link[rel="self"][title="thumb"])').each(function() {
self.attached(this, tFName, tFURL, tFThumb, tFSource, tFType, tFLength, tFEComments, tFNComments);
});
// Read attached files without any thumb
this_sel.find('link[rel="enclosure"]:not(:has(link[rel="self"][title="thumb"]))').each(function() {
self.attached(this, tFName, tFURL, tFThumb, tFSource, tFType, tFLength, tFEComments, tFNComments);
});
// Get the repeat value
var uRepeat = [this_sel.find('author name').text(), Common.explodeThis(':', this_sel.find('author uri').text(), 1)];
var uRepeated = false;
if(!uRepeat[0])
uRepeat = [Name.getBuddy(from), uRepeat[1]];
if(!uRepeat[1])
uRepeat = [uRepeat[0], from];
// Repeated?
if(uRepeat[1] != from)
uRepeated = true;
// Get the comments node
var entityComments, nodeComments;
// Get the comments
var comments_href = this_sel.find('link[title="comments"]:first').attr('href');
if(comments_href && comments_href.match(/^xmpp:(.+)\?;node=(.+)/)) {
entityComments = RegExp.$1;
nodeComments = decodeURIComponent(RegExp.$2);
}
// No comments node?
if(!entityComments || !nodeComments) {
entityComments = '';
nodeComments = '';
}
// Get the stamp & time
if(tDate) {
tStamp = DateUtils.extractStamp(Date.jab2date(tDate));
tTime = DateUtils.relative(tDate);
}
else {
tStamp = DateUtils.getTimeStamp();
tTime = '';
}
// Get the item geoloc
var tGeoloc = '';
var sGeoloc = this_sel.find('geoloc:first');
var gLat = sGeoloc.find('lat').text();
var gLon = sGeoloc.find('lon').text();
if(gLat && gLon) {
tGeoloc += '';
// Human-readable name?
var gHuman = PEP.humanPosition(
sGeoloc.find('locality').text(),
sGeoloc.find('region').text(),
sGeoloc.find('country').text()
);
if(gHuman) {
tGeoloc += gHuman.htmlEnc();
} else {
tGeoloc += gLat.htmlEnc() + '; ' + gLon.htmlEnc();
}
tGeoloc += '';
}
// Entry content: HTML, parse!
if(this_sel.find('content[type="html"]').size()) {
// Filter the xHTML message
tContent = Filter.xhtml(this);
tHTMLEscape = false;
}
// Entry content: Fallback on PLAIN?
if(!tContent) {
tContent = this_sel.find('content[type="text"]').text();
if(!tContent) {
// Legacy?
tContent = this_sel.find('title:not(source > title)').text();
// Last chance?
if(!tContent) {
tContent = tBody;
}
}
// Trim the content
tContent = $.trim(tContent);
tHTMLEscape = true;
}
// Any content?
if(tContent) {
// Apply links to message body
tFiltered = Filter.message(tContent, tName.htmlEnc(), tHTMLEscape);
// Display the received message
var html = '
' +
'
' +
'
' +
'' +
'
' +
'
' +
'
' +
'
';
// Is it a repeat?
if(uRepeated)
html += '';
html += '' + tName.htmlEnc() + '' + tFiltered + '
' +
'
' + tTime + tGeoloc + '
';
// Any file to display?
if(tFURL.length)
html += '
';
// Generate an array of the files URL
for(var a = 0; a < tFURL.length; a++) {
// Not enough data?
if(!tFURL[a]) {
continue;
}
// Push the current URL! (YouTube or file)
if(tFURL[a].match(/(\w{3,5})(:)(\S+)((\.youtube\.com\/watch(\?v|\?\S+v|\#\!v|\#\!\S+v)\=)|(youtu\.be\/))([^& ]+)((&\S)|(&\S)|\s|$)/gim)) {
aFURL.push($.trim(RegExp.$8));
aFCat.push('youtube');
}
else if(IntegrateBox.can(Common.strAfterLast('.', tFURL[a]))) {
aFURL.push(tFURL[a]);
aFCat.push(Utils.fileCategory(Common.strAfterLast('.', tFURL[a])));
}
}
// Add each file code
for(var f = 0; f < tFURL.length; f++) {
// Not enough data?
if(!tFURL[f]) {
continue;
}
// Get the file type
var tFLink = tFURL[f];
var tFExt = Common.strAfterLast('.', tFLink);
var tFCat = Utils.fileCategory(tFExt);
// Youtube video?
if(tFLink.match(/(\w{3,5})(:)(\S+)((\.youtube\.com\/watch(\?v|\?\S+v|\#\!v|\#\!\S+v)\=)|(youtu\.be\/))([^& ]+)((&\S)|(&\S)|\s|$)/gim)) {
tFLink = $.trim(RegExp.$8);
tFCat = 'youtube';
}
// Supported image/video/sound
if(IntegrateBox.can(tFExt) || (tFCat == 'youtube')) {
tFEClick = 'onclick="return IntegrateBox.apply(\'' + Utils.encodeOnclick(tFLink) + '\', \'' + Utils.encodeOnclick(tFCat) + '\', \'' + Utils.encodeOnclick(aFURL) + '\', \'' + Utils.encodeOnclick(aFCat) + '\', \'' + Utils.encodeOnclick(tFEComments) + '\', \'' + Utils.encodeOnclick(tFNComments) + '\', \'large\');" ';
} else {
tFEClick = '';
}
// Any thumbnail?
if(tFThumb[f]) {
html += '';
} else {
html += '' + tFName[f].htmlEnc() + '';
}
}
if(tFURL.length) {
html += '
';
}
// It's my own notice, we can remove it!
if(from == Common.getXID()) {
html += '';
}
// Notice from another user
else {
// User profile
html += '';
// If PEP is enabled
if(Features.enabledPEP() && tHTMLEscape) {
html += '';
}
}
html += '
';
// Mixed mode
if((mode == 'mixed') && !Common.exists('.mixed .' + tHash)) {
// Remove the old element
if(way == 'push') {
$('#channel .content.mixed .one-update.update_' + hash).remove();
}
// Get the nearest element
var nearest = Search.sortElementByStamp(tStamp, '#channel .mixed .one-update');
// Append the content at the right position (date relative)
if(nearest === 0) {
$('#channel .content.mixed').append(html);
} else {
$('#channel .one-update[data-stamp="' + nearest + '"]:first').before(html);
}
// Show the new item
if(way == 'push') {
$('#channel .content.mixed .one-update.' + tHash).fadeIn('fast');
} else {
$('#channel .content.mixed .one-update.' + tHash).show();
}
// Remove the old notices to make the DOM lighter
var oneUpdate = '#channel .content.mixed .one-update';
if($(oneUpdate).size() > 80) {
$(oneUpdate + ':last').remove();
}
// Click event on avatar/name
$('.mixed .' + tHash + ' .avatar-container, .mixed .' + tHash + ' .body b').click(function() {
self.get(from, hash);
});
}
// Individual mode
tIndividual = '#channel .content.individual.microblog-' + hash;
// Can append individual content?
var can_individual = true;
if($('#channel .top.individual input[name="comments"]').val() && Common.exists(tIndividual + ' .one-update')) {
can_individual = false;
}
if(can_individual && Common.exists(tIndividual) && !Common.exists('.individual .' + tHash)) {
if(mode == 'mixed') {
$(tIndividual).prepend(html);
} else {
$(tIndividual + ' a.more').before(html);
}
// Show the new item
if(way == 'push') {
$('#channel .content.individual .one-update.' + tHash).fadeIn('fast');
} else {
$('#channel .content.individual .one-update.' + tHash).show();
}
// Make 'more' link visible
$(tIndividual + ' a.more').css('visibility', 'visible');
// Click event on name (if not me!)
if(from != Common.getXID()) {
$('.individual .' + tHash + ' .avatar-container, .individual .' + tHash + ' .body b').click(function() {
Chat.checkCreate(from, 'chat');
});
}
}
// Apply the click event
$('.' + tHash + ' a.repost:not([data-event="true"])').click(function() {
return self.publish(tContent, tFName, tFURL, tFType, tFLength, tFThumb, uRepeat, entityComments, nodeComments, tFEComments, tFNComments);
})
.attr('data-event', 'true');
// Apply the hover event
if(nodeComments) {
$('.' + mode + ' .' + tHash).hover(function() {
self.showComments($(this), entityComments, nodeComments, tHash);
}, function() {
if($(this).find('div.comments a.one-comment.loading').size()) {
$(this).find('div.comments').remove();
}
});
}
}
});
// Display the avatar of this buddy
Avatar.get(from, 'cache', 'true', 'forget');
} catch(e) {
Console.error('Microblog.display', e);
}
};
/**
* Removes a given microblog item
* @public
* @param {string} id
* @param {string} hash
* @param {string} pserver
* @param {string} cnode
* @return {boolean}
*/
self.remove = function(id, hash, pserver, cnode) {
/* REF: http://xmpp.org/extensions/xep-0060.html#publisher-delete */
try {
// Initialize
var selector = $('.' + hash);
var get_last = false;
// Get the latest item for the mixed mode
if(Common.exists('#channel .content.mixed .' + hash)) {
get_last = true;
}
// Remove the item from our DOM
selector.fadeOut('fast', function() {
$(this).remove();
});
// Send the IQ to remove the item (and get eventual error callback)
// Also attempt to remove the comments node.
var retract_iq = new JSJaCIQ();
retract_iq.setType('set');
retract_iq.appendNode('pubsub', {
'xmlns': NS_PUBSUB
}).appendChild(retract_iq.buildNode('retract', {
'node': NS_URN_MBLOG,
'xmlns': NS_PUBSUB
})).appendChild(retract_iq.buildNode('item', {
'id': id,
'xmlns': NS_PUBSUB
}));
var comm_delete_iq;
if(pserver !== '' && cnode !== '') {
comm_delete_iq = new JSJaCIQ();
comm_delete_iq.setType('set');
comm_delete_iq.setTo(pserver);
comm_delete_iq.appendNode('pubsub', {
'xmlns': 'http://jabber.org/protocol/pubsub#owner'
}).appendChild(comm_delete_iq.buildNode('delete', {
'node': cnode,
'xmlns': 'http://jabber.org/protocol/pubsub#owner'
}));
}
if(get_last) {
if(comm_delete_iq) {
con.send(comm_delete_iq);
}
con.send(retract_iq, self.handleRemove);
} else {
if(comm_delete_iq) {
con.send(comm_delete_iq);
}
con.send(retract_iq, Errors.handleReply);
}
} catch(e) {
Console.error('Microblog.remove', e);
} finally {
return false;
}
};
/**
* Handles the microblog item removal
* @public
* @param {object} iq
* @return {undefined}
*/
self.handleRemove = function(iq) {
try {
// Handle the error reply
Errors.handleReply(iq);
// Get the latest item
self.request(Common.getXID(), '1', false, self.handleUpdateRemove);
} catch(e) {
Console.error('Microblog.handleRemove', e);
}
};
/**
* Handles the microblog update
* @public
* @param {object} iq
* @return {undefined}
*/
self.handleUpdateRemove = function(iq) {
try {
// Error?
if(iq.getType() == 'error') {
return;
}
// Initialize
var xid = Common.bareXID(Common.getStanzaFrom(iq));
var hash = hex_md5(xid);
// Display the item!
self.display(iq, xid, hash, 'mixed', 'push');
} catch(e) {
Console.error('Microblog.handleUpdateRemove', e);
}
};
/**
* Gets a given microblog comments node
* @public
* @param {string} server
* @param {string} node
* @param {string} id
* @return {boolean}
*/
self.getComments = function(server, node, id) {
/* REF: http://xmpp.org/extensions/xep-0060.html#subscriber-retrieve-requestall */
try {
var iq = new JSJaCIQ();
iq.setType('get');
iq.setID('get_' + genID() + '-' + id);
iq.setTo(server);
var pubsub = iq.appendNode('pubsub', {
'xmlns': NS_PUBSUB
});
pubsub.appendChild(iq.buildNode('items', {
'node': node,
'xmlns': NS_PUBSUB
}));
con.send(iq, self.handleComments);
} catch(e) {
Console.error('Microblog.getComments', e);
} finally {
return false;
}
};
/**
* Handles a microblog comments node items
* @public
* @param {object} iq
* @return {undefined}
*/
self.handleComments = function(iq) {
try {
// Path
var id = Common.explodeThis('-', iq.getID(), 1);
var path = 'div.comments[data-id="' + id + '"] div.comments-content';
// Does not exist?
if(!Common.exists(path)) {
return false;
}
var path_sel = $(path);
// Any error?
if(Errors.handleReply(iq)) {
path_sel.html('
' + Common._e("Could not get the comments!") + '
');
return false;
}
// Initialize
var data = iq.getNode();
var server = Common.bareXID(Common.getStanzaFrom(iq));
var node = $(data).find('items:first').attr('node');
var users_xid = [];
var code = '';
// No node?
if(!node) {
node = $(data).find('publish:first').attr('node');
}
// Get the parent microblog item
var parent_select = $('#channel .one-update:has(*[data-node="' + node + '"])');
var parent_data = [parent_select.attr('data-xid'), NS_URN_MBLOG, parent_select.attr('data-id')];
// Get the owner XID
var owner_xid = parent_select.attr('data-xid');
var repeat_xid = parent_select.find('a.repeat').attr('data-xid');
// Must we create the complete DOM?
var complete = true;
if(path_sel.find('.one-comment.compose').size()) {
complete = false;
}
// Add the comment tool
if(complete) {
code += '
' +
'' +
'
';
}
// Append the comments
$(data).find('item').each(function() {
var this_sel = $(this);
// Get comment
var current_id = this_sel.attr('id');
var current_xid = Common.explodeThis(':', this_sel.find('author uri').text(), 1);
var current_name = this_sel.find('author name').text();
var current_date = this_sel.find('published').text();
var current_body = this_sel.find('content[type="text"]').text();
var current_bname = Name.getBuddy(current_xid);
// Legacy?
if(!current_body) {
current_body = this_sel.find('title:not(source > title)').text();
}
// Yet displayed? (continue the loop)
if(path_sel.find('.one-comment[data-id="' + current_id + '"]').size()) {
return;
}
// No XID?
if(!current_xid) {
current_xid = '';
if(!current_name) {
current_name = Common._e("unknown");
}
}
else if(!current_name || (current_bname != Common.getXIDNick(current_xid))) {
current_name = current_bname;
}
// Any date?
if(current_date) {
current_date = DateUtils.relative(current_date);
} else {
current_date = DateUtils.getCompleteTime();
}
// Click event
var onclick = 'false';
if(current_xid != Common.getXID()) {
onclick = 'Chat.checkCreate(\'' + Utils.encodeOnclick(current_xid) + '\', \'chat\')';
}
// If this is my comment, add a marker
var type = 'him';
var marker = '';
var remove = '';
if(current_xid == Common.getXID()) {
type = 'me';
marker = '';
remove = '' + Common._e("Remove") + '';
}
// New comment?
var new_class = '';
if(!complete) {
new_class = ' new';
}
// Add the comment
if(current_body) {
// Add the XID
if(!Utils.existArrayValue(users_xid, current_xid)) {
users_xid.push(current_xid);
}
// Add the HTML code
code = '
' + Filter.message(current_body, current_name, true) + '
' + '