]> git.mxchange.org Git - friendica-addons.git/blob - jappixmini/jappix/js/microblog.js
ceff07ab003d4f2bc8beacbf55873ea0cce80b01
[friendica-addons.git] / jappixmini / jappix / js / microblog.js
1 /*
2
3 Jappix - An open social platform
4 These are the microblog JS scripts for Jappix
5
6 -------------------------------------------------
7
8 License: AGPL
9 Author: Vanaryon
10 Last revision: 03/12/11
11
12 */
13
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'));
18         else
19                 tFName.push('');
20         
21         if($(selector).attr('href'))
22                 tFURL.push($(selector).attr('href'));
23         else
24                 tFURL.push('');
25         
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'));
28         else
29                 tFThumb.push('');
30         
31         if($(selector).attr('source'))
32                 tFSource.push($(selector).attr('source'));
33         else
34                 tFSource.push('');
35         
36         if($(selector).attr('type'))
37                 tFType.push($(selector).attr('type'));
38         else
39                 tFType.push('');
40         
41         if($(selector).attr('length'))
42                 tFLength.push($(selector).attr('length'));
43         else
44                 tFLength.push('');
45         
46         // Comments?
47         var comments_href_c = $(selector).find('link[rel=replies][title=comments_file]:first').attr('href');
48         
49         if(comments_href_c && comments_href_c.match(/^xmpp:(.+)\?;node=(.+)/)) {
50                 tFEComments.push(RegExp.$1);
51                 tFNComments.push(decodeURIComponent(RegExp.$2));
52         }
53         
54         else {
55                 tFEComments.push('');
56                 tFNComments.push('');
57         }
58 }
59
60 // Displays a given microblog item
61 function displayMicroblog(packet, from, hash, mode, way) {
62         // Get some values
63         var iParse = $(packet.getNode()).find('items item');
64         
65         iParse.each(function() {
66                 // Initialize
67                 var tTitle, tFiltered, tTime, tDate, tStamp, tBody, tName, tID, tHash, tIndividual, tFEClick;
68                 
69                 // Arrays
70                 var tFName = [];
71                 var tFURL = [];
72                 var tFThumb = [];
73                 var tFSource = [];
74                 var tFType = [];
75                 var tFLength = [];
76                 var tFEComments = [];
77                 var tFNComments = [];
78                 var aFURL = [];
79                 var aFCat = [];
80                 
81                 // Get the values
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);
87                 
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);
91                 });
92                 
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);
96                 });
97                 
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;
101                 
102                 if(!uRepeat[0])
103                         uRepeat = [getBuddyName(from), uRepeat[1]];
104                 if(!uRepeat[1])
105                         uRepeat = [uRepeat[0], from];
106                 
107                 // Repeated?
108                 if(uRepeat[1] != from)
109                         uRepeated = true;
110                 
111                 // Get the comments node
112                 var entityComments, nodeComments;
113                 
114                 // Get the comments
115                 var comments_href = $(this).find('link[title=comments]:first').attr('href');
116                 
117                 if(comments_href && comments_href.match(/^xmpp:(.+)\?;node=(.+)/)) {
118                         entityComments = RegExp.$1;
119                         nodeComments = decodeURIComponent(RegExp.$2);
120                 }
121                 
122                 // No comments node?
123                 if(!entityComments || !nodeComments) {
124                         entityComments = '';
125                         nodeComments = '';
126                 }
127                 
128                 // Get the stamp & time
129                 if(tDate) {
130                         tStamp = extractStamp(Date.jab2date(tDate));
131                         tTime = relativeDate(tDate);
132                 }
133                 
134                 else {
135                         tStamp = getTimeStamp();
136                         tTime = '';
137                 }
138                 
139                 // Get the item geoloc
140                 var tGeoloc = '';
141                 var sGeoloc = $(this).find('geoloc[xmlns=' + NS_GEOLOC + ']:first');
142                 var gLat = sGeoloc.find('lat').text();
143                 var gLon = sGeoloc.find('lon').text();
144                 
145                 if(gLat && gLon) {
146                         tGeoloc += '<a class="geoloc talk-images" href="http://maps.google.com/?q=' + encodeQuotes(gLat) + ',' + encodeQuotes(gLon) + '" target="_blank">';
147                         
148                         // Human-readable name?
149                         var gHuman = humanPosition(
150                                            sGeoloc.find('locality').text(),
151                                            sGeoloc.find('region').text(),
152                                            sGeoloc.find('country').text()
153                                           );
154                         
155                         if(gHuman)
156                                 tGeoloc += gHuman.htmlEnc();
157                         else
158                                 tGeoloc += gLat.htmlEnc() + '; ' + gLon.htmlEnc();
159                         
160                         tGeoloc += '</a>';
161                 }
162                 
163                 // Retrieve the message body
164                 tTitle = $(this).find('content[type=text]').text();
165                 
166                 if(!tTitle) {
167                         // Legacy?
168                         tTitle = $(this).find('title:not(source > title)').text();
169                         
170                         // Last chance?
171                         if(!tTitle)
172                                 tTitle = tBody;
173                 }
174                 
175                 // Trim the content
176                 tTitle = trim(tTitle);
177                 
178                 // Any content?
179                 if(tTitle) {
180                         // Apply links to message body
181                         tFiltered = filterThisMessage(tTitle, tName.htmlEnc(), true);
182                         
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="" />' + 
188                                                 '</div>' + 
189                                         '</div>' + 
190                                         
191                                         '<div class="body">' + 
192                                                 '<p>';
193                         
194                         // Is it a repeat?
195                         if(uRepeated)
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>';
197                         
198                         html += '<b title="' + from + '" class="name">' + tName.htmlEnc() + '</b> <span>' + tFiltered + '</span></p>' + 
199                                 '<p class="infos">' + tTime + tGeoloc + '</p>';
200                         
201                         // Any file to display?
202                         if(tFURL.length)
203                                 html += '<p class="file">';
204                         
205                         // Generate an array of the files URL
206                         for(var a = 0; a < tFURL.length; a++) {
207                                 // Not enough data?
208                                 if(!tFURL[a])
209                                         continue;
210                                 
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\/))([^& ]+)((&amp;\S)|(&\S)|\s|$)/gim)) {
213                                         aFURL.push(trim(RegExp.$8));
214                                         aFCat.push('youtube');
215                                 }
216                                 
217                                 else if(canIntegrateBox(explodeThis('/', tFType[a], 1))) {
218                                         aFURL.push(tFURL[a]);
219                                         aFCat.push(fileCategory(explodeThis('/', tFType[a], 1)));
220                                 }
221                         }
222                         
223                         // Add each file code
224                         for(var f = 0; f < tFURL.length; f++) {
225                                 // Not enough data?
226                                 if(!tFURL[f])
227                                         continue;
228                                 
229                                 // Get the file type
230                                 var tFExt = explodeThis('/', tFType[f], 1);
231                                 var tFCat = fileCategory(tFExt);
232                                 var tFLink = tFURL[f];
233                                 
234                                 // Youtube video?
235                                 if(tFLink.match(/(\w{3,5})(:)(\S+)((\.youtube\.com\/watch(\?v|\?\S+v|\#\!v|\#\!\S+v)\=)|(youtu\.be\/))([^& ]+)((&amp;\S)|(&\S)|\s|$)/gim)) {
236                                         tFLink = trim(RegExp.$8);
237                                         tFCat = 'youtube';
238                                 }
239                                 
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\');" ';
243                                 else
244                                         tFEClick = '';
245                                 
246                                 // Any thumbnail?
247                                 if(tFThumb[f])
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>';
249                                 else
250                                         html += '<a class="' + encodeQuotes(tFCat) + ' link talk-images" ' + tFEClick + 'href="' + encodeQuotes(tFURL[f]) + '" target="_blank" data-node="' + encodeQuotes(tFNComments) + '">' + tFName[f].htmlEnc() + '</a>';
251                         }
252                         
253                         if(tFURL.length)
254                                 html += '</p>';
255                         
256                         // It's my own notice, we can remove it!
257                         if(from == getXID())
258                                 html += '<a href="#" onclick="return removeMicroblog(\'' + encodeOnclick(tID) + '\', \'' + encodeOnclick(tHash) + '\');" title="' + _e("Remove this notice") + '" class="mbtool remove talk-images"></a>';
259                         
260                         // Notice from another user
261                         else {
262                                 // User profile
263                                 html += '<a href="#" title="' + _e("View profile") + '" class="mbtool profile talk-images" onclick="return openUserInfos(\'' + encodeOnclick(from) + '\');"></a>';
264                                 
265                                 // If PEP is enabled
266                                 if(enabledPEP())
267                                         html += '<a href="#" title="' + _e("Repeat this notice") + '" class="mbtool repost talk-images"></a>';
268                         }
269                         
270                         html += '</div><div class="comments-container" data-node="' + encodeQuotes(nodeComments) + '"></div></div>';
271                         
272                         // Mixed mode
273                         if((mode == 'mixed') && !exists('.mixed .' + tHash)) {
274                                 // Remove the old element
275                                 if(way == 'push')
276                                         $('#channel .content.mixed .one-update.update_' + hash).remove();
277                                 
278                                 // Get the nearest element
279                                 var nearest = sortElementByStamp(tStamp, '#channel .mixed .one-update');
280                                 
281                                 // Append the content at the right position (date relative)
282                                 if(nearest == 0)
283                                         $('#channel .content.mixed').append(html);
284                                 else
285                                         $('#channel .one-update[data-stamp=' + nearest + ']:first').before(html);
286                                 
287                                 // Show the new item
288                                 if(way == 'push')
289                                         $('#channel .content.mixed .one-update.' + tHash).fadeIn('fast');
290                                 else
291                                         $('#channel .content.mixed .one-update.' + tHash).show();
292                                 
293                                 // Remove the old notices to make the DOM lighter
294                                 var oneUpdate = '#channel .content.mixed .one-update';
295                                 
296                                 if($(oneUpdate).size() > 80)
297                                         $(oneUpdate + ':last').remove();
298                                 
299                                 // Click event on avatar/name
300                                 $('.mixed .' + tHash + ' .avatar-container, .mixed .' + tHash + ' .body b').click(function() {
301                                         getMicroblog(from, hash);
302                                 });
303                         }
304                         
305                         // Individual mode
306                         tIndividual = '#channel .content.individual.microblog-' + hash;
307                         
308                         // Can append individual content?
309                         var can_individual = true;
310                         
311                         if($('#channel .top.individual input[name=comments]').val() && exists(tIndividual + ' .one-update'))
312                                 can_individual = false;
313                         
314                         if(can_individual && exists(tIndividual) && !exists('.individual .' + tHash)) {
315                                 if(mode == 'mixed')
316                                         $(tIndividual).prepend(html);
317                                 else
318                                         $(tIndividual + ' a.more').before(html);
319                                 
320                                 // Show the new item
321                                 if(way == 'push')
322                                         $('#channel .content.individual .one-update.' + tHash).fadeIn('fast');
323                                 else
324                                         $('#channel .content.individual .one-update.' + tHash).show();
325                                 
326                                 // Make 'more' link visible
327                                 $(tIndividual + ' a.more').css('visibility', 'visible');
328                                 
329                                 // Click event on name (if not me!)
330                                 if(from != getXID())
331                                         $('.individual .' + tHash + ' .avatar-container, .individual .' + tHash + ' .body b').click(function() {
332                                                 checkChatCreate(from, 'chat');
333                                         });
334                         }
335                         
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);
339                         })
340                         
341                         .attr('data-event', 'true');
342                         
343                         // Apply the hover event
344                         if(nodeComments)
345                                 $('.' + mode + ' .' + tHash).hover(function() {
346                                         showCommentsMicroblog($(this), entityComments, nodeComments, tHash);
347                                 }, function() {
348                                         if($(this).find('div.comments a.one-comment.loading').size())
349                                                 $(this).find('div.comments').remove();
350                                 });
351                 }
352         });
353         
354         // Display the avatar of this buddy
355         getAvatar(from, 'cache', 'true', 'forget');
356 }
357
358 // Removes a given microblog item
359 function removeMicroblog(id, hash) {
360         /* REF: http://xmpp.org/extensions/xep-0060.html#publisher-delete */
361         
362         // Initialize
363         var selector = $('.' + hash);
364         var get_last = false;
365         
366         // Get the latest item for the mixed mode
367         if(exists('#channel .content.mixed .' + hash))
368                 get_last = true;
369         
370         // Remove the item from our DOM
371         selector.fadeOut('fast', function() {
372                 $(this).remove();
373         });
374         
375         // Send the IQ to remove the item (and get eventual error callback)
376         var iq = new JSJaCIQ();
377         iq.setType('set');
378         
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}));
382         
383         if(get_last)
384                 con.send(iq, handleRemoveMicroblog);
385         else
386                 con.send(iq, handleErrorReply);
387         
388         return false;
389 }
390
391 // Handles the microblog item removal
392 function handleRemoveMicroblog(iq) {
393         // Handle the error reply
394         handleErrorReply(iq);
395         
396         // Get the latest item
397         requestMicroblog(getXID(), '1', false, handleUpdateRemoveMicroblog);
398 }
399
400 // Handles the microblog update
401 function handleUpdateRemoveMicroblog(iq) {
402         // Error?
403         if(iq.getType() == 'error')
404                 return;
405         
406         // Initialize
407         var xid = bareXID(getStanzaFrom(iq));
408         var hash = hex_md5(xid);
409         
410         // Display the item!
411         displayMicroblog(iq, xid, hash, 'mixed', 'push');
412 }
413
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 */
417         
418         var iq = new JSJaCIQ();
419         iq.setType('get');
420         iq.setID('get_' + genID() + '-' + id);
421         iq.setTo(server);
422         
423         var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
424         pubsub.appendChild(iq.buildNode('items', {'node': node, 'xmlns': NS_PUBSUB}));
425         
426         con.send(iq, handleCommentsMicroblog);
427         
428         return false;
429 }
430
431 // Handles a microblog comments node items
432 function handleCommentsMicroblog(iq) {
433         // Path
434         var id = explodeThis('-', iq.getID(), 1);
435         var path = 'div.comments[data-id=' + id + '] div.comments-content';
436         
437         // Does not exist?
438         if(!exists(path))
439                 return false;
440         
441         // Any error?
442         if(handleErrorReply(iq)) {
443                 $(path).html('<div class="one-comment loading">' + _e("Could not get the comments!") + '</div>');
444                 
445                 return false;
446         }
447         
448         // Initialize
449         var data = iq.getNode();
450         var server = bareXID(getStanzaFrom(iq));
451         var node = $(data).find('items:first').attr('node');
452         var users_xid = [];
453         var code = '';
454         
455         // No node?
456         if(!node)
457                 node = $(data).find('publish:first').attr('node');
458         
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')];
462         
463         // Get the owner XID
464         var owner_xid = parent_select.attr('data-xid');
465         var repeat_xid = parent_select.find('a.repeat').attr('data-xid');
466         
467         // Must we create the complete DOM?
468         var complete = true;
469         
470         if($(path).find('.one-comment.compose').size())
471                 complete = false;
472         
473         // Add the comment tool
474         if(complete)
475                 code += '<div class="one-comment compose">' + 
476                                 '<span class="icon talk-images"></span><input type="text" placeholder="' + _e("Type your comment here...") + '" />' + 
477                         '</div>';
478         
479         // Append the comments
480         $(data).find('item').each(function() {
481                 // Get comment
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);
488                 
489                 // Legacy?
490                 if(!current_body)
491                         current_body = $(this).find('title:not(source > title)').text();
492                 
493                 // Yet displayed? (continue the loop)
494                 if($(path).find('.one-comment[data-id=' + current_id + ']').size())
495                         return;
496                 
497                 // No XID?
498                 if(!current_xid) {
499                         current_xid = '';
500                         
501                         if(!current_name)
502                                 current_name = _e("unknown");
503                 }
504                 
505                 else if(!current_name || (current_bname != getXIDNick(current_xid)))
506                         current_name = current_bname;
507                 
508                 // Any date?
509                 if(current_date)
510                         current_date = relativeDate(current_date);
511                 else
512                         current_date = getCompleteTime();
513                 
514                 // Click event
515                 var onclick = 'false';
516                 
517                 if(current_xid != getXID())
518                         onclick = 'checkChatCreate(\'' + encodeOnclick(current_xid) + '\', \'chat\')';
519                 
520                 // If this is my comment, add a marker
521                 var type = 'him';
522                 var marker = '';
523                 var remove = '';
524                 
525                 if(current_xid == getXID()) {
526                         type = 'me';
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>';
529                 }
530                 
531                 // New comment?
532                 var new_class = '';
533                 
534                 if(!complete)
535                         new_class = ' new';
536                 
537                 // Add the comment
538                 if(current_body) {
539                         // Add the XID
540                         if(!existArrayValue(users_xid, current_xid))
541                                 users_xid.push(current_xid);
542                         
543                         // Add the HTML code
544                         code += '<div class="one-comment ' + hex_md5(current_xid) + ' ' + type + new_class + '" data-id="' + encodeQuotes(current_id) + '">' + 
545                                         marker + 
546                                         
547                                         '<div class="avatar-container" onclick="return ' + onclick + ';">' + 
548                                                 '<img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" />' + 
549                                         '</div>' + 
550                                         
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>' + 
554                                                 remove + 
555                                         
556                                                 '<p class="body">' + filterThisMessage(current_body, current_name, true) + '</p>' + 
557                                         '</div>' + 
558                                         
559                                         '<div class="clear"></div>' + 
560                                 '</div>';
561                 }
562         });
563         
564         // Add the HTML
565         if(complete) {
566                 $(path).html(code);
567                 
568                 // Focus on the compose input
569                 $(document).oneTime(10, function() {
570                         $(path).find('.one-comment.compose input').focus();
571                 });
572         }
573         
574         else {
575                 $(path).find('.one-comment.compose').after(code);
576                 
577                 // Beautiful effect
578                 $(path).find('.one-comment.new').slideDown('fast', function() {
579                         adaptCommentMicroblog(id);
580                 }).removeClass('new');
581         }
582         
583         // Set the good widths
584         adaptCommentMicroblog(id);
585         
586         // Get the avatars
587         for(a in users_xid)
588                 getAvatar(users_xid[a], 'cache', 'true', 'forget');
589         
590         // Add the owner XID
591         if(owner_xid && owner_xid.match('@') && !existArrayValue(users_xid, owner_xid))
592                 users_xid.push(owner_xid);
593         
594         // Add the repeated from XID
595         if(repeat_xid && repeat_xid.match('@') && !existArrayValue(users_xid, repeat_xid))
596                 users_xid.push(repeat_xid);
597         
598         // Remove my own XID
599         removeArrayValue(users_xid, getXID());
600         
601         // DOM events
602         if(complete) {
603                 // Update timer
604                 $(path).everyTime('60s', function() {
605                         getCommentsMicroblog(server, node, id);
606                         
607                         logThis('Updating comments node: ' + node + ' on ' + server + '...');
608                 });
609                 
610                 // Input key event
611                 $(path).find('.one-comment.compose input').placeholder()
612                                      .keyup(function(e) {
613                                                 if((e.keyCode == 13) && $(this).val()) {
614                                                         // Send the comment!
615                                                         sendCommentMicroblog($(this).val(), server, node, id, users_xid, parent_data);
616                                                         
617                                                         // Reset the input value
618                                                         $(this).val('');
619                                                         
620                                                         return false;
621                                                 }
622                                      });
623         }
624 }
625
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())
630                 return;
631         
632         // Generate an unique ID
633         var idComments = genID();
634         
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>' + 
641                         '</div>' + 
642                 '</div>'
643         );
644         
645         // Click event
646         path.find('div.comments a.one-comment').click(function() {
647                 // Set loading info
648                 $(this).parent().html('<div class="one-comment loading"><span class="icon talk-images"></span>' + _e("Loading comments...") + '</div>');
649                 
650                 // Request comments
651                 getCommentsMicroblog(entityComments, nodeComments, idComments);
652                 
653                 // Remove the comments from the DOM if click away
654                 if(tHash) {
655                         $('#channel').die('click');
656                         
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();
662                                 }
663                         });
664                 }
665                 
666                 return false;
667         });
668 }
669
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 */
673         
674         // Not enough data?
675         if(!value || !server || !node)
676                 return false;
677         
678         // Get some values
679         var date = getXMPPTime('utc');
680         var hash = hex_md5(value + date);
681         
682         // New IQ
683         var iq = new JSJaCIQ();
684         iq.setType('set');
685         iq.setTo(server);
686         iq.setID('set_' + genID() + '-' + id);
687         
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}));
693         
694         // Author infos
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()));
699         
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));
703         
704         con.send(iq);
705         
706         // Handle this comment!
707         iq.setFrom(server);
708         handleCommentsMicroblog(iq);
709         
710         // Notify users
711         if(notifiy_arr && notifiy_arr.length) {
712                 // XMPP link to the item
713                 var href = 'xmpp:' + server + '?;node=' + encodeURIComponent(node) + ';item=' + encodeURIComponent(hash);
714                 
715                 // Loop!
716                 for(n in notifiy_arr)
717                         sendNotification(notifiy_arr[n], 'comment', href, value, parent_data);
718         }
719         
720         return false;
721 }
722
723 // Removes a given microblog comment item
724 function removeCommentMicroblog(server, node, id) {
725         /* REF: http://xmpp.org/extensions/xep-0060.html#publisher-delete */
726         
727         // Remove the item from our DOM
728         $('.one-comment[data-id=' + id + ']').slideUp('fast', function() {
729                 // Get the parent ID
730                 var parent_id = $(this).parents('div.comments').attr('data-id');
731                 
732                 // Remove it!
733                 $(this).remove();
734                 
735                 // Adapt the width
736                 adaptCommentMicroblog(parent_id);
737         });
738         
739         // Send the IQ to remove the item (and get eventual error callback)
740         var iq = new JSJaCIQ();
741         iq.setType('set');
742         iq.setTo(server);
743         
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}));
747         
748         con.send(iq);
749         
750         return false;
751 }
752
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();
757         
758         // Change widths
759         selector.find('.one-comment.compose input').css('width', selector_width - 60);
760         selector.find('.one-comment .comment-container').css('width', selector_width - 55);
761 }
762
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));
767         
768         // Define the selector path
769         var selector = '#channel .top.individual input[name=';
770         
771         // Is this request still alive?
772         if(from == $(selector + 'jid]').val()) {
773                 var hash = hex_md5(from);
774                 
775                 // Update the items counter
776                 var old_count = parseInt($(selector + 'counter]').val());
777                 $(selector + 'counter]').val(old_count + 20);
778                 
779                 // Display the microblog
780                 displayMicroblog(iq, from, hash, 'individual', 'request');
781                 
782                 // Hide the waiting icon
783                 if(enabledPEP())
784                         waitMicroblog('sync');
785                 else
786                         waitMicroblog('unsync');
787                 
788                 // Hide the 'more items' link?
789                 if($(iq.getNode()).find('item').size() < old_count)
790                         $('#channel .individual a.more').remove();
791                 
792                 // Get the comments?
793                 var comments_node = $('#channel .top.individual input[name=comments]').val();
794                 
795                 if(comments_node && comments_node.match(/^xmpp:(.+)\?;node=(.+);item=(.+)/)) {
796                         // Get the values
797                         var comments_entity = RegExp.$1;
798                         comments_node = decodeURIComponent(RegExp.$2);
799                         
800                         // Selectors
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 + '])');
803                         
804                         // Is it a file?
805                         if(file_link.size())
806                                 file_link.click();
807                         
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();
812                         }
813                 }
814         }
815         
816         logThis('Microblog got: ' + from, 3);
817 }
818
819 // Resets the microblog elements
820 function resetMicroblog() {
821         // Reset everything
822         $('#channel .individual .one-update div.comments-content').stopTime();
823         $('#channel .individual').remove();
824         $('#channel .mixed').show();
825         
826         // Hide the waiting icon
827         if(enabledPEP())
828                 waitMicroblog('sync');
829         else
830                 waitMicroblog('unsync');
831         
832         return false;
833 }
834
835 // Gets the user's microblog to check it exists
836 function getInitMicroblog() {
837         getMicroblog(getXID(), hex_md5(getXID()), true);
838 }
839
840 // Handles the user's microblog to create it in case of error
841 function handleInitMicroblog(iq) {
842         // Any error?
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);
846                 
847                 logThis('Error while getting microblog, trying to reconfigure the Pubsub node!', 2);
848         }
849 }
850
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();
855         iq.setType('get');
856         iq.setTo(xid);
857         
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}));
860         
861         // Request a particular item?
862         if(get_item)
863                 ps_items.appendChild(iq.buildNode('item', {'id': get_item, 'xmlns': NS_PUBSUB}));
864         else
865                 ps_items.setAttribute('max_items', items);
866         
867         if(handler)
868                 con.send(iq, handler);
869         else
870                 con.send(iq, handleMicroblog);
871         
872         return false;
873 }
874
875 // Gets the microblog of an user
876 function getMicroblog(xid, hash, check) {
877         /* REF: http://xmpp.org/extensions/xep-0060.html#subscriber-retrieve */
878         
879         logThis('Get the microblog: ' + xid, 3);
880         
881         // Fire the wait event
882         waitMicroblog('fetch');
883         
884         // XMPP URI?
885         var get_item = '';
886         
887         if(xid.match(/^xmpp:(.+)\?;node=(.+);item=(.+)/)) {
888                 xid = RegExp.$1;
889                 get_item = decodeURIComponent(RegExp.$3);
890         }
891         
892         // No hash?
893         if(!hash)
894                 hash = hex_md5(xid);
895         
896         // Can display the individual channel?
897         if(!check && !exists('#channel .individual')) {
898                 // Hide the mixed channel
899                 $('#channel .mixed').hide();
900                 
901                 // Get the channel title depending on the XID
902                 var cTitle;
903                 var cShortcuts = '';
904                 
905                 if(xid == getXID())
906                         cTitle = _e("Your channel");
907                 else {
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>' + 
914                                      '</div>';
915                 }
916                 
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>' + 
921                                 '</div>'
922                                                  )
923                                            
924                                            .before(
925                                 '<div class="top individual ' + hash + '">' + 
926                                         '<div class="avatar-container">' + 
927                                                 '<img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" />' + 
928                                         '</div>' + 
929                                         
930                                         '<div class="update">' + 
931                                                 '<h2>' + cTitle + '</h2>' + 
932                                                 '<a href="#" onclick="return resetMicroblog();">« ' + _e("Previous") + '</a>' + 
933                                         '</div>' + 
934                                         
935                                         cShortcuts + 
936                                         
937                                         '<input type="hidden" name="jid" value="' + encodeQuotes(xid) + '" />' + 
938                                         '<input type="hidden" name="counter" value="20" />' + 
939                                 '</div>'
940                                                  );
941                 
942                 // Display the user avatar
943                 getAvatar(xid, 'cache', 'true', 'forget');
944         }
945         
946         // Get the number of items to retrieve
947         var items = '0';
948         
949         if(!check)
950                 items = $('#channel .top.individual input[name=counter]').val();
951         
952         // Request
953         if(check)
954                 requestMicroblog(xid, items, get_item, handleInitMicroblog);
955         else
956                 requestMicroblog(xid, items, get_item, handleMicroblog);
957         
958         return false;
959 }
960
961 // Show a given microblog waiting status
962 function waitMicroblog(type) {
963         // First hide all the infos elements
964         $('#channel .footer div').hide();
965         
966         // Display the good one
967         $('#channel .footer div.' + type).show();
968         
969         // Depending on the type, disable/enable certain tools
970         var selector = $('#channel .top input[name=microblog_body]');
971         
972         if(type == 'unsync')
973                 selector.attr('disabled', true);
974         else if(type == 'sync')
975                 $(document).oneTime(10, function() {
976                         selector.removeAttr('disabled').focus();
977                 });
978 }
979
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 */
983         
984         // Create the PubSub node
985         var iq = new JSJaCIQ();
986         iq.setType('set');
987         
988         // Any external entity?
989         if(entity)
990                 iq.setTo(entity);
991         
992         // Create it?
993         if(create) {
994                 var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB});
995                 pubsub.appendChild(iq.buildNode('create', {'xmlns': NS_PUBSUB, 'node': node}));
996         }
997         
998         else
999                 var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB_OWNER});
1000         
1001         // Configure it!
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'}));
1004         
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));
1007         
1008         // Persist items?
1009         if(persist) {
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));
1012         }
1013         
1014         // Maximum items?
1015         if(maximum) {
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));
1018         }
1019         
1020         // Access rights?
1021         if(access) {
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));
1024         }
1025         
1026         // Publish rights?
1027         if(publish) {
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));
1030         }
1031         
1032         con.send(iq);
1033 }
1034
1035 // Gets the microblog configuration
1036 function getConfigMicroblog() {
1037         // Lock the microblog options
1038         $('#persistent, #maxnotices').attr('disabled', true);
1039         
1040         // Get the microblog configuration
1041         var iq = new JSJaCIQ();
1042         iq.setType('get');
1043         
1044         var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB_OWNER});
1045         pubsub.appendChild(iq.buildNode('configure', {'node': NS_URN_MBLOG, 'xmlns': NS_PUBSUB_OWNER}));
1046         
1047         con.send(iq, handleGetConfigMicroblog);
1048 }
1049
1050 // Handles the microblog configuration
1051 function handleGetConfigMicroblog(iq) {
1052         // Reset the options stuffs
1053         waitOptions('microblog');
1054         
1055         // Unlock the microblog options
1056         $('#persistent, #maxnotices').removeAttr('disabled');
1057         
1058         // End if not a result
1059         if(!iq || (iq.getType() != 'result'))
1060                 return;
1061         
1062         // Initialize the values
1063         var selector = $(iq.getNode());
1064         var persistent = '0';
1065         var maxnotices = '1000000';
1066         
1067         // Get the values
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();
1070         
1071         // Any value?
1072         if(xPersistent)
1073                 persistent = xPersistent;
1074         
1075         if(xMaxnotices)
1076                 maxnotices = xMaxnotices;
1077         
1078         // Change the maxnotices value
1079         switch(maxnotices) {
1080                 case '1':
1081                 case '100':
1082                 case '1000':
1083                 case '10000':
1084                 case '100000':
1085                 case '1000000':
1086                         break;
1087                 
1088                 default:
1089                         maxnotices = '1000000';
1090                         break;
1091         }
1092         
1093         // Apply persistent value
1094         if(persistent == '0')
1095                 $('#persistent').attr('checked', false);
1096         else
1097                 $('#persistent').attr('checked', true);
1098         
1099         // Apply maxnotices value
1100         $('#maxnotices').val(maxnotices);
1101 }
1102
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();
1109         
1110         // Check for errors
1111         handleErrorReply(packet);
1112 }
1113
1114 // Performs the microblog sender checks
1115 function sendMicroblog() {
1116         logThis('Send a new microblog item', 3);
1117         
1118         // Avoid nasty errors
1119         try {
1120                 // Get the values
1121                 var selector = $('#channel .top input[name=microblog_body]');
1122                 var body = trim(selector.val());
1123                 
1124                 // Sufficient parameters
1125                 if(body) {
1126                         // Disable & blur our input
1127                         selector.attr('disabled', true).blur();
1128                         
1129                         // Files array
1130                         var fName = [];
1131                         var fType = [];
1132                         var fLength = [];
1133                         var fURL = [];
1134                         var fThumb = [];
1135                         
1136                         // Read the files
1137                         $('#attach .one-file').each(function() {
1138                                 // Push the values!
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'));
1144                         });
1145                         
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\/))([^& ]+)((&amp;\S)|(&\S)|\s|$)/gim);
1148                         
1149                         for(y in yt_matches) {
1150                                 fName.push('');
1151                                 fType.push('text/html');
1152                                 fLength.push('');
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\/))([^& ]+)((&amp;\S)|(&\S)|\s|$)/gim, '$8')) + '/0.jpg');
1155                         }
1156                         
1157                         // Send the message on the XMPP network
1158                         publishMicroblog(body, fName, fURL, fType, fLength, fThumb);
1159                 }
1160         }
1161         
1162         // Return false (security)
1163         finally {
1164                 return false;
1165         }
1166 }
1167
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 */
1171         
1172         // Generate some values
1173         var time = getXMPPTime('utc');
1174         var id = hex_md5(body + time);
1175         var nick = getName();
1176         var xid = getXID();
1177         
1178         // Define repeat options
1179         var author_nick = nick;
1180         var author_xid = xid;
1181         
1182         if(repeat && repeat.length) {
1183                 author_nick = repeat[0];
1184                 author_xid = repeat[1];
1185         }
1186         
1187         // Define comments options
1188         var node_create = false;
1189         
1190         if(!comments_entity || !comments_node) {
1191                 node_create = true;
1192                 comments_entity = HOST_PUBSUB;
1193                 comments_node = NS_URN_MBLOG + ':comments/' + id;
1194         }
1195         
1196         if(!comments_entity_file)
1197                 comments_entity_file = [];
1198         if(!comments_node_file)
1199                 comments_node_file = [];
1200         
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];
1205         }
1206         
1207         // New IQ
1208         var iq = new JSJaCIQ();
1209         iq.setType('set');
1210         iq.setTo(xid);
1211         
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}));
1217         
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));
1223         
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', {
1229                         'rel': 'alternate',
1230                         'href': 'xmpp:' + xid + '?;node=' + encodeURIComponent(NS_URN_MBLOG) + ';item=' + encodeURIComponent(id),
1231                         'xmlns': NS_ATOM
1232         }));
1233         
1234         // Create the attached files nodes
1235         for(var i = 0; i < attachedurl.length; i++) {
1236                 // Not enough data?
1237                 if(!attachedurl[i])
1238                         continue;
1239                 
1240                 // Append a new file element
1241                 var file = entry.appendChild(iq.buildNode('link', {'xmlns': NS_ATOM, 'rel': 'enclosure', 'href': attachedurl[i]}));
1242                 
1243                 // Add attributes
1244                 if(attachedname[i])
1245                         file.setAttribute('title', attachedname[i]);
1246                 if(attachedtype[i])
1247                         file.setAttribute('type', attachedtype[i]);
1248                 if(attachedlength[i])
1249                         file.setAttribute('length', attachedlength[i]);
1250                 
1251                 // Any thumbnail?
1252                 if(attachedthumb[i])
1253                         file.appendChild(iq.buildNode('link', {'xmlns': NS_URN_MBLOG, 'rel': 'self', 'title': 'thumb', 'type': attachedtype[i], 'href': attachedthumb[i]}));
1254                 
1255                 // Any comments node?
1256                 if(!comments_entity_file[i] || !comments_node_file[i]) {
1257                         // Generate values
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);
1260                         
1261                         // Create the node
1262                         setupMicroblog(comments_entity_file[i], comments_node_file[i], '1', '1000000', 'open', 'open', true);
1263                 }
1264                 
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])}));
1266         }
1267         
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)}));
1270         
1271         // Create the geoloc child
1272         var geoloc_xml = getDB('geolocation', 'now');
1273         
1274         if(geoloc_xml) {
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));
1278                 
1279                 // New geoloc child
1280                 var geoloc = entry.appendChild(iq.buildNode('geoloc', {'xmlns': NS_GEOLOC}));
1281                 
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]));
1286                 }
1287         }
1288         
1289         // Send the IQ
1290         con.send(iq, handleMyMicroblog);
1291         
1292         // Create the XML comments PubSub nodes
1293         if(node_create)
1294                 setupMicroblog(comments_entity, comments_node, '1', '1000000', 'open', 'open', true);
1295         
1296         return false;
1297 }
1298
1299 // Attaches a file to a microblog post
1300 function attachMicroblog() {
1301         // File upload vars
1302         var attach_options = {
1303                 dataType:       'xml',
1304                 beforeSubmit:   waitMicroblogAttach,
1305                 success:        handleMicroblogAttach
1306         };
1307         
1308         // Upload form submit event
1309         $('#attach').submit(function() {
1310                 if(!exists('#attach .wait') && $('#attach input[type=file]').val())
1311                         $(this).ajaxSubmit(attach_options);
1312                 
1313                 return false;
1314         });
1315         
1316         // Upload input change event
1317         $('#attach input[type=file]').change(function() {
1318                 if(!exists('#attach .wait') && $(this).val())
1319                         $('#attach').ajaxSubmit(attach_options);
1320                 
1321                 return false;
1322         });
1323 }
1324
1325 // Unattaches a microblog file
1326 function unattachMicroblog(id) {
1327         // Individual removal?
1328         if(id)
1329                 $('#attach .one-file[data-id=' + id + ']').remove();
1330         else
1331                 $('#attach .one-file').remove();
1332         
1333         // Must enable the popup again?
1334         if(!exists('#attach .one-file')) {
1335                 // Restore the bubble class
1336                 $('#attach').addClass('bubble');
1337                 
1338                 // Enable the bubble click events
1339                 if(id) {
1340                         $('#attach').hide();
1341                         showBubble('#attach');
1342                 }
1343                 
1344                 else
1345                         closeBubbles();
1346         }
1347         
1348         return false;
1349 }
1350
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>');
1355         
1356         // Lock the bubble
1357         $('#attach').removeClass('bubble');
1358 }
1359
1360 // Success event for file attaching
1361 function handleMicroblogAttach(responseXML) {
1362         // Data selector
1363         var dData = $(responseXML).find('jappix');
1364         
1365         // Process the returned data
1366         if(!dData.find('error').size()) {
1367                 // Do not allow this bubble to be hidden
1368                 $('#attach').removeClass('bubble');
1369                 
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();
1376                 
1377                 // Generate a file ID
1378                 var fID = hex_md5(fURL);
1379                 
1380                 // Add this file
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>' + 
1385                         '</div>'
1386                 );
1387                 
1388                 // Click event
1389                 $('#attach .one-file[data-id=' + fID + '] a.remove').click(function() {
1390                         return unattachMicroblog(fID);
1391                 });
1392                 
1393                 logThis('File attached.', 3);
1394         }
1395         
1396         // Any error?
1397         else {
1398                 openThisError(4);
1399                 
1400                 // Unlock the bubble?
1401                 if(!exists('#attach .one-file')) {
1402                         $('#attach').addClass('bubble').hide();
1403                         
1404                         // Show the bubble again!
1405                         showBubble('#attach');
1406                 }
1407                 
1408                 logThis('Error while attaching the file: ' + dData.find('error').text(), 1);
1409         }
1410         
1411         // Reset the attach bubble
1412         $('#attach input[type=file]').val('');
1413         $('#attach .wait').remove();
1414         
1415         // Focus on the text input
1416         $(document).oneTime(10, function() {
1417                 $('#channel .top input[name=microblog_body]').focus();
1418         });
1419 }
1420
1421 // Shows the microblog of an user from his infos
1422 function fromInfosMicroblog(xid, hash) {
1423         // Renitialize the channel
1424         resetMicroblog();
1425         
1426         // Switch to the channel
1427         switchChan('channel');
1428         
1429         // Get the microblog
1430         getMicroblog(xid, hash);
1431 }
1432
1433 // Plugin launcher
1434 function launchMicroblog() {
1435         // Keyboard event
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();
1440         })
1441         
1442         // Placeholder
1443         .placeholder();
1444         
1445         // Microblog file attacher
1446         attachMicroblog();
1447 }