3 Jappix - An open social platform
4 These are the microblog JS scripts for Jappix
6 -------------------------------------------------
10 Last revision: 03/12/11
14 // Completes arrays of an entry's attached files
15 function attachedMicroblog(selector, tFName, tFURL, tFThumb, tFSource, tFType, tFLength, tFEComments, tFNComments) {
16 if($(selector).attr('title'))
17 tFName.push($(selector).attr('title'));
21 if($(selector).attr('href'))
22 tFURL.push($(selector).attr('href'));
26 if($(selector).find('link[rel=self][title=thumb]:first').attr('href'))
27 tFThumb.push($(selector).find('link[rel=self][title=thumb]:first').attr('href'));
31 if($(selector).attr('source'))
32 tFSource.push($(selector).attr('source'));
36 if($(selector).attr('type'))
37 tFType.push($(selector).attr('type'));
41 if($(selector).attr('length'))
42 tFLength.push($(selector).attr('length'));
47 var comments_href_c = $(selector).find('link[rel=replies][title=comments_file]:first').attr('href');
49 if(comments_href_c && comments_href_c.match(/^xmpp:(.+)\?;node=(.+)/)) {
50 tFEComments.push(RegExp.$1);
51 tFNComments.push(decodeURIComponent(RegExp.$2));
60 // Displays a given microblog item
61 function displayMicroblog(packet, from, hash, mode, way) {
63 var iParse = $(packet.getNode()).find('items item');
65 iParse.each(function() {
67 var tTitle, tFiltered, tTime, tDate, tStamp, tBody, tName, tID, tHash, tIndividual, tFEClick;
82 tDate = $(this).find('published').text();
83 tBody = $(this).find('body').text();
84 tID = $(this).attr('id');
85 tName = getBuddyName(from);
86 tHash = 'update-' + hex_md5(tName + tDate + tID);
88 // Read attached files with a thumb (place them at first)
89 $(this).find('link[rel=enclosure]:has(link[rel=self][title=thumb])').each(function() {
90 attachedMicroblog(this, tFName, tFURL, tFThumb, tFSource, tFType, tFLength, tFEComments, tFNComments);
93 // Read attached files without any thumb
94 $(this).find('link[rel=enclosure]:not(:has(link[rel=self][title=thumb]))').each(function() {
95 attachedMicroblog(this, tFName, tFURL, tFThumb, tFSource, tFType, tFLength, tFEComments, tFNComments);
98 // Get the repeat value
99 var uRepeat = [$(this).find('source author name').text(), explodeThis(':', $(this).find('source author uri').text(), 1)];
100 var uRepeated = false;
103 uRepeat = [getBuddyName(from), uRepeat[1]];
105 uRepeat = [uRepeat[0], from];
108 if(uRepeat[1] != from)
111 // Get the comments node
112 var entityComments, nodeComments;
115 var comments_href = $(this).find('link[title=comments]:first').attr('href');
117 if(comments_href && comments_href.match(/^xmpp:(.+)\?;node=(.+)/)) {
118 entityComments = RegExp.$1;
119 nodeComments = decodeURIComponent(RegExp.$2);
123 if(!entityComments || !nodeComments) {
128 // Get the stamp & time
130 tStamp = extractStamp(Date.jab2date(tDate));
131 tTime = relativeDate(tDate);
135 tStamp = getTimeStamp();
139 // Get the item geoloc
141 var sGeoloc = $(this).find('geoloc[xmlns=' + NS_GEOLOC + ']:first');
142 var gLat = sGeoloc.find('lat').text();
143 var gLon = sGeoloc.find('lon').text();
146 tGeoloc += '<a class="geoloc talk-images" href="http://maps.google.com/?q=' + encodeQuotes(gLat) + ',' + encodeQuotes(gLon) + '" target="_blank">';
148 // Human-readable name?
149 var gHuman = humanPosition(
150 sGeoloc.find('locality').text(),
151 sGeoloc.find('region').text(),
152 sGeoloc.find('country').text()
156 tGeoloc += gHuman.htmlEnc();
158 tGeoloc += gLat.htmlEnc() + '; ' + gLon.htmlEnc();
163 // Retrieve the message body
164 tTitle = $(this).find('content[type=text]').text();
168 tTitle = $(this).find('title:not(source > title)').text();
176 tTitle = trim(tTitle);
180 // Apply links to message body
181 tFiltered = filterThisMessage(tTitle, tName.htmlEnc(), true);
183 // Display the received message
184 var html = '<div class="one-update update_' + hash + ' ' + tHash + '" data-stamp="' + encodeQuotes(tStamp) + '" data-id="' + encodeQuotes(tID) + '" data-xid="' + encodeQuotes(from) + '">' +
185 '<div class="' + hash + '">' +
186 '<div class="avatar-container">' +
187 '<img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" />' +
191 '<div class="body">' +
196 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>';
198 html += '<b title="' + from + '" class="name">' + tName.htmlEnc() + '</b> <span>' + tFiltered + '</span></p>' +
199 '<p class="infos">' + tTime + tGeoloc + '</p>';
201 // Any file to display?
203 html += '<p class="file">';
205 // Generate an array of the files URL
206 for(var a = 0; a < tFURL.length; a++) {
211 // Push the current URL! (YouTube or file)
212 if(tFURL[a].match(/(\w{3,5})(:)(\S+)((\.youtube\.com\/watch(\?v|\?\S+v|\#\!v|\#\!\S+v)\=)|(youtu\.be\/))([^& ]+)((&\S)|(&\S)|\s|$)/gim)) {
213 aFURL.push(trim(RegExp.$8));
214 aFCat.push('youtube');
217 else if(canIntegrateBox(explodeThis('/', tFType[a], 1))) {
218 aFURL.push(tFURL[a]);
219 aFCat.push(fileCategory(explodeThis('/', tFType[a], 1)));
223 // Add each file code
224 for(var f = 0; f < tFURL.length; f++) {
230 var tFExt = explodeThis('/', tFType[f], 1);
231 var tFCat = fileCategory(tFExt);
232 var tFLink = tFURL[f];
235 if(tFLink.match(/(\w{3,5})(:)(\S+)((\.youtube\.com\/watch(\?v|\?\S+v|\#\!v|\#\!\S+v)\=)|(youtu\.be\/))([^& ]+)((&\S)|(&\S)|\s|$)/gim)) {
236 tFLink = trim(RegExp.$8);
240 // Supported image/video/sound
241 if(canIntegrateBox(tFExt) || (tFCat == 'youtube'))
242 tFEClick = 'onclick="return applyIntegrateBox(\'' + encodeOnclick(tFLink) + '\', \'' + encodeOnclick(tFCat) + '\', \'' + encodeOnclick(aFURL) + '\', \'' + encodeOnclick(aFCat) + '\', \'' + encodeOnclick(tFEComments) + '\', \'' + encodeOnclick(tFNComments) + '\', \'large\');" ';
248 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>';
250 html += '<a class="' + encodeQuotes(tFCat) + ' link talk-images" ' + tFEClick + 'href="' + encodeQuotes(tFURL[f]) + '" target="_blank" data-node="' + encodeQuotes(tFNComments) + '">' + tFName[f].htmlEnc() + '</a>';
256 // It's my own notice, we can remove it!
258 html += '<a href="#" onclick="return removeMicroblog(\'' + encodeOnclick(tID) + '\', \'' + encodeOnclick(tHash) + '\');" title="' + _e("Remove this notice") + '" class="mbtool remove talk-images"></a>';
260 // Notice from another user
263 html += '<a href="#" title="' + _e("View profile") + '" class="mbtool profile talk-images" onclick="return openUserInfos(\'' + encodeOnclick(from) + '\');"></a>';
267 html += '<a href="#" title="' + _e("Repeat this notice") + '" class="mbtool repost talk-images"></a>';
270 html += '</div><div class="comments-container" data-node="' + encodeQuotes(nodeComments) + '"></div></div>';
273 if((mode == 'mixed') && !exists('.mixed .' + tHash)) {
274 // Remove the old element
276 $('#channel .content.mixed .one-update.update_' + hash).remove();
278 // Get the nearest element
279 var nearest = sortElementByStamp(tStamp, '#channel .mixed .one-update');
281 // Append the content at the right position (date relative)
283 $('#channel .content.mixed').append(html);
285 $('#channel .one-update[data-stamp=' + nearest + ']:first').before(html);
289 $('#channel .content.mixed .one-update.' + tHash).fadeIn('fast');
291 $('#channel .content.mixed .one-update.' + tHash).show();
293 // Remove the old notices to make the DOM lighter
294 var oneUpdate = '#channel .content.mixed .one-update';
296 if($(oneUpdate).size() > 80)
297 $(oneUpdate + ':last').remove();
299 // Click event on avatar/name
300 $('.mixed .' + tHash + ' .avatar-container, .mixed .' + tHash + ' .body b').click(function() {
301 getMicroblog(from, hash);
306 tIndividual = '#channel .content.individual.microblog-' + hash;
308 // Can append individual content?
309 var can_individual = true;
311 if($('#channel .top.individual input[name=comments]').val() && exists(tIndividual + ' .one-update'))
312 can_individual = false;
314 if(can_individual && exists(tIndividual) && !exists('.individual .' + tHash)) {
316 $(tIndividual).prepend(html);
318 $(tIndividual + ' a.more').before(html);
322 $('#channel .content.individual .one-update.' + tHash).fadeIn('fast');
324 $('#channel .content.individual .one-update.' + tHash).show();
326 // Make 'more' link visible
327 $(tIndividual + ' a.more').css('visibility', 'visible');
329 // Click event on name (if not me!)
331 $('.individual .' + tHash + ' .avatar-container, .individual .' + tHash + ' .body b').click(function() {
332 checkChatCreate(from, 'chat');
336 // Apply the click event
337 $('.' + tHash + ' a.repost:not([data-event=true])').click(function() {
338 return publishMicroblog(tTitle, tFName, tFURL, tFType, tFLength, tFThumb, uRepeat, entityComments, nodeComments, tFEComments, tFNComments);
341 .attr('data-event', 'true');
343 // Apply the hover event
345 $('.' + mode + ' .' + tHash).hover(function() {
346 showCommentsMicroblog($(this), entityComments, nodeComments, tHash);
348 if($(this).find('div.comments a.one-comment.loading').size())
349 $(this).find('div.comments').remove();
354 // Display the avatar of this buddy
355 getAvatar(from, 'cache', 'true', 'forget');
358 // Removes a given microblog item
359 function removeMicroblog(id, hash) {
360 /* REF: http://xmpp.org/extensions/xep-0060.html#publisher-delete */
363 var selector = $('.' + hash);
364 var get_last = false;
366 // Get the latest item for the mixed mode
367 if(exists('#channel .content.mixed .' + hash))
370 // Remove the item from our DOM
371 selector.fadeOut('fast', function() {
375 // Send the IQ to remove the item (and get eventual error callback)
376 var iq = new JSJaCIQ();
379 var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
380 var retract = pubsub.appendChild(iq.buildNode('retract', {'node': NS_URN_MBLOG, 'xmlns': NS_PUBSUB}));
381 retract.appendChild(iq.buildNode('item', {'id': id, 'xmlns': NS_PUBSUB}));
384 con.send(iq, handleRemoveMicroblog);
386 con.send(iq, handleErrorReply);
391 // Handles the microblog item removal
392 function handleRemoveMicroblog(iq) {
393 // Handle the error reply
394 handleErrorReply(iq);
396 // Get the latest item
397 requestMicroblog(getXID(), '1', false, handleUpdateRemoveMicroblog);
400 // Handles the microblog update
401 function handleUpdateRemoveMicroblog(iq) {
403 if(iq.getType() == 'error')
407 var xid = bareXID(getStanzaFrom(iq));
408 var hash = hex_md5(xid);
411 displayMicroblog(iq, xid, hash, 'mixed', 'push');
414 // Gets a given microblog comments node
415 function getCommentsMicroblog(server, node, id) {
416 /* REF: http://xmpp.org/extensions/xep-0060.html#subscriber-retrieve-requestall */
418 var iq = new JSJaCIQ();
420 iq.setID('get_' + genID() + '-' + id);
423 var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
424 pubsub.appendChild(iq.buildNode('items', {'node': node, 'xmlns': NS_PUBSUB}));
426 con.send(iq, handleCommentsMicroblog);
431 // Handles a microblog comments node items
432 function handleCommentsMicroblog(iq) {
434 var id = explodeThis('-', iq.getID(), 1);
435 var path = 'div.comments[data-id=' + id + '] div.comments-content';
442 if(handleErrorReply(iq)) {
443 $(path).html('<div class="one-comment loading">' + _e("Could not get the comments!") + '</div>');
449 var data = iq.getNode();
450 var server = bareXID(getStanzaFrom(iq));
451 var node = $(data).find('items:first').attr('node');
457 node = $(data).find('publish:first').attr('node');
459 // Get the parent microblog item
460 var parent_select = $('#channel .one-update:has(*[data-node=' + node + '])');
461 var parent_data = [parent_select.attr('data-xid'), NS_URN_MBLOG, parent_select.attr('data-id')];
464 var owner_xid = parent_select.attr('data-xid');
465 var repeat_xid = parent_select.find('a.repeat').attr('data-xid');
467 // Must we create the complete DOM?
470 if($(path).find('.one-comment.compose').size())
473 // Add the comment tool
475 code += '<div class="one-comment compose">' +
476 '<span class="icon talk-images"></span><input type="text" placeholder="' + _e("Type your comment here...") + '" />' +
479 // Append the comments
480 $(data).find('item').each(function() {
482 var current_id = $(this).attr('id');
483 var current_xid = explodeThis(':', $(this).find('source author uri').text(), 1);
484 var current_name = $(this).find('source author name').text();
485 var current_date = $(this).find('published').text();
486 var current_body = $(this).find('content[type=text]').text();
487 var current_bname = getBuddyName(current_xid);
491 current_body = $(this).find('title:not(source > title)').text();
493 // Yet displayed? (continue the loop)
494 if($(path).find('.one-comment[data-id=' + current_id + ']').size())
502 current_name = _e("unknown");
505 else if(!current_name || (current_bname != getXIDNick(current_xid)))
506 current_name = current_bname;
510 current_date = relativeDate(current_date);
512 current_date = getCompleteTime();
515 var onclick = 'false';
517 if(current_xid != getXID())
518 onclick = 'checkChatCreate(\'' + encodeOnclick(current_xid) + '\', \'chat\')';
520 // If this is my comment, add a marker
525 if(current_xid == getXID()) {
527 marker = '<div class="marker"></div>';
528 remove = '<a href="#" class="remove" onclick="return removeCommentMicroblog(\'' + encodeOnclick(server) + '\', \'' + encodeOnclick(node) + '\', \'' + encodeOnclick(current_id) + '\');">' + _e("Remove") + '</a>';
540 if(!existArrayValue(users_xid, current_xid))
541 users_xid.push(current_xid);
544 code += '<div class="one-comment ' + hex_md5(current_xid) + ' ' + type + new_class + '" data-id="' + encodeQuotes(current_id) + '">' +
547 '<div class="avatar-container" onclick="return ' + onclick + ';">' +
548 '<img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" />' +
551 '<div class="comment-container">' +
552 '<a href="#" onclick="return ' + onclick + ';" title="' + encodeQuotes(current_xid) + '" class="name">' + current_name.htmlEnc() + '</a>' +
553 '<span class="date">' + current_date.htmlEnc() + '</span>' +
556 '<p class="body">' + filterThisMessage(current_body, current_name, true) + '</p>' +
559 '<div class="clear"></div>' +
568 // Focus on the compose input
569 $(document).oneTime(10, function() {
570 $(path).find('.one-comment.compose input').focus();
575 $(path).find('.one-comment.compose').after(code);
578 $(path).find('.one-comment.new').slideDown('fast', function() {
579 adaptCommentMicroblog(id);
580 }).removeClass('new');
583 // Set the good widths
584 adaptCommentMicroblog(id);
588 getAvatar(users_xid[a], 'cache', 'true', 'forget');
591 if(owner_xid && owner_xid.match('@') && !existArrayValue(users_xid, owner_xid))
592 users_xid.push(owner_xid);
594 // Add the repeated from XID
595 if(repeat_xid && repeat_xid.match('@') && !existArrayValue(users_xid, repeat_xid))
596 users_xid.push(repeat_xid);
599 removeArrayValue(users_xid, getXID());
604 $(path).everyTime('60s', function() {
605 getCommentsMicroblog(server, node, id);
607 logThis('Updating comments node: ' + node + ' on ' + server + '...');
611 $(path).find('.one-comment.compose input').placeholder()
613 if((e.keyCode == 13) && $(this).val()) {
615 sendCommentMicroblog($(this).val(), server, node, id, users_xid, parent_data);
617 // Reset the input value
626 // Shows the microblog comments box
627 function showCommentsMicroblog(path, entityComments, nodeComments, tHash) {
628 // Do not display it twice!
629 if(path.find('div.comments').size())
632 // Generate an unique ID
633 var idComments = genID();
635 // Create comments container
636 path.find('div.comments-container').append(
637 '<div class="comments" data-id="' + encodeQuotes(idComments) + '">' +
638 '<div class="arrow talk-images"></div>' +
639 '<div class="comments-content">' +
640 '<a href="#" class="one-comment loading"><span class="icon talk-images"></span>' + _e("Show comments") + '</a>' +
646 path.find('div.comments a.one-comment').click(function() {
648 $(this).parent().html('<div class="one-comment loading"><span class="icon talk-images"></span>' + _e("Loading comments...") + '</div>');
651 getCommentsMicroblog(entityComments, nodeComments, idComments);
653 // Remove the comments from the DOM if click away
655 $('#channel').die('click');
657 $('#channel').live('click', function(evt) {
658 if(!$(evt.target).parents('.' + tHash).size()) {
659 $('#channel').die('click');
660 $('#channel .one-update div.comments-content').stopTime();
661 $('#channel .one-update div.comments').remove();
670 // Sends a comment on a given microblog comments node
671 function sendCommentMicroblog(value, server, node, id, notifiy_arr, parent_data) {
672 /* REF: http://xmpp.org/extensions/xep-0060.html#publisher-publish */
675 if(!value || !server || !node)
679 var date = getXMPPTime('utc');
680 var hash = hex_md5(value + date);
683 var iq = new JSJaCIQ();
686 iq.setID('set_' + genID() + '-' + id);
688 // PubSub main elements
689 var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
690 var publish = pubsub.appendChild(iq.buildNode('publish', {'node': node, 'xmlns': NS_PUBSUB}));
691 var item = publish.appendChild(iq.buildNode('item', {'id': hash, 'xmlns': NS_PUBSUB}));
692 var entry = item.appendChild(iq.buildNode('entry', {'xmlns': NS_ATOM}));
695 var Source = entry.appendChild(iq.buildNode('source', {'xmlns': NS_ATOM}));
696 var author = Source.appendChild(iq.buildNode('author', {'xmlns': NS_ATOM}));
697 author.appendChild(iq.buildNode('name', {'xmlns': NS_ATOM}, getName()));
698 author.appendChild(iq.buildNode('uri', {'xmlns': NS_ATOM}, 'xmpp:' + getXID()));
700 // Create the comment
701 entry.appendChild(iq.buildNode('content', {'type': 'text', 'xmlns': NS_ATOM}, value));
702 entry.appendChild(iq.buildNode('published', {'xmlns': NS_ATOM}, date));
706 // Handle this comment!
708 handleCommentsMicroblog(iq);
711 if(notifiy_arr && notifiy_arr.length) {
712 // XMPP link to the item
713 var href = 'xmpp:' + server + '?;node=' + encodeURIComponent(node) + ';item=' + encodeURIComponent(hash);
716 for(n in notifiy_arr)
717 sendNotification(notifiy_arr[n], 'comment', href, value, parent_data);
723 // Removes a given microblog comment item
724 function removeCommentMicroblog(server, node, id) {
725 /* REF: http://xmpp.org/extensions/xep-0060.html#publisher-delete */
727 // Remove the item from our DOM
728 $('.one-comment[data-id=' + id + ']').slideUp('fast', function() {
730 var parent_id = $(this).parents('div.comments').attr('data-id');
736 adaptCommentMicroblog(parent_id);
739 // Send the IQ to remove the item (and get eventual error callback)
740 var iq = new JSJaCIQ();
744 var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
745 var retract = pubsub.appendChild(iq.buildNode('retract', {'node': node, 'xmlns': NS_PUBSUB}));
746 retract.appendChild(iq.buildNode('item', {'id': id, 'xmlns': NS_PUBSUB}));
753 // Adapts the comment elements width
754 function adaptCommentMicroblog(id) {
755 var selector = $('div.comments[data-id=' + id + '] div.comments-content');
756 var selector_width = selector.width();
759 selector.find('.one-comment.compose input').css('width', selector_width - 60);
760 selector.find('.one-comment .comment-container').css('width', selector_width - 55);
763 // Handles the microblog of an user
764 function handleMicroblog(iq) {
765 // Get the from attribute of this IQ
766 var from = bareXID(getStanzaFrom(iq));
768 // Define the selector path
769 var selector = '#channel .top.individual input[name=';
771 // Is this request still alive?
772 if(from == $(selector + 'jid]').val()) {
773 var hash = hex_md5(from);
775 // Update the items counter
776 var old_count = parseInt($(selector + 'counter]').val());
777 $(selector + 'counter]').val(old_count + 20);
779 // Display the microblog
780 displayMicroblog(iq, from, hash, 'individual', 'request');
782 // Hide the waiting icon
784 waitMicroblog('sync');
786 waitMicroblog('unsync');
788 // Hide the 'more items' link?
789 if($(iq.getNode()).find('item').size() < old_count)
790 $('#channel .individual a.more').remove();
793 var comments_node = $('#channel .top.individual input[name=comments]').val();
795 if(comments_node && comments_node.match(/^xmpp:(.+)\?;node=(.+);item=(.+)/)) {
797 var comments_entity = RegExp.$1;
798 comments_node = decodeURIComponent(RegExp.$2);
801 var file_link = $('#channel .individual .one-update p.file a[data-node=' + comments_node + ']');
802 var entry_link = $('#channel .individual .one-update:has(*[data-node=' + comments_node + '])');
808 // Is it a microblog entry?
809 else if(entry_link.size()) {
810 showCommentsMicroblog(entry_link, comments_entity, comments_node);
811 entry_link.find('a.one-comment').click();
816 logThis('Microblog got: ' + from, 3);
819 // Resets the microblog elements
820 function resetMicroblog() {
822 $('#channel .individual .one-update div.comments-content').stopTime();
823 $('#channel .individual').remove();
824 $('#channel .mixed').show();
826 // Hide the waiting icon
828 waitMicroblog('sync');
830 waitMicroblog('unsync');
835 // Gets the user's microblog to check it exists
836 function getInitMicroblog() {
837 getMicroblog(getXID(), hex_md5(getXID()), true);
840 // Handles the user's microblog to create it in case of error
841 function handleInitMicroblog(iq) {
843 if((iq.getType() == 'error') && $(iq.getNode()).find('item-not-found').size()) {
844 // The node may not exist, create it!
845 setupMicroblog('', NS_URN_MBLOG, '1', '1000000', '', '', true);
847 logThis('Error while getting microblog, trying to reconfigure the Pubsub node!', 2);
851 // Requests an user's microblog
852 function requestMicroblog(xid, items, get_item, handler) {
853 // Ask the server the user's microblog
854 var iq = new JSJaCIQ();
858 var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
859 var ps_items = pubsub.appendChild(iq.buildNode('items', {'node': NS_URN_MBLOG, 'xmlns': NS_PUBSUB}));
861 // Request a particular item?
863 ps_items.appendChild(iq.buildNode('item', {'id': get_item, 'xmlns': NS_PUBSUB}));
865 ps_items.setAttribute('max_items', items);
868 con.send(iq, handler);
870 con.send(iq, handleMicroblog);
875 // Gets the microblog of an user
876 function getMicroblog(xid, hash, check) {
877 /* REF: http://xmpp.org/extensions/xep-0060.html#subscriber-retrieve */
879 logThis('Get the microblog: ' + xid, 3);
881 // Fire the wait event
882 waitMicroblog('fetch');
887 if(xid.match(/^xmpp:(.+)\?;node=(.+);item=(.+)/)) {
889 get_item = decodeURIComponent(RegExp.$3);
896 // Can display the individual channel?
897 if(!check && !exists('#channel .individual')) {
898 // Hide the mixed channel
899 $('#channel .mixed').hide();
901 // Get the channel title depending on the XID
906 cTitle = _e("Your channel");
908 cTitle = _e("Channel of") + ' ' + getBuddyName(xid).htmlEnc();
909 cShortcuts = '<div class="shortcuts">' +
910 '<a href="#" class="message talk-images" title="' + _e("Send him/her a message") + '" onclick="return composeInboxMessage(\'' + encodeOnclick(xid) + '\');"></a>' +
911 '<a href="#" class="chat talk-images" title="' + _e("Start a chat with him/her") + '" onclick="return checkChatCreate(\'' + encodeOnclick(xid) + '\', \'chat\');"></a>' +
912 '<a href="#" class="command talk-images" title="' + _e("Command") + '" onclick="return retrieveAdHoc(\'' + encodeOnclick(xid) + '\');"></a>' +
913 '<a href="#" class="profile talk-images" title="' + _e("Show user profile") + '" onclick="return openUserInfos(\'' + encodeOnclick(xid) + '\');"></a>' +
917 // Create a new individual channel
918 $('#channel .content.mixed').after(
919 '<div class="content individual microblog-' + hash + '">' +
920 '<a href="#" class="more home-images" onclick="return getMicroblog(\'' + encodeOnclick(xid) + '\', \'' + encodeOnclick(hash) + '\');">' + _e("More notices...") + '</a>' +
925 '<div class="top individual ' + hash + '">' +
926 '<div class="avatar-container">' +
927 '<img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" />' +
930 '<div class="update">' +
931 '<h2>' + cTitle + '</h2>' +
932 '<a href="#" onclick="return resetMicroblog();">« ' + _e("Previous") + '</a>' +
937 '<input type="hidden" name="jid" value="' + encodeQuotes(xid) + '" />' +
938 '<input type="hidden" name="counter" value="20" />' +
942 // Display the user avatar
943 getAvatar(xid, 'cache', 'true', 'forget');
946 // Get the number of items to retrieve
950 items = $('#channel .top.individual input[name=counter]').val();
954 requestMicroblog(xid, items, get_item, handleInitMicroblog);
956 requestMicroblog(xid, items, get_item, handleMicroblog);
961 // Show a given microblog waiting status
962 function waitMicroblog(type) {
963 // First hide all the infos elements
964 $('#channel .footer div').hide();
966 // Display the good one
967 $('#channel .footer div.' + type).show();
969 // Depending on the type, disable/enable certain tools
970 var selector = $('#channel .top input[name=microblog_body]');
973 selector.attr('disabled', true);
974 else if(type == 'sync')
975 $(document).oneTime(10, function() {
976 selector.removeAttr('disabled').focus();
980 // Setups a new microblog
981 function setupMicroblog(entity, node, persist, maximum, access, publish, create) {
982 /* REF: http://xmpp.org/extensions/xep-0060.html#owner-create-and-configure */
984 // Create the PubSub node
985 var iq = new JSJaCIQ();
988 // Any external entity?
994 var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
995 pubsub.appendChild(iq.buildNode('create', {'xmlns': NS_PUBSUB, 'node': node}));
999 var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB_OWNER});
1002 var configure = pubsub.appendChild(iq.buildNode('configure', {'node': node, 'xmlns': NS_PUBSUB}));
1003 var x = configure.appendChild(iq.buildNode('x', {'xmlns': NS_XDATA, 'type': 'submit'}));
1005 var field1 = x.appendChild(iq.buildNode('field', {'var': 'FORM_TYPE', 'type': 'hidden', 'xmlns': NS_XDATA}));
1006 field1.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, NS_PUBSUB_NC));
1010 var field2 = x.appendChild(iq.buildNode('field', {'var': 'pubsub#persist_items', 'xmlns': NS_XDATA}));
1011 field2.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, persist));
1016 var field3 = x.appendChild(iq.buildNode('field', {'var': 'pubsub#max_items', 'xmlns': NS_XDATA}));
1017 field3.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, maximum));
1022 var field4 = x.appendChild(iq.buildNode('field', {'var': 'pubsub#access_model', 'xmlns': NS_XDATA}));
1023 field4.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, access));
1028 var field5 = x.appendChild(iq.buildNode('field', {'var': 'pubsub#publish_model', 'xmlns': NS_XDATA}));
1029 field5.appendChild(iq.buildNode('value', {'xmlns': NS_XDATA}, publish));
1035 // Gets the microblog configuration
1036 function getConfigMicroblog() {
1037 // Lock the microblog options
1038 $('#persistent, #maxnotices').attr('disabled', true);
1040 // Get the microblog configuration
1041 var iq = new JSJaCIQ();
1044 var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB_OWNER});
1045 pubsub.appendChild(iq.buildNode('configure', {'node': NS_URN_MBLOG, 'xmlns': NS_PUBSUB_OWNER}));
1047 con.send(iq, handleGetConfigMicroblog);
1050 // Handles the microblog configuration
1051 function handleGetConfigMicroblog(iq) {
1052 // Reset the options stuffs
1053 waitOptions('microblog');
1055 // Unlock the microblog options
1056 $('#persistent, #maxnotices').removeAttr('disabled');
1058 // End if not a result
1059 if(!iq || (iq.getType() != 'result'))
1062 // Initialize the values
1063 var selector = $(iq.getNode());
1064 var persistent = '0';
1065 var maxnotices = '1000000';
1068 var xPersistent = selector.find('field[var=pubsub#persist_items] value:first').text();
1069 var xMaxnotices = selector.find('field[var=pubsub#max_items] value:first').text();
1073 persistent = xPersistent;
1076 maxnotices = xMaxnotices;
1078 // Change the maxnotices value
1079 switch(maxnotices) {
1089 maxnotices = '1000000';
1093 // Apply persistent value
1094 if(persistent == '0')
1095 $('#persistent').attr('checked', false);
1097 $('#persistent').attr('checked', true);
1099 // Apply maxnotices value
1100 $('#maxnotices').val(maxnotices);
1103 // Handles the user's microblog
1104 function handleMyMicroblog(packet) {
1105 // Reset the entire form
1106 $('#channel .top input[name=microblog_body]').removeAttr('disabled').val('');
1107 $('#channel .top input[name=microblog_body]').placeholder();
1108 unattachMicroblog();
1111 handleErrorReply(packet);
1114 // Performs the microblog sender checks
1115 function sendMicroblog() {
1116 logThis('Send a new microblog item', 3);
1118 // Avoid nasty errors
1121 var selector = $('#channel .top input[name=microblog_body]');
1122 var body = trim(selector.val());
1124 // Sufficient parameters
1126 // Disable & blur our input
1127 selector.attr('disabled', true).blur();
1137 $('#attach .one-file').each(function() {
1139 fName.push($(this).find('a.link').text());
1140 fType.push($(this).attr('data-type'));
1141 fLength.push($(this).attr('data-length'));
1142 fURL.push($(this).find('a.link').attr('href'));
1143 fThumb.push($(this).attr('data-thumb'));
1146 // Containing YouTube videos?
1147 var yt_matches = body.match(/(\w{3,5})(:)(\S+)((\.youtube\.com\/watch(\?v|\?\S+v|\#\!v|\#\!\S+v)\=)|(youtu\.be\/))([^& ]+)((&\S)|(&\S)|\s|$)/gim);
1149 for(y in yt_matches) {
1151 fType.push('text/html');
1153 fURL.push(trim(yt_matches[y]));
1154 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');
1157 // Send the message on the XMPP network
1158 publishMicroblog(body, fName, fURL, fType, fLength, fThumb);
1162 // Return false (security)
1168 // Publishes a given microblog item
1169 function publishMicroblog(body, attachedname, attachedurl, attachedtype, attachedlength, attachedthumb, repeat, comments_entity, comments_node, comments_entity_file, comments_node_file) {
1170 /* REF: http://xmpp.org/extensions/xep-0277.html */
1172 // Generate some values
1173 var time = getXMPPTime('utc');
1174 var id = hex_md5(body + time);
1175 var nick = getName();
1178 // Define repeat options
1179 var author_nick = nick;
1180 var author_xid = xid;
1182 if(repeat && repeat.length) {
1183 author_nick = repeat[0];
1184 author_xid = repeat[1];
1187 // Define comments options
1188 var node_create = false;
1190 if(!comments_entity || !comments_node) {
1192 comments_entity = HOST_PUBSUB;
1193 comments_node = NS_URN_MBLOG + ':comments/' + id;
1196 if(!comments_entity_file)
1197 comments_entity_file = [];
1198 if(!comments_node_file)
1199 comments_node_file = [];
1201 // Don't create another comments node if only 1 file is attached
1202 if(attachedurl && (attachedurl.length == 1) && (!comments_entity_file[0] || !comments_node_file[0])) {
1203 comments_entity_file = [comments_entity];
1204 comments_node_file = [comments_node];
1208 var iq = new JSJaCIQ();
1212 // Create the main XML nodes/childs
1213 var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
1214 var publish = pubsub.appendChild(iq.buildNode('publish', {'node': NS_URN_MBLOG, 'xmlns': NS_PUBSUB}));
1215 var item = publish.appendChild(iq.buildNode('item', {'id': id, 'xmlns': NS_PUBSUB}));
1216 var entry = item.appendChild(iq.buildNode('entry', {'xmlns': NS_ATOM}));
1218 // Create the XML source childs
1219 var Source = entry.appendChild(iq.buildNode('source', {'xmlns': NS_ATOM}));
1220 var author = Source.appendChild(iq.buildNode('author', {'xmlns': NS_ATOM}));
1221 author.appendChild(iq.buildNode('name', {'xmlns': NS_ATOM}, author_nick));
1222 author.appendChild(iq.buildNode('uri', {'xmlns': NS_ATOM}, 'xmpp:' + author_xid));
1224 // Create the XML entry childs
1225 entry.appendChild(iq.buildNode('content', {'type': 'text', 'xmlns': NS_ATOM}, body));
1226 entry.appendChild(iq.buildNode('published', {'xmlns': NS_ATOM}, time));
1227 entry.appendChild(iq.buildNode('updated', {'xmlns': NS_ATOM}, time));
1228 entry.appendChild(iq.buildNode('link', {
1230 'href': 'xmpp:' + xid + '?;node=' + encodeURIComponent(NS_URN_MBLOG) + ';item=' + encodeURIComponent(id),
1234 // Create the attached files nodes
1235 for(var i = 0; i < attachedurl.length; i++) {
1240 // Append a new file element
1241 var file = entry.appendChild(iq.buildNode('link', {'xmlns': NS_ATOM, 'rel': 'enclosure', 'href': attachedurl[i]}));
1245 file.setAttribute('title', attachedname[i]);
1247 file.setAttribute('type', attachedtype[i]);
1248 if(attachedlength[i])
1249 file.setAttribute('length', attachedlength[i]);
1252 if(attachedthumb[i])
1253 file.appendChild(iq.buildNode('link', {'xmlns': NS_URN_MBLOG, 'rel': 'self', 'title': 'thumb', 'type': attachedtype[i], 'href': attachedthumb[i]}));
1255 // Any comments node?
1256 if(!comments_entity_file[i] || !comments_node_file[i]) {
1258 comments_entity_file[i] = HOST_PUBSUB;
1259 comments_node_file[i] = NS_URN_MBLOG + ':comments/' + hex_md5(attachedurl[i] + attachedname[i] + attachedtype[i] + attachedlength[i] + time);
1262 setupMicroblog(comments_entity_file[i], comments_node_file[i], '1', '1000000', 'open', 'open', true);
1265 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])}));
1268 // Create the comments child
1269 entry.appendChild(iq.buildNode('link', {'xmlns': NS_ATOM, 'rel': 'replies', 'title': 'comments', 'href': 'xmpp:' + comments_entity + '?;node=' + encodeURIComponent(comments_node)}));
1271 // Create the geoloc child
1272 var geoloc_xml = getDB('geolocation', 'now');
1275 // Create two position arrays
1276 var geo_names = ['lat', 'lon', 'country', 'countrycode', 'region', 'postalcode', 'locality', 'street', 'building', 'text', 'uri', 'timestamp'];
1277 var geo_values = parsePosition(XMLFromString(geoloc_xml));
1280 var geoloc = entry.appendChild(iq.buildNode('geoloc', {'xmlns': NS_GEOLOC}));
1282 // Append the geoloc content
1283 for(var g = 0; g < geo_names.length; g++) {
1284 if(geo_names[g] && geo_values[g])
1285 geoloc.appendChild(iq.buildNode(geo_names[g], {'xmlns': NS_GEOLOC}, geo_values[g]));
1290 con.send(iq, handleMyMicroblog);
1292 // Create the XML comments PubSub nodes
1294 setupMicroblog(comments_entity, comments_node, '1', '1000000', 'open', 'open', true);
1299 // Attaches a file to a microblog post
1300 function attachMicroblog() {
1302 var attach_options = {
1304 beforeSubmit: waitMicroblogAttach,
1305 success: handleMicroblogAttach
1308 // Upload form submit event
1309 $('#attach').submit(function() {
1310 if(!exists('#attach .wait') && $('#attach input[type=file]').val())
1311 $(this).ajaxSubmit(attach_options);
1316 // Upload input change event
1317 $('#attach input[type=file]').change(function() {
1318 if(!exists('#attach .wait') && $(this).val())
1319 $('#attach').ajaxSubmit(attach_options);
1325 // Unattaches a microblog file
1326 function unattachMicroblog(id) {
1327 // Individual removal?
1329 $('#attach .one-file[data-id=' + id + ']').remove();
1331 $('#attach .one-file').remove();
1333 // Must enable the popup again?
1334 if(!exists('#attach .one-file')) {
1335 // Restore the bubble class
1336 $('#attach').addClass('bubble');
1338 // Enable the bubble click events
1340 $('#attach').hide();
1341 showBubble('#attach');
1351 // Wait event for file attaching
1352 function waitMicroblogAttach() {
1353 // Append the wait icon
1354 $('#attach input[type=submit]').after('<div class="wait wait-medium"></div>');
1357 $('#attach').removeClass('bubble');
1360 // Success event for file attaching
1361 function handleMicroblogAttach(responseXML) {
1363 var dData = $(responseXML).find('jappix');
1365 // Process the returned data
1366 if(!dData.find('error').size()) {
1367 // Do not allow this bubble to be hidden
1368 $('#attach').removeClass('bubble');
1370 // Get the file values
1371 var fName = dData.find('title').text();
1372 var fType = dData.find('type').text();
1373 var fLength = dData.find('length').text();
1374 var fURL = dData.find('href').text();
1375 var fThumb = dData.find('thumb').text();
1377 // Generate a file ID
1378 var fID = hex_md5(fURL);
1381 $('#attach .attach-subitem').append(
1382 '<div class="one-file" data-type="' + encodeQuotes(fType) + '" data-length="' + encodeQuotes(fLength) + '" data-thumb="' + encodeQuotes(fThumb) + '" data-id="' + fID + '">' +
1383 '<a class="remove talk-images" href="#" title="' + encodeQuotes(_e("Unattach the file")) + '"></a>' +
1384 '<a class="link" href="' + encodeQuotes(fURL) + '" target="_blank">' + fName.htmlEnc() + '</a>' +
1389 $('#attach .one-file[data-id=' + fID + '] a.remove').click(function() {
1390 return unattachMicroblog(fID);
1393 logThis('File attached.', 3);
1400 // Unlock the bubble?
1401 if(!exists('#attach .one-file')) {
1402 $('#attach').addClass('bubble').hide();
1404 // Show the bubble again!
1405 showBubble('#attach');
1408 logThis('Error while attaching the file: ' + dData.find('error').text(), 1);
1411 // Reset the attach bubble
1412 $('#attach input[type=file]').val('');
1413 $('#attach .wait').remove();
1415 // Focus on the text input
1416 $(document).oneTime(10, function() {
1417 $('#channel .top input[name=microblog_body]').focus();
1421 // Shows the microblog of an user from his infos
1422 function fromInfosMicroblog(xid, hash) {
1423 // Renitialize the channel
1426 // Switch to the channel
1427 switchChan('channel');
1429 // Get the microblog
1430 getMicroblog(xid, hash);
1434 function launchMicroblog() {
1436 $('#channel .top input[name=microblog_body]').keyup(function(e) {
1437 // Enter pressed: send the microblog notice
1438 if((e.keyCode == 13) && !exists('#attach .wait'))
1439 return sendMicroblog();
1445 // Microblog file attacher