/* Jappix - An open social platform These are the microblog JS scripts for Jappix ------------------------------------------------- License: AGPL Author: Vanaryon Last revision: 03/12/11 */ // Completes arrays of an entry's attached files function attachedMicroblog(selector, tFName, tFURL, tFThumb, tFSource, tFType, tFLength, tFEComments, tFNComments) { if($(selector).attr('title')) tFName.push($(selector).attr('title')); else tFName.push(''); if($(selector).attr('href')) tFURL.push($(selector).attr('href')); else tFURL.push(''); if($(selector).find('link[rel=self][title=thumb]:first').attr('href')) tFThumb.push($(selector).find('link[rel=self][title=thumb]:first').attr('href')); else tFThumb.push(''); if($(selector).attr('source')) tFSource.push($(selector).attr('source')); else tFSource.push(''); if($(selector).attr('type')) tFType.push($(selector).attr('type')); else tFType.push(''); if($(selector).attr('length')) tFLength.push($(selector).attr('length')); else tFLength.push(''); // 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(''); } } // Displays a given microblog item function displayMicroblog(packet, from, hash, mode, way) { // Get some values var iParse = $(packet.getNode()).find('items item'); iParse.each(function() { // Initialize var tTitle, tFiltered, tTime, tDate, tStamp, tBody, tName, tID, tHash, tIndividual, tFEClick; // 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).find('published').text(); tBody = $(this).find('body').text(); tID = $(this).attr('id'); tName = getBuddyName(from); tHash = 'update-' + hex_md5(tName + tDate + tID); // Read attached files with a thumb (place them at first) $(this).find('link[rel=enclosure]:has(link[rel=self][title=thumb])').each(function() { attachedMicroblog(this, tFName, tFURL, tFThumb, tFSource, tFType, tFLength, tFEComments, tFNComments); }); // Read attached files without any thumb $(this).find('link[rel=enclosure]:not(:has(link[rel=self][title=thumb]))').each(function() { attachedMicroblog(this, tFName, tFURL, tFThumb, tFSource, tFType, tFLength, tFEComments, tFNComments); }); // Get the repeat value var uRepeat = [$(this).find('source author name').text(), explodeThis(':', $(this).find('source author uri').text(), 1)]; var uRepeated = false; if(!uRepeat[0]) uRepeat = [getBuddyName(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).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 = extractStamp(Date.jab2date(tDate)); tTime = relativeDate(tDate); } else { tStamp = getTimeStamp(); tTime = ''; } // Get the item geoloc var tGeoloc = ''; var sGeoloc = $(this).find('geoloc[xmlns=' + NS_GEOLOC + ']:first'); var gLat = sGeoloc.find('lat').text(); var gLon = sGeoloc.find('lon').text(); if(gLat && gLon) { tGeoloc += '<a class="geoloc talk-images" href="http://maps.google.com/?q=' + encodeQuotes(gLat) + ',' + encodeQuotes(gLon) + '" target="_blank">'; // Human-readable name? var gHuman = 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 += '</a>'; } // Retrieve the message body tTitle = $(this).find('content[type=text]').text(); if(!tTitle) { // Legacy? tTitle = $(this).find('title:not(source > title)').text(); // Last chance? if(!tTitle) tTitle = tBody; } // Trim the content tTitle = trim(tTitle); // Any content? if(tTitle) { // Apply links to message body tFiltered = filterThisMessage(tTitle, tName.htmlEnc(), true); // Display the received message var html = '<div class="one-update update_' + hash + ' ' + tHash + '" data-stamp="' + encodeQuotes(tStamp) + '" data-id="' + encodeQuotes(tID) + '" data-xid="' + encodeQuotes(from) + '">' + '<div class="' + hash + '">' + '<div class="avatar-container">' + '<img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" />' + '</div>' + '</div>' + '<div class="body">' + '<p>'; // Is it a repeat? if(uRepeated) html += '<a href="#" class="repeat talk-images" title="' + encodeQuotes(printf(_e("This is a repeat from %s"), uRepeat[0] + ' (' + uRepeat[1] + ')')) + '" onclick="return checkChatCreate(\'' + encodeOnclick(uRepeat[1]) + '\', \'chat\');" data-xid="' + encodeQuotes(uRepeat[1]) + '"></a>'; html += '<b title="' + from + '" class="name">' + tName.htmlEnc() + '</b> <span>' + tFiltered + '</span></p>' + '<p class="infos">' + tTime + tGeoloc + '</p>'; // Any file to display? if(tFURL.length) html += '<p class="file">'; // 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(canIntegrateBox(explodeThis('/', tFType[a], 1))) { aFURL.push(tFURL[a]); aFCat.push(fileCategory(explodeThis('/', tFType[a], 1))); } } // Add each file code for(var f = 0; f < tFURL.length; f++) { // Not enough data? if(!tFURL[f]) continue; // Get the file type var tFExt = explodeThis('/', tFType[f], 1); var tFCat = fileCategory(tFExt); var tFLink = tFURL[f]; // 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(canIntegrateBox(tFExt) || (tFCat == 'youtube')) tFEClick = 'onclick="return applyIntegrateBox(\'' + encodeOnclick(tFLink) + '\', \'' + encodeOnclick(tFCat) + '\', \'' + encodeOnclick(aFURL) + '\', \'' + encodeOnclick(aFCat) + '\', \'' + encodeOnclick(tFEComments) + '\', \'' + encodeOnclick(tFNComments) + '\', \'large\');" '; else tFEClick = ''; // Any thumbnail? if(tFThumb[f]) html += '<a class="thumb" ' + tFEClick + 'href="' + encodeQuotes(tFURL[f]) + '" target="_blank" title="' + encodeQuotes(tFName[f]) + '" data-node="' + encodeQuotes(tFNComments) + '"><img src="' + encodeQuotes(tFThumb[f]) + '" alt="" /></a>'; else html += '<a class="' + encodeQuotes(tFCat) + ' link talk-images" ' + tFEClick + 'href="' + encodeQuotes(tFURL[f]) + '" target="_blank" data-node="' + encodeQuotes(tFNComments) + '">' + tFName[f].htmlEnc() + '</a>'; } if(tFURL.length) html += '</p>'; // It's my own notice, we can remove it! if(from == getXID()) html += '<a href="#" onclick="return removeMicroblog(\'' + encodeOnclick(tID) + '\', \'' + encodeOnclick(tHash) + '\');" title="' + _e("Remove this notice") + '" class="mbtool remove talk-images"></a>'; // Notice from another user else { // User profile html += '<a href="#" title="' + _e("View profile") + '" class="mbtool profile talk-images" onclick="return openUserInfos(\'' + encodeOnclick(from) + '\');"></a>'; // If PEP is enabled if(enabledPEP()) html += '<a href="#" title="' + _e("Repeat this notice") + '" class="mbtool repost talk-images"></a>'; } html += '</div><div class="comments-container" data-node="' + encodeQuotes(nodeComments) + '"></div></div>'; // Mixed mode if((mode == 'mixed') && !exists('.mixed .' + tHash)) { // Remove the old element if(way == 'push') $('#channel .content.mixed .one-update.update_' + hash).remove(); // Get the nearest element var nearest = 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() { getMicroblog(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() && exists(tIndividual + ' .one-update')) can_individual = false; if(can_individual && exists(tIndividual) && !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 != getXID()) $('.individual .' + tHash + ' .avatar-container, .individual .' + tHash + ' .body b').click(function() { checkChatCreate(from, 'chat'); }); } // Apply the click event $('.' + tHash + ' a.repost:not([data-event=true])').click(function() { return publishMicroblog(tTitle, tFName, tFURL, tFType, tFLength, tFThumb, uRepeat, entityComments, nodeComments, tFEComments, tFNComments); }) .attr('data-event', 'true'); // Apply the hover event if(nodeComments) $('.' + mode + ' .' + tHash).hover(function() { showCommentsMicroblog($(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 getAvatar(from, 'cache', 'true', 'forget'); } // Removes a given microblog item function removeMicroblog(id, hash) { /* REF: http://xmpp.org/extensions/xep-0060.html#publisher-delete */ // Initialize var selector = $('.' + hash); var get_last = false; // Get the latest item for the mixed mode if(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) var iq = new JSJaCIQ(); iq.setType('set'); var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB}); var retract = pubsub.appendChild(iq.buildNode('retract', {'node': NS_URN_MBLOG, 'xmlns': NS_PUBSUB})); retract.appendChild(iq.buildNode('item', {'id': id, 'xmlns': NS_PUBSUB})); if(get_last) con.send(iq, handleRemoveMicroblog); else con.send(iq, handleErrorReply); return false; } // Handles the microblog item removal function handleRemoveMicroblog(iq) { // Handle the error reply handleErrorReply(iq); // Get the latest item requestMicroblog(getXID(), '1', false, handleUpdateRemoveMicroblog); } // Handles the microblog update function handleUpdateRemoveMicroblog(iq) { // Error? if(iq.getType() == 'error') return; // Initialize var xid = bareXID(getStanzaFrom(iq)); var hash = hex_md5(xid); // Display the item! displayMicroblog(iq, xid, hash, 'mixed', 'push'); } // Gets a given microblog comments node function getCommentsMicroblog(server, node, id) { /* REF: http://xmpp.org/extensions/xep-0060.html#subscriber-retrieve-requestall */ 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, handleCommentsMicroblog); return false; } // Handles a microblog comments node items function handleCommentsMicroblog(iq) { // Path var id = explodeThis('-', iq.getID(), 1); var path = 'div.comments[data-id=' + id + '] div.comments-content'; // Does not exist? if(!exists(path)) return false; // Any error? if(handleErrorReply(iq)) { $(path).html('<div class="one-comment loading">' + _e("Could not get the comments!") + '</div>'); return false; } // Initialize var data = iq.getNode(); var server = bareXID(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).find('.one-comment.compose').size()) complete = false; // Add the comment tool if(complete) code += '<div class="one-comment compose">' + '<span class="icon talk-images"></span><input type="text" placeholder="' + _e("Type your comment here...") + '" />' + '</div>'; // Append the comments $(data).find('item').each(function() { // Get comment var current_id = $(this).attr('id'); var current_xid = explodeThis(':', $(this).find('source author uri').text(), 1); var current_name = $(this).find('source author name').text(); var current_date = $(this).find('published').text(); var current_body = $(this).find('content[type=text]').text(); var current_bname = getBuddyName(current_xid); // Legacy? if(!current_body) current_body = $(this).find('title:not(source > title)').text(); // Yet displayed? (continue the loop) if($(path).find('.one-comment[data-id=' + current_id + ']').size()) return; // No XID? if(!current_xid) { current_xid = ''; if(!current_name) current_name = _e("unknown"); } else if(!current_name || (current_bname != getXIDNick(current_xid))) current_name = current_bname; // Any date? if(current_date) current_date = relativeDate(current_date); else current_date = getCompleteTime(); // Click event var onclick = 'false'; if(current_xid != getXID()) onclick = 'checkChatCreate(\'' + encodeOnclick(current_xid) + '\', \'chat\')'; // If this is my comment, add a marker var type = 'him'; var marker = ''; var remove = ''; if(current_xid == getXID()) { type = 'me'; marker = '<div class="marker"></div>'; remove = '<a href="#" class="remove" onclick="return removeCommentMicroblog(\'' + encodeOnclick(server) + '\', \'' + encodeOnclick(node) + '\', \'' + encodeOnclick(current_id) + '\');">' + _e("Remove") + '</a>'; } // New comment? var new_class = ''; if(!complete) new_class = ' new'; // Add the comment if(current_body) { // Add the XID if(!existArrayValue(users_xid, current_xid)) users_xid.push(current_xid); // Add the HTML code code += '<div class="one-comment ' + hex_md5(current_xid) + ' ' + type + new_class + '" data-id="' + encodeQuotes(current_id) + '">' + marker + '<div class="avatar-container" onclick="return ' + onclick + ';">' + '<img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" />' + '</div>' + '<div class="comment-container">' + '<a href="#" onclick="return ' + onclick + ';" title="' + encodeQuotes(current_xid) + '" class="name">' + current_name.htmlEnc() + '</a>' + '<span class="date">' + current_date.htmlEnc() + '</span>' + remove + '<p class="body">' + filterThisMessage(current_body, current_name, true) + '</p>' + '</div>' + '<div class="clear"></div>' + '</div>'; } }); // Add the HTML if(complete) { $(path).html(code); // Focus on the compose input $(document).oneTime(10, function() { $(path).find('.one-comment.compose input').focus(); }); } else { $(path).find('.one-comment.compose').after(code); // Beautiful effect $(path).find('.one-comment.new').slideDown('fast', function() { adaptCommentMicroblog(id); }).removeClass('new'); } // Set the good widths adaptCommentMicroblog(id); // Get the avatars for(a in users_xid) getAvatar(users_xid[a], 'cache', 'true', 'forget'); // Add the owner XID if(owner_xid && owner_xid.match('@') && !existArrayValue(users_xid, owner_xid)) users_xid.push(owner_xid); // Add the repeated from XID if(repeat_xid && repeat_xid.match('@') && !existArrayValue(users_xid, repeat_xid)) users_xid.push(repeat_xid); // Remove my own XID removeArrayValue(users_xid, getXID()); // DOM events if(complete) { // Update timer $(path).everyTime('60s', function() { getCommentsMicroblog(server, node, id); logThis('Updating comments node: ' + node + ' on ' + server + '...'); }); // Input key event $(path).find('.one-comment.compose input').placeholder() .keyup(function(e) { if((e.keyCode == 13) && $(this).val()) { // Send the comment! sendCommentMicroblog($(this).val(), server, node, id, users_xid, parent_data); // Reset the input value $(this).val(''); return false; } }); } } // Shows the microblog comments box function showCommentsMicroblog(path, entityComments, nodeComments, tHash) { // Do not display it twice! if(path.find('div.comments').size()) return; // Generate an unique ID var idComments = genID(); // Create comments container path.find('div.comments-container').append( '<div class="comments" data-id="' + encodeQuotes(idComments) + '">' + '<div class="arrow talk-images"></div>' + '<div class="comments-content">' + '<a href="#" class="one-comment loading"><span class="icon talk-images"></span>' + _e("Show comments") + '</a>' + '</div>' + '</div>' ); // Click event path.find('div.comments a.one-comment').click(function() { // Set loading info $(this).parent().html('<div class="one-comment loading"><span class="icon talk-images"></span>' + _e("Loading comments...") + '</div>'); // Request comments getCommentsMicroblog(entityComments, nodeComments, idComments); // Remove the comments from the DOM if click away if(tHash) { $('#channel').die('click'); $('#channel').live('click', function(evt) { if(!$(evt.target).parents('.' + tHash).size()) { $('#channel').die('click'); $('#channel .one-update div.comments-content').stopTime(); $('#channel .one-update div.comments').remove(); } }); } return false; }); } // Sends a comment on a given microblog comments node function sendCommentMicroblog(value, server, node, id, notifiy_arr, parent_data) { /* REF: http://xmpp.org/extensions/xep-0060.html#publisher-publish */ // Not enough data? if(!value || !server || !node) return false; // Get some values var date = getXMPPTime('utc'); var hash = hex_md5(value + date); // New IQ var iq = new JSJaCIQ(); iq.setType('set'); iq.setTo(server); iq.setID('set_' + genID() + '-' + id); // PubSub main elements var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB}); var publish = pubsub.appendChild(iq.buildNode('publish', {'node': node, 'xmlns': NS_PUBSUB})); var item = publish.appendChild(iq.buildNode('item', {'id': hash, 'xmlns': NS_PUBSUB})); var entry = item.appendChild(iq.buildNode('entry', {'xmlns': NS_ATOM})); // Author infos var Source = entry.appendChild(iq.buildNode('source', {'xmlns': NS_ATOM})); var author = Source.appendChild(iq.buildNode('author', {'xmlns': NS_ATOM})); author.appendChild(iq.buildNode('name', {'xmlns': NS_ATOM}, getName())); author.appendChild(iq.buildNode('uri', {'xmlns': NS_ATOM}, 'xmpp:' + getXID())); // Create the comment entry.appendChild(iq.buildNode('content', {'type': 'text', 'xmlns': NS_ATOM}, value)); entry.appendChild(iq.buildNode('published', {'xmlns': NS_ATOM}, date)); con.send(iq); // Handle this comment! iq.setFrom(server); handleCommentsMicroblog(iq); // Notify users if(notifiy_arr && notifiy_arr.length) { // XMPP link to the item var href = 'xmpp:' + server + '?;node=' + encodeURIComponent(node) + ';item=' + encodeURIComponent(hash); // Loop! for(n in notifiy_arr) sendNotification(notifiy_arr[n], 'comment', href, value, parent_data); } return false; } // Removes a given microblog comment item function removeCommentMicroblog(server, node, id) { /* REF: http://xmpp.org/extensions/xep-0060.html#publisher-delete */ // Remove the item from our DOM $('.one-comment[data-id=' + id + ']').slideUp('fast', function() { // Get the parent ID var parent_id = $(this).parents('div.comments').attr('data-id'); // Remove it! $(this).remove(); // Adapt the width adaptCommentMicroblog(parent_id); }); // Send the IQ to remove the item (and get eventual error callback) var iq = new JSJaCIQ(); iq.setType('set'); iq.setTo(server); var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB}); var retract = pubsub.appendChild(iq.buildNode('retract', {'node': node, 'xmlns': NS_PUBSUB})); retract.appendChild(iq.buildNode('item', {'id': id, 'xmlns': NS_PUBSUB})); con.send(iq); return false; } // Adapts the comment elements width function adaptCommentMicroblog(id) { var selector = $('div.comments[data-id=' + id + '] div.comments-content'); var selector_width = selector.width(); // Change widths selector.find('.one-comment.compose input').css('width', selector_width - 60); selector.find('.one-comment .comment-container').css('width', selector_width - 55); } // Handles the microblog of an user function handleMicroblog(iq) { // Get the from attribute of this IQ var from = bareXID(getStanzaFrom(iq)); // Define the selector path var selector = '#channel .top.individual input[name='; // Is this request still alive? if(from == $(selector + 'jid]').val()) { var hash = hex_md5(from); // Update the items counter var old_count = parseInt($(selector + 'counter]').val()); $(selector + 'counter]').val(old_count + 20); // Display the microblog displayMicroblog(iq, from, hash, 'individual', 'request'); // Hide the waiting icon if(enabledPEP()) waitMicroblog('sync'); else waitMicroblog('unsync'); // Hide the 'more items' link? if($(iq.getNode()).find('item').size() < old_count) $('#channel .individual a.more').remove(); // Get the comments? var comments_node = $('#channel .top.individual input[name=comments]').val(); if(comments_node && comments_node.match(/^xmpp:(.+)\?;node=(.+);item=(.+)/)) { // Get the values var comments_entity = RegExp.$1; comments_node = decodeURIComponent(RegExp.$2); // Selectors var file_link = $('#channel .individual .one-update p.file a[data-node=' + comments_node + ']'); var entry_link = $('#channel .individual .one-update:has(*[data-node=' + comments_node + '])'); // Is it a file? if(file_link.size()) file_link.click(); // Is it a microblog entry? else if(entry_link.size()) { showCommentsMicroblog(entry_link, comments_entity, comments_node); entry_link.find('a.one-comment').click(); } } } logThis('Microblog got: ' + from, 3); } // Resets the microblog elements function resetMicroblog() { // Reset everything $('#channel .individual .one-update div.comments-content').stopTime(); $('#channel .individual').remove(); $('#channel .mixed').show(); // Hide the waiting icon if(enabledPEP()) waitMicroblog('sync'); else waitMicroblog('unsync'); return false; } // Gets the user's microblog to check it exists function getInitMicroblog() { getMicroblog(getXID(), hex_md5(getXID()), true); } // Handles the user's microblog to create it in case of error function handleInitMicroblog(iq) { // Any error? if((iq.getType() == 'error') && $(iq.getNode()).find('item-not-found').size()) { // The node may not exist, create it! setupMicroblog('', NS_URN_MBLOG, '1', '1000000', '', '', true); logThis('Error while getting microblog, trying to reconfigure the Pubsub node!', 2); } } // Requests an user's microblog function requestMicroblog(xid, items, get_item, handler) { // Ask the server the user's microblog var iq = new JSJaCIQ(); iq.setType('get'); iq.setTo(xid); var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB}); var ps_items = pubsub.appendChild(iq.buildNode('items', {'node': NS_URN_MBLOG, 'xmlns': NS_PUBSUB})); // Request a particular item? if(get_item) ps_items.appendChild(iq.buildNode('item', {'id': get_item, 'xmlns': NS_PUBSUB})); else ps_items.setAttribute('max_items', items); if(handler) con.send(iq, handler); else con.send(iq, handleMicroblog); return false; } // Gets the microblog of an user function getMicroblog(xid, hash, check) { /* REF: http://xmpp.org/extensions/xep-0060.html#subscriber-retrieve */ logThis('Get the microblog: ' + xid, 3); // Fire the wait event waitMicroblog('fetch'); // XMPP URI? var get_item = ''; if(xid.match(/^xmpp:(.+)\?;node=(.+);item=(.+)/)) { xid = RegExp.$1; get_item = decodeURIComponent(RegExp.$3); } // No hash? if(!hash) hash = hex_md5(xid); // Can display the individual channel? if(!check && !exists('#channel .individual')) { // Hide the mixed channel $('#channel .mixed').hide(); // Get the channel title depending on the XID var cTitle; var cShortcuts = ''; if(xid == getXID()) cTitle = _e("Your channel"); else { cTitle = _e("Channel of") + ' ' + getBuddyName(xid).htmlEnc(); cShortcuts = '<div class="shortcuts">' + '<a href="#" class="message talk-images" title="' + _e("Send him/her a message") + '" onclick="return composeInboxMessage(\'' + encodeOnclick(xid) + '\');"></a>' + '<a href="#" class="chat talk-images" title="' + _e("Start a chat with him/her") + '" onclick="return checkChatCreate(\'' + encodeOnclick(xid) + '\', \'chat\');"></a>' + '<a href="#" class="command talk-images" title="' + _e("Command") + '" onclick="return retrieveAdHoc(\'' + encodeOnclick(xid) + '\');"></a>' + '<a href="#" class="profile talk-images" title="' + _e("Show user profile") + '" onclick="return openUserInfos(\'' + encodeOnclick(xid) + '\');"></a>' + '</div>'; } // Create a new individual channel $('#channel .content.mixed').after( '<div class="content individual microblog-' + hash + '">' + '<a href="#" class="more home-images" onclick="return getMicroblog(\'' + encodeOnclick(xid) + '\', \'' + encodeOnclick(hash) + '\');">' + _e("More notices...") + '</a>' + '</div>' ) .before( '<div class="top individual ' + hash + '">' + '<div class="avatar-container">' + '<img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" />' + '</div>' + '<div class="update">' + '<h2>' + cTitle + '</h2>' + '<a href="#" onclick="return resetMicroblog();">« ' + _e("Previous") + '</a>' + '</div>' + cShortcuts + '<input type="hidden" name="jid" value="' + encodeQuotes(xid) + '" />' + '<input type="hidden" name="counter" value="20" />' + '</div>' ); // Display the user avatar getAvatar(xid, 'cache', 'true', 'forget'); } // Get the number of items to retrieve var items = '0'; if(!check) items = $('#channel .top.individual input[name=counter]').val(); // Request if(check) requestMicroblog(xid, items, get_item, handleInitMicroblog); else requestMicroblog(xid, items, get_item, handleMicroblog); return false; } // Show a given microblog waiting status function waitMicroblog(type) { // First hide all the infos elements $('#channel .footer div').hide(); // Display the good one $('#channel .footer div.' + type).show(); // Depending on the type, disable/enable certain tools var selector = $('#channel .top input[name=microblog_body]'); if(type == 'unsync') selector.attr('disabled', true); else if(type == 'sync') $(document).oneTime(10, function() { selector.removeAttr('disabled').focus(); }); } // Setups a new microblog function setupMicroblog(entity, node, persist, maximum, access, publish, create) { /* REF: http://xmpp.org/extensions/xep-0060.html#owner-create-and-configure */ // Create the PubSub node var iq = new JSJaCIQ(); iq.setType('set'); // Any external entity? if(entity) iq.setTo(entity); // Create it? if(create) { var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB}); pubsub.appendChild(iq.buildNode('create', {'xmlns': NS_PUBSUB, 'node': node})); } else var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB_OWNER}); // Configure it! var configure = pubsub.appendChild(iq.buildNode('configure', {'node': node, 'xmlns': NS_PUBSUB})); var x = configure.appendChild(iq.buildNode('x', {'xmlns': NS_XDATA, 'type': 'submit'})); var field1 = x.appendChild(iq.buildNode('field', {'var': 'FORM_TYPE', 'type': 'hidden', 'xmlns': NS_XDATA})); field1.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, NS_PUBSUB_NC)); // Persist items? if(persist) { var field2 = x.appendChild(iq.buildNode('field', {'var': 'pubsub#persist_items', 'xmlns': NS_XDATA})); field2.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, persist)); } // Maximum items? if(maximum) { var field3 = x.appendChild(iq.buildNode('field', {'var': 'pubsub#max_items', 'xmlns': NS_XDATA})); field3.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, maximum)); } // Access rights? if(access) { var field4 = x.appendChild(iq.buildNode('field', {'var': 'pubsub#access_model', 'xmlns': NS_XDATA})); field4.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, access)); } // Publish rights? if(publish) { var field5 = x.appendChild(iq.buildNode('field', {'var': 'pubsub#publish_model', 'xmlns': NS_XDATA})); field5.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, publish)); } con.send(iq); } // Gets the microblog configuration function getConfigMicroblog() { // Lock the microblog options $('#persistent, #maxnotices').attr('disabled', true); // Get the microblog configuration var iq = new JSJaCIQ(); iq.setType('get'); var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB_OWNER}); pubsub.appendChild(iq.buildNode('configure', {'node': NS_URN_MBLOG, 'xmlns': NS_PUBSUB_OWNER})); con.send(iq, handleGetConfigMicroblog); } // Handles the microblog configuration function handleGetConfigMicroblog(iq) { // Reset the options stuffs waitOptions('microblog'); // Unlock the microblog options $('#persistent, #maxnotices').removeAttr('disabled'); // End if not a result if(!iq || (iq.getType() != 'result')) return; // Initialize the values var selector = $(iq.getNode()); var persistent = '0'; var maxnotices = '1000000'; // Get the values var xPersistent = selector.find('field[var=pubsub#persist_items] value:first').text(); var xMaxnotices = selector.find('field[var=pubsub#max_items] value:first').text(); // Any value? if(xPersistent) persistent = xPersistent; if(xMaxnotices) maxnotices = xMaxnotices; // Change the maxnotices value switch(maxnotices) { case '1': case '100': case '1000': case '10000': case '100000': case '1000000': break; default: maxnotices = '1000000'; break; } // Apply persistent value if(persistent == '0') $('#persistent').attr('checked', false); else $('#persistent').attr('checked', true); // Apply maxnotices value $('#maxnotices').val(maxnotices); } // Handles the user's microblog function handleMyMicroblog(packet) { // Reset the entire form $('#channel .top input[name=microblog_body]').removeAttr('disabled').val(''); $('#channel .top input[name=microblog_body]').placeholder(); unattachMicroblog(); // Check for errors handleErrorReply(packet); } // Performs the microblog sender checks function sendMicroblog() { logThis('Send a new microblog item', 3); // Avoid nasty errors try { // Get the values var selector = $('#channel .top input[name=microblog_body]'); var body = trim(selector.val()); // Sufficient parameters if(body) { // Disable & blur our input selector.attr('disabled', true).blur(); // Files array var fName = []; var fType = []; var fLength = []; var fURL = []; var fThumb = []; // Read the files $('#attach .one-file').each(function() { // Push the values! fName.push($(this).find('a.link').text()); fType.push($(this).attr('data-type')); fLength.push($(this).attr('data-length')); fURL.push($(this).find('a.link').attr('href')); fThumb.push($(this).attr('data-thumb')); }); // Containing YouTube videos? var yt_matches = body.match(/(\w{3,5})(:)(\S+)((\.youtube\.com\/watch(\?v|\?\S+v|\#\!v|\#\!\S+v)\=)|(youtu\.be\/))([^& ]+)((&\S)|(&\S)|\s|$)/gim); for(y in yt_matches) { fName.push(''); fType.push('text/html'); fLength.push(''); fURL.push(trim(yt_matches[y])); fThumb.push('https://img.youtube.com/vi/' + trim(yt_matches[y].replace(/(\w{3,5})(:)(\S+)((\.youtube\.com\/watch(\?v|\?\S+v|\#\!v|\#\!\S+v)\=)|(youtu\.be\/))([^& ]+)((&\S)|(&\S)|\s|$)/gim, '$8')) + '/0.jpg'); } // Send the message on the XMPP network publishMicroblog(body, fName, fURL, fType, fLength, fThumb); } } // Return false (security) finally { return false; } } // Publishes a given microblog item function publishMicroblog(body, attachedname, attachedurl, attachedtype, attachedlength, attachedthumb, repeat, comments_entity, comments_node, comments_entity_file, comments_node_file) { /* REF: http://xmpp.org/extensions/xep-0277.html */ // Generate some values var time = getXMPPTime('utc'); var id = hex_md5(body + time); var nick = getName(); var xid = getXID(); // Define repeat options var author_nick = nick; var author_xid = xid; if(repeat && repeat.length) { author_nick = repeat[0]; author_xid = repeat[1]; } // Define comments options var node_create = false; if(!comments_entity || !comments_node) { node_create = true; comments_entity = HOST_PUBSUB; comments_node = NS_URN_MBLOG + ':comments/' + id; } if(!comments_entity_file) comments_entity_file = []; if(!comments_node_file) comments_node_file = []; // Don't create another comments node if only 1 file is attached if(attachedurl && (attachedurl.length == 1) && (!comments_entity_file[0] || !comments_node_file[0])) { comments_entity_file = [comments_entity]; comments_node_file = [comments_node]; } // New IQ var iq = new JSJaCIQ(); iq.setType('set'); iq.setTo(xid); // Create the main XML nodes/childs var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB}); var publish = pubsub.appendChild(iq.buildNode('publish', {'node': NS_URN_MBLOG, 'xmlns': NS_PUBSUB})); var item = publish.appendChild(iq.buildNode('item', {'id': id, 'xmlns': NS_PUBSUB})); var entry = item.appendChild(iq.buildNode('entry', {'xmlns': NS_ATOM})); // Create the XML source childs var Source = entry.appendChild(iq.buildNode('source', {'xmlns': NS_ATOM})); var author = Source.appendChild(iq.buildNode('author', {'xmlns': NS_ATOM})); author.appendChild(iq.buildNode('name', {'xmlns': NS_ATOM}, author_nick)); author.appendChild(iq.buildNode('uri', {'xmlns': NS_ATOM}, 'xmpp:' + author_xid)); // Create the XML entry childs entry.appendChild(iq.buildNode('content', {'type': 'text', 'xmlns': NS_ATOM}, body)); entry.appendChild(iq.buildNode('published', {'xmlns': NS_ATOM}, time)); entry.appendChild(iq.buildNode('updated', {'xmlns': NS_ATOM}, time)); entry.appendChild(iq.buildNode('link', { 'rel': 'alternate', 'href': 'xmpp:' + xid + '?;node=' + encodeURIComponent(NS_URN_MBLOG) + ';item=' + encodeURIComponent(id), 'xmlns': NS_ATOM })); // Create the attached files nodes for(var i = 0; i < attachedurl.length; i++) { // Not enough data? if(!attachedurl[i]) continue; // Append a new file element var file = entry.appendChild(iq.buildNode('link', {'xmlns': NS_ATOM, 'rel': 'enclosure', 'href': attachedurl[i]})); // Add attributes if(attachedname[i]) file.setAttribute('title', attachedname[i]); if(attachedtype[i]) file.setAttribute('type', attachedtype[i]); if(attachedlength[i]) file.setAttribute('length', attachedlength[i]); // Any thumbnail? if(attachedthumb[i]) file.appendChild(iq.buildNode('link', {'xmlns': NS_URN_MBLOG, 'rel': 'self', 'title': 'thumb', 'type': attachedtype[i], 'href': attachedthumb[i]})); // Any comments node? if(!comments_entity_file[i] || !comments_node_file[i]) { // Generate values comments_entity_file[i] = HOST_PUBSUB; comments_node_file[i] = NS_URN_MBLOG + ':comments/' + hex_md5(attachedurl[i] + attachedname[i] + attachedtype[i] + attachedlength[i] + time); // Create the node setupMicroblog(comments_entity_file[i], comments_node_file[i], '1', '1000000', 'open', 'open', true); } file.appendChild(iq.buildNode('link', {'xmlns': NS_URN_MBLOG, 'rel': 'replies', 'title': 'comments_file', 'href': 'xmpp:' + comments_entity_file[i] + '?;node=' + encodeURIComponent(comments_node_file[i])})); } // Create the comments child entry.appendChild(iq.buildNode('link', {'xmlns': NS_ATOM, 'rel': 'replies', 'title': 'comments', 'href': 'xmpp:' + comments_entity + '?;node=' + encodeURIComponent(comments_node)})); // Create the geoloc child var geoloc_xml = getDB('geolocation', 'now'); if(geoloc_xml) { // Create two position arrays var geo_names = ['lat', 'lon', 'country', 'countrycode', 'region', 'postalcode', 'locality', 'street', 'building', 'text', 'uri', 'timestamp']; var geo_values = parsePosition(XMLFromString(geoloc_xml)); // New geoloc child var geoloc = entry.appendChild(iq.buildNode('geoloc', {'xmlns': NS_GEOLOC})); // Append the geoloc content for(var g = 0; g < geo_names.length; g++) { if(geo_names[g] && geo_values[g]) geoloc.appendChild(iq.buildNode(geo_names[g], {'xmlns': NS_GEOLOC}, geo_values[g])); } } // Send the IQ con.send(iq, handleMyMicroblog); // Create the XML comments PubSub nodes if(node_create) setupMicroblog(comments_entity, comments_node, '1', '1000000', 'open', 'open', true); return false; } // Attaches a file to a microblog post function attachMicroblog() { // File upload vars var attach_options = { dataType: 'xml', beforeSubmit: waitMicroblogAttach, success: handleMicroblogAttach }; // Upload form submit event $('#attach').submit(function() { if(!exists('#attach .wait') && $('#attach input[type=file]').val()) $(this).ajaxSubmit(attach_options); return false; }); // Upload input change event $('#attach input[type=file]').change(function() { if(!exists('#attach .wait') && $(this).val()) $('#attach').ajaxSubmit(attach_options); return false; }); } // Unattaches a microblog file function unattachMicroblog(id) { // Individual removal? if(id) $('#attach .one-file[data-id=' + id + ']').remove(); else $('#attach .one-file').remove(); // Must enable the popup again? if(!exists('#attach .one-file')) { // Restore the bubble class $('#attach').addClass('bubble'); // Enable the bubble click events if(id) { $('#attach').hide(); showBubble('#attach'); } else closeBubbles(); } return false; } // Wait event for file attaching function waitMicroblogAttach() { // Append the wait icon $('#attach input[type=submit]').after('<div class="wait wait-medium"></div>'); // Lock the bubble $('#attach').removeClass('bubble'); } // Success event for file attaching function handleMicroblogAttach(responseXML) { // Data selector var dData = $(responseXML).find('jappix'); // Process the returned data if(!dData.find('error').size()) { // Do not allow this bubble to be hidden $('#attach').removeClass('bubble'); // Get the file values var fName = dData.find('title').text(); var fType = dData.find('type').text(); var fLength = dData.find('length').text(); var fURL = dData.find('href').text(); var fThumb = dData.find('thumb').text(); // Generate a file ID var fID = hex_md5(fURL); // Add this file $('#attach .attach-subitem').append( '<div class="one-file" data-type="' + encodeQuotes(fType) + '" data-length="' + encodeQuotes(fLength) + '" data-thumb="' + encodeQuotes(fThumb) + '" data-id="' + fID + '">' + '<a class="remove talk-images" href="#" title="' + encodeQuotes(_e("Unattach the file")) + '"></a>' + '<a class="link" href="' + encodeQuotes(fURL) + '" target="_blank">' + fName.htmlEnc() + '</a>' + '</div>' ); // Click event $('#attach .one-file[data-id=' + fID + '] a.remove').click(function() { return unattachMicroblog(fID); }); logThis('File attached.', 3); } // Any error? else { openThisError(4); // Unlock the bubble? if(!exists('#attach .one-file')) { $('#attach').addClass('bubble').hide(); // Show the bubble again! showBubble('#attach'); } logThis('Error while attaching the file: ' + dData.find('error').text(), 1); } // Reset the attach bubble $('#attach input[type=file]').val(''); $('#attach .wait').remove(); // Focus on the text input $(document).oneTime(10, function() { $('#channel .top input[name=microblog_body]').focus(); }); } // Shows the microblog of an user from his infos function fromInfosMicroblog(xid, hash) { // Renitialize the channel resetMicroblog(); // Switch to the channel switchChan('channel'); // Get the microblog getMicroblog(xid, hash); } // Addon launcher function launchMicroblog() { // Keyboard event $('#channel .top input[name=microblog_body]').keyup(function(e) { // Enter pressed: send the microblog notice if((e.keyCode == 13) && !exists('#attach .wait')) return sendMicroblog(); }) // Placeholder .placeholder(); // Microblog file attacher attachMicroblog(); }