/* Jappix - An open social platform These are the Jappix Mini JS scripts for Jappix ------------------------------------------------- License: dual-licensed under AGPL and MPLv2 Authors: Valérian Saliou, hunterjm, Camaran, regilero, Kloadut, Maranda */ // Jappix Mini globals var MINI_DISCONNECT = false; var MINI_AUTOCONNECT = false; var MINI_SHOWPANE = false; var MINI_INITIALIZED = false; var MINI_ROSTER_INIT = false; var MINI_ROSTER_NOGROUP = 'jm_nogroup'; var MINI_ANONYMOUS = false; var MINI_ANIMATE = false; var MINI_RANDNICK = false; var MINI_GROUPCHAT_PRESENCE = false; var MINI_DISABLE_MOBILE = false; var MINI_NICKNAME = ''; var MINI_TITLE = null; var MINI_DOMAIN = null; var MINI_USER = null; var MINI_PASSWORD = null; var MINI_HASH = null; var MINI_ACTIVE = null; var MINI_RECONNECT = 0; var MINI_RECONNECT_MAX = 100; var MINI_RECONNECT_INTERVAL = 1; var MINI_PIXEL_STREAM_DURATION = 300; var MINI_PIXEL_STREAM_INTERVAL = 7200; var MINI_QUEUE = []; var MINI_CHATS = []; var MINI_GROUPCHATS = []; var MINI_SUGGEST_CHATS = []; var MINI_SUGGEST_GROUPCHATS = []; var MINI_SUGGEST_PASSWORDS = []; var MINI_PASSWORDS = []; var MINI_PRIORITY = 1; var MINI_RESOURCE = JAPPIX_RESOURCE + ' Mini'; var MINI_ERROR_LINK = 'https://mini.jappix.com/issues'; // Bundle var JappixMini = (function () { /** * Alias of this * @private */ var self = {}; /** * Setups connection handlers * @public * @param {object} con * @return {undefined} */ self.setupCon = function(con) { try { con.registerHandler('message', self.handleMessage); con.registerHandler('presence', self.handlePresence); con.registerHandler('iq', self.handleIQ); con.registerHandler('onerror', self.handleError); con.registerHandler('onconnect', self.connected); } catch(e) { JappixConsole.error('JappixMini.setupCon', e); } }; /** * Connects the user with the given logins * @public * @param {type} domain * @param {type} user * @param {type} password * @return {boolean} */ self.connect = function(domain, user, password) { try { oArgs = {}; // Check BOSH origin BOSH_SAME_ORIGIN = Origin.isSame(oArgs.httpbase); // We create the new http-binding connection con = new JSJaCHttpBindingConnection({ httpbase: (HOST_BOSH_MINI || HOST_BOSH) }); // And we handle everything that happen self.setupCon(con); // fixes #339 var store_resource = (BrowserDetect.browser != 'Explorer'); var random_resource = null; if(store_resource) { // Randomize resource? random_resource = JappixDataStore.getDB(MINI_HASH, 'jappix-mini', 'resource'); } if(!random_resource) { random_resource = MINI_RESOURCE + ' (' + (new Date()).getTime() + ')'; } // We retrieve what the user typed in the login inputs oArgs = {}; oArgs.secure = true; oArgs.xmllang = XML_LANG; oArgs.resource = random_resource; oArgs.domain = domain; // Store the resource (for reconnection) if(store_resource) { JappixDataStore.setDB(MINI_HASH, 'jappix-mini', 'resource', random_resource); } // Anonymous login? if(MINI_ANONYMOUS) { // Anonymous mode disabled? if(!JappixCommon.allowedAnonymous()) { JappixConsole.warn('Not allowed to use anonymous mode.'); // Notify this error self.notifyError(); return false; } // Bad domain? else if(JappixCommon.lockHost() && (domain != HOST_ANONYMOUS)) { JappixConsole.warn('Not allowed to connect to this anonymous domain: ' + domain); // Notify this error self.notifyError(); return false; } oArgs.authtype = 'saslanon'; } // Normal login else { // Bad domain? if(JappixCommon.lockHost() && (domain != HOST_MAIN)) { JappixConsole.warn('Not allowed to connect to this main domain: ' + domain); // Notify this error self.notifyError(); return false; } // No nickname? if(!MINI_NICKNAME) { MINI_NICKNAME = user; } oArgs.username = user; oArgs.pass = password; } // We connect ! con.connect(oArgs); JappixConsole.info('Jappix Mini is connecting...'); } catch(e) { JappixConsole.error('JappixMini.connect', e); // Reset Jappix Mini self.disconnected(); } finally { return false; } }; /** * When the user is connected * @public * @return {undefined} */ self.connected = function() { try { // Do not get the roster if anonymous if(!MINI_RECONNECT) { // Update the roster jQuery('#jappix_mini a.jm_pane.jm_button span.jm_counter').text('0'); if(MINI_ANONYMOUS) { self.initialize(); } else { self.getRoster(); } JappixConsole.info('Jappix Mini is now connected.'); } else { self.reconnected(); JappixConsole.info('Jappix Mini is now reconnected.'); } // Reset reconnect var MINI_RECONNECT = 0; JappixDataStore.removeDB(MINI_HASH, 'jappix-mini', 'reconnect'); // Execute enqueued events self.dequeue(); } catch(e) { JappixConsole.error('JappixMini.connected', e); } }; /** * When the user is reconnected * @public * @return {undefined} */ self.reconnected = function() { try { var last_presence = JappixDataStore.getDB(MINI_HASH, 'jappix-mini', 'presence-last') || 'available'; // Flush presence storage self.flushStorage('presence'); // Empty groupchat messages jQuery('#jappix_mini div.jm_conversation.jm_type_groupchat div.jm_received-messages div.jm_group').remove(); // Re-send all presences jQuery('#jappix_mini div.jm_status_picker a[data-status="' + JappixCommon.encodeQuotes(last_presence) + '"]').click(); } catch(e) { JappixConsole.error('JappixMini.reconnected', e); } }; /** * When the user disconnects * @public * @return {undefined} */ self.saveSession = function() { try { // Not initialized? if(!MINI_INITIALIZED) { return; } // Reset Jappix Mini DOM before saving it self.resetPixStream(); // Save the actual Jappix Mini DOM JappixDataStore.setDB(MINI_HASH, 'jappix-mini', 'dom', jQuery('#jappix_mini').html()); JappixDataStore.setDB(MINI_HASH, 'jappix-mini', 'nickname', MINI_NICKNAME); // Save the scrollbar position var scroll_position = ''; var scroll_hash = jQuery('#jappix_mini div.jm_conversation:has(a.jm_pane.jm_clicked)').attr('data-hash'); if(scroll_hash) { scroll_position = document.getElementById('received-' + scroll_hash).scrollTop + ''; } JappixDataStore.setDB(MINI_HASH, 'jappix-mini', 'scroll', scroll_position); // Suspend connection if(JappixCommon.isConnected()) { con.suspend(false); } else { JappixDataStore.setDB(MINI_HASH, 'jappix-mini', 'reconnect', ((MINI_RECONNECT === 0) ? 0 : (MINI_RECONNECT - 1))); self.serializeQueue(); } JappixConsole.info('Jappix Mini session save tool launched.'); } catch(e) { JappixConsole.error('JappixMini.saveSession', e); } }; /** * Flushes Jappix Mini storage database * @public * @param {string} r_override * @return {undefined} */ self.flushStorage = function(r_override) { try { var i, db_regex, db_current; db_regex = new RegExp(('^' + MINI_HASH + '_') + 'jappix-mini' + (r_override ? ('_' + r_override) : '')); for(i = 0; i < JappixDataStore.storageDB.length; i++) { db_current = JappixDataStore.storageDB.key(i); if(db_regex.exec(db_current)) { JappixDataStore.storageDB.removeItem(db_current); } } JappixConsole.log('Jappix Mini DB has been successfully flushed (' + (r_override ? 'partly' : 'completely') + ').'); } catch(e) { JappixConsole.error('JappixMini.flushStorage', e); } }; /** * Disconnects the connected user * @public * @return {boolean} */ self.disconnect = function() { try { // No connection? if(!JappixCommon.isConnected()) { return false; } JappixConsole.info('Jappix Mini is disconnecting...'); // Change markers MINI_DISCONNECT = true; MINI_INITIALIZED = false; // Flush storage self.flushStorage(); // Add disconnection handler con.registerHandler('ondisconnect', function() { self.disconnected(); }); // Disconnect the user con.disconnect(); return false; } catch(e) { JappixConsole.error('JappixMini.disconnect', e); } }; /** * When the user is disconnected * @public * @return {boolean} */ self.disconnected = function() { try { // Connection error? if(!MINI_DISCONNECT || MINI_INITIALIZED) { // Reset reconnect timer jQuery('#jappix_mini').stopTime(); // Try to reconnect after a while if(MINI_INITIALIZED && (MINI_RECONNECT++ < MINI_RECONNECT_MAX)) { // Set timer jQuery('#jappix_mini').oneTime(MINI_RECONNECT_INTERVAL * 1000, function() { JappixConsole.debug('Trying to reconnect... (attempt: ' + MINI_RECONNECT + ' / ' + MINI_RECONNECT_MAX + ')'); // Silently reconnect user self.connect(MINI_DOMAIN, MINI_USER, MINI_PASSWORD); }); JappixConsole.info('Jappix Mini is encountering connectivity issues.'); } else { // Remove the stored items self.flushStorage(); // Notify this error self.notifyError(); // Reset markers MINI_DISCONNECT = false; MINI_INITIALIZED = false; JappixConsole.info('Jappix Mini is giving up. Server seems to be down.'); } } // Normal disconnection? else { launchMini(false, MINI_SHOWPANE, MINI_DOMAIN, MINI_USER, MINI_PASSWORD); // Reset markers MINI_DISCONNECT = false; MINI_INITIALIZED = false; JappixConsole.info('Jappix Mini is now disconnected.'); } } catch(e) { JappixConsole.error('JappixMini.disconnected', e); } }; /** * Handles the incoming errors * @public * @param {object} err * @return {undefined} */ self.handleError = function(err) { try { // First level error (connection error) if(jQuery(err).is('error')) { // Notify this error self.disconnected(); JappixConsole.error('First level error received.'); } } catch(e) { JappixConsole.error('JappixMini.handleError', e); } }; /** * Handles the incoming messages * @public * @param {object} msg * @return {undefined} */ self.handleMessage = function(msg) { try { var type = msg.getType(); // This is a message Jappix can handle if((type == 'chat') || (type == 'normal') || (type == 'groupchat') || !type) { // Get the packet data var node = msg.getNode(); var subject = jQuery.trim(msg.getSubject()); var body = subject ? subject : jQuery.trim(msg.getBody()); // Get the sender data var from = JappixCommon.fullXID(JappixCommon.getStanzaFrom(msg)); var xid = JappixCommon.bareXID(from); var hash = hex_md5(xid); // Any attached message body? if(body) { // Get more sender data var use_xid = xid; var nick = JappixCommon.thisResource(from); // Read the delay var delay = JappixDateUtils.readMessageDelay(node); var d_stamp; // Manage this delay if(delay) { time = JappixDateUtils.relative(delay); d_stamp = Date.jab2date(delay); } else { time = JappixDateUtils.getCompleteTime(); d_stamp = new Date(); } // Get the stamp var stamp = JappixDateUtils.extractStamp(d_stamp); // Is this a groupchat private message? if(JappixCommon.exists('#jappix_mini #chat-' + hash + '[data-type="groupchat"]')) { // Regenerate some stuffs if((type == 'chat') || (type == 'normal') || !type) { xid = from; hash = hex_md5(xid); } // XID to use for a groupchat else { use_xid = from; } } // Message type var message_type = 'user-message'; // Grouphat values if(type == 'groupchat') { // Old message if(msg.getChild('delay', NS_URN_DELAY) || msg.getChild('x', NS_DELAY)) { message_type = 'old-message'; } // System message? if(!nick || subject) { nick = ''; message_type = 'system-message'; } } // Chat values else { nick = jQuery('#jappix_mini a#friend-' + hash).text().revertHtmlEnc(); // No nickname? if(!nick) { // If the roster does not give us any nick the user may have send us a nickname to use with his first message // @see http://xmpp.org/extensions/xep-0172.html var unknown_entry = jQuery('#jappix_mini a.jm_unknown[data-xid="' + xid + '"]'); if(unknown_entry.size() > 0) { nick = unknown_entry.attr('data-nick'); } else { var msgnick = msg.getNick(); nick = JappixCommon.getXIDNick(xid); if(msgnick) { // If there is a nickname in the message which differs from the jid-extracted nick then tell it to the user if(nick != msgnick) nick = msgnick + ' (' + nick + ')'; } // Push that unknown guy in a temporary roster entry unknown_entry = jQuery('').attr('data-nick', nick).attr('data-xid', xid); unknown_entry.appendTo('#jappix_mini div.jm_roster div.jm_buddies'); } } } // Define the target div var target = '#jappix_mini #chat-' + hash; // Create the chat if it does not exist if(!JappixCommon.exists(target) && (type != 'groupchat')) { self.chat(type, xid, nick, hash); } // Display the message self.displayMessage(type, body, use_xid, nick, hash, time, stamp, message_type); // Notify the user if not focused & the message is not a groupchat old one if((!jQuery(target + ' a.jm_chat-tab').hasClass('jm_clicked') || !JappixCommon.isFocused() || (MINI_ACTIVE != hash)) && (message_type == 'user-message')) { // Play a sound if(type != 'groupchat') { self.soundPlay(); } // Show a notification bubble self.notifyMessage(hash); } JappixConsole.log('Message received from: ' + from); } // Chatstate groupchat filter if(JappixCommon.exists('#jappix_mini #chat-' + hash + '[data-type="groupchat"]')) { xid = from; hash = hex_md5(xid); } // Reset current chatstate self.resetChatstate(xid, hash, type); // Apply new chatstate (if supported) if(jQuery(node).find('active[xmlns="' + NS_CHATSTATES + '"]').size() || jQuery(node).find('composing[xmlns="' + NS_CHATSTATES + '"]').size()) { // Set marker to tell other user supports chatstates jQuery('#jappix_mini #chat-' + hash + ' input.jm_send-messages').attr('data-chatstates', 'true'); // Composing? if(jQuery(node).find('composing[xmlns="' + NS_CHATSTATES + '"]').size()) { self.displayChatstate('composing', xid, hash, type); } } } } catch(e) { JappixConsole.error('JappixMini.handleMessage', e); } }; /** * Handles the incoming IQs * @public * @param {object} iq * @return {undefined} */ self.handleIQ = function(iq) { try { // Define some variables var iqFrom = JappixCommon.fullXID(JappixCommon.getStanzaFrom(iq)); var iqID = iq.getID(); var iqQueryXMLNS = iq.getQueryXMLNS(); var iqType = iq.getType(); var iqNode = iq.getNode(); var iqQuery; // Build the response var iqResponse = new JSJaCIQ(); iqResponse.setID(iqID); iqResponse.setTo(iqFrom); iqResponse.setType('result'); // Software version query if((iqQueryXMLNS == NS_VERSION) && (iqType == 'get')) { /* REF: http://xmpp.org/extensions/xep-0092.html */ iqQuery = iqResponse.setQuery(NS_VERSION); iqQuery.appendChild(iq.buildNode('name', {'xmlns': NS_VERSION}, 'Jappix Mini')); iqQuery.appendChild(iq.buildNode('version', {'xmlns': NS_VERSION}, JAPPIX_VERSION)); iqQuery.appendChild(iq.buildNode('os', {'xmlns': NS_VERSION}, navigator.platform)); con.send(iqResponse); JappixConsole.log('Received software version query: ' + iqFrom); } // Roster push else if((iqQueryXMLNS == NS_ROSTER) && (iqType == 'set')) { // Display the friend self.handleRoster(iq); con.send(iqResponse); JappixConsole.log('Received a roster push.'); } // Disco info query else if((iqQueryXMLNS == NS_DISCO_INFO) && (iqType == 'get')) { /* REF: http://xmpp.org/extensions/xep-0030.html */ iqQuery = iqResponse.setQuery(NS_DISCO_INFO); // We set the name of the client iqQuery.appendChild(iq.appendNode('identity', { 'category': 'client', 'type': 'web', 'name': 'Jappix Mini', 'xmlns': NS_DISCO_INFO })); // We set all the supported features var fArray = [ NS_DISCO_INFO, NS_VERSION, NS_ROSTER, NS_MUC, NS_VERSION, NS_URN_TIME ]; for(var i in fArray) { iqQuery.appendChild(iq.buildNode('feature', {'var': fArray[i], 'xmlns': NS_DISCO_INFO})); } con.send(iqResponse); JappixConsole.log('Received a disco#infos query.'); } // User time query else if(jQuery(iqNode).find('time').size() && (iqType == 'get')) { /* REF: http://xmpp.org/extensions/xep-0202.html */ var iqTime = iqResponse.appendNode('time', {'xmlns': NS_URN_TIME}); iqTime.appendChild(iq.buildNode('tzo', {'xmlns': NS_URN_TIME}, JappixDateUtils.getTZO())); iqTime.appendChild(iq.buildNode('utc', {'xmlns': NS_URN_TIME}, JappixDateUtils.getXMPPTime('utc'))); con.send(iqResponse); JappixConsole.log('Received local time query: ' + iqFrom); } // Ping else if(jQuery(iqNode).find('ping').size() && (iqType == 'get')) { /* REF: http://xmpp.org/extensions/xep-0199.html */ con.send(iqResponse); JappixConsole.log('Received a ping: ' + iqFrom); } // Not implemented else if(!jQuery(iqNode).find('error').size() && ((iqType == 'get') || (iqType == 'set'))) { // Append stanza content for(var c = 0; c < iqNode.childNodes.length; c++) { iqResponse.getNode().appendChild(iqNode.childNodes.item(c).cloneNode(true)); } // Append error content var iqError = iqResponse.appendNode('error', {'xmlns': NS_CLIENT, 'code': '501', 'type': 'cancel'}); iqError.appendChild(iq.buildNode('feature-not-implemented', {'xmlns': NS_STANZAS})); iqError.appendChild(iq.buildNode('text', {'xmlns': NS_STANZAS}, JappixCommon._e("The feature requested is not implemented by the recipient or server and therefore cannot be processed."))); con.send(iqResponse); JappixConsole.log('Received an unsupported IQ query from: ' + iqFrom); } } catch(e) { JappixConsole.error('JappixMini.handleIQ', e); } }; /** * Handles the incoming presences * @public * @param {object} pr * @return {undefined} */ self.handlePresence = function(pr) { try { // Get the values var xml = pr.getNode(); var from = JappixCommon.fullXID(JappixCommon.getStanzaFrom(pr)); var xid = JappixCommon.bareXID(from); var resource = JappixCommon.thisResource(from); var resources_obj = {}; // Is this a groupchat? if(JappixCommon.exists('#jappix_mini div.jm_conversation[data-type="groupchat"][data-xid="' + JappixCommon.encodeQuotes(xid) + '"]')) { xid = from; } // Store presence stanza JappixDataStore.setDB(MINI_HASH, 'jappix-mini', 'presence-stanza-' + from, pr.xml()); resources_obj = self.addResourcePresence(xid, resource); // Re-process presence storage for this buddy self.processPresence(xid, resource, resources_obj); // Display that presence self.displayPresence(xid); JappixConsole.log('Presence received from: ' + from); } catch(e) { JappixConsole.error('JappixMini.handlePresence', e); } }; /** * Reads a stored presence * @public * @param {string} from * @return {undefined} */ self.readPresence = function(from) { try { var pr = JappixDataStore.getDB(MINI_HASH, 'jappix-mini', 'presence-stanza-' + from); if(!pr) { pr = ''; } return JappixCommon.XMLFromString(pr); } catch(e) { JappixConsole.error('JappixMini.readPresence', e); } }; /** * Lists presence resources for an user * @public * @param {string} xid * @return {object} */ self.resourcesPresence = function(xid) { var resources_obj = {}; try { var resources_db = JappixDataStore.getDB(MINI_HASH, 'jappix-mini', 'presence-resources-' + xid); if(resources_db) { resources_obj = jQuery.evalJSON(resources_db); } } catch(e) { JappixConsole.error('JappixMini.resourcesPresence', e); } finally { return resources_obj; } }; /** * Adds a given presence resource for an user * @public * @param {string} xid * @param {string} resource * @return {object} */ self.addResourcePresence = function(xid, resource) { try { var resources_obj = self.resourcesPresence(xid); resources_obj[resource] = 1; JappixDataStore.setDB(MINI_HASH, 'jappix-mini', 'presence-resources-' + xid, jQuery.toJSON(resources_obj)); return resources_obj; } catch(e) { JappixConsole.error('JappixMini.addResourcePresence', e); return null; } }; /** * Removes a given presence resource for an user * @public * @param {string} xid * @param {string} resource * @return {object} */ self.removeResourcePresence = function(xid, resource) { try { var resources_obj = self.resourcesPresence(xid); delete resources_obj[resource]; JappixDataStore.setDB(MINI_HASH, 'jappix-mini', 'presence-resources-' + xid, jQuery.toJSON(resources_obj)); return resources_obj; } catch(e) { JappixConsole.error('JappixMini.removeResourcePresence', e); return null; } }; /** * Process presence storage for a given contact * @public * @param {string} xid * @param {string} resource * @param {object} resources_obj * @return {undefined} */ self.processPresence = function(xid, resource, resources_obj) { try { if(!xid) { JappixConsole.warn('No XID value for precense processing.'); return; } // Initialize vars var cur_resource, cur_from, cur_pr, cur_xml, cur_priority, from_highest; from_highest = null; max_priority = null; // Groupchat presence? (no priority here) if(xid.indexOf('/') !== -1) { from_highest = xid; JappixConsole.log('Processed presence for groupchat user: ' + xid); } else { if(!self.priorityPresence(xid)) { from_highest = xid + '/' + resource; JappixConsole.log('Processed initial presence for regular user: ' + xid + ' (highest priority for: ' + (from_highest || 'none') + ')'); } else { for(cur_resource in resources_obj) { // Read presence data cur_from = xid + '/' + cur_resource; cur_pr = JappixDataStore.getDB(MINI_HASH, 'jappix-mini', 'presence-stanza-' + cur_from); if(cur_pr) { // Parse presence data cur_xml = JappixCommon.XMLFromString(cur_pr); cur_priority = jQuery(cur_xml).find('priority').text(); cur_priority = !isNaN(cur_priority) ? parseInt(cur_priority) : 0; // Higher priority? if((cur_priority >= max_priority) || (max_priority === null)) { max_priority = cur_priority; from_highest = cur_from; } } } JappixConsole.log('Processed presence for regular user: ' + xid + ' (highest priority for: ' + (from_highest || 'none') + ')'); } } if(from_highest) { JappixDataStore.setDB(MINI_HASH, 'jappix-mini', 'presence-priority-' + xid, from_highest); } else { JappixDataStore.removeDB(MINI_HASH, 'jappix-mini', 'presence-priority-' + xid); } } catch(e) { JappixConsole.error('JappixMini.processPresence', e); } }; /** * Returns highest presence priority * @public * @param {string} xid * @return {string} */ self.priorityPresence = function(xid) { try { return JappixDataStore.getDB(MINI_HASH, 'jappix-mini', 'presence-priority-' + xid) || ''; } catch(e) { JappixConsole.error('JappixMini.priorityPresence', e); return null; } }; /** * Displays a Jappix Mini presence * @public * @param {string} xid * @return {undefined} */ self.displayPresence = function(xid) { try { // Get the values var from = self.priorityPresence(xid); var xml = self.readPresence(from); var pr = jQuery(xml).find('presence'); var resource = JappixCommon.thisResource(from); var bare_xid = JappixCommon.bareXID(xid); var hash = hex_md5(bare_xid); var type = pr.attr('type'); var show = pr.find('show').text(); // Manage the received presence values if((type == 'error') || (type == 'unavailable')) { show = 'unavailable'; } else { switch(show) { case 'chat': case 'away': case 'xa': case 'dnd': break; default: show = 'available'; break; } } // Is this a groupchat presence? var groupchat_path = '#jappix_mini #chat-' + hash + '[data-type="groupchat"]'; var is_groupchat = false; if(JappixCommon.exists(groupchat_path)) { // Groupchat exists is_groupchat = true; // Groupchat buddy presence (not me) if(resource != unescape(jQuery(groupchat_path).attr('data-nick'))) { // Regenerate some stuffs var groupchat = xid; var groupchat_hash = hash; xid = from; hash = hex_md5(xid); // Process this groupchat user presence var log_message; if(show == 'unavailable') { // Remove from roster view self.removeBuddy(hash, groupchat); // Generate log message log_message = JappixCommon.printf(JappixCommon._e("%s left"), resource.htmlEnc()); } else { // Add to roster view self.addBuddy(xid, hash, resource, groupchat); // Generate log message log_message = JappixCommon.printf(JappixCommon._e("%s joined"), resource.htmlEnc()); } // Log message in chat view if(MINI_GROUPCHAT_PRESENCE && log_message && (jQuery(groupchat_path).attr('data-init') == 'true')) { self.displayMessage('groupchat', log_message, xid, '', groupchat_hash, JappixDateUtils.getCompleteTime(), JappixDateUtils.getTimeStamp(), 'system-message'); } } } // Friend path var chat = '#jappix_mini #chat-' + hash; var friend = '#jappix_mini a#friend-' + hash; var send_input = chat + ' input.jm_send-messages'; // Is this friend online? if(show == 'unavailable') { // Offline marker jQuery(friend).addClass('jm_offline').removeClass('jm_online jm_hover'); // Hide the friend just to be safe since the search uses .hide() and .show() which can override the CSS display attribute jQuery(friend).hide(); // Disable the chat tools if(is_groupchat) { jQuery(chat).addClass('jm_disabled').attr('data-init', 'false'); jQuery(send_input).blur().attr('disabled', true).attr('data-value', JappixCommon._e("Unavailable")).val(JappixCommon._e("Unavailable")); } } else { // Online marker jQuery(friend).removeClass('jm_offline').addClass('jm_online'); // Check against search string var search = jQuery('#jappix_mini div.jm_roster div.jm_search input.jm_searchbox').val(); var regex = new RegExp('((^)|( ))' + JappixCommon.escapeRegex(search), 'gi'); var nick = unescape(jQuery(friend).data('nick')); if(search && !nick.match(regex)) { jQuery(friend).hide(); } else { jQuery(friend).show(); } // Enable the chat input if(is_groupchat) { jQuery(chat).removeClass('jm_disabled'); jQuery(send_input).removeAttr('disabled').val(''); } } // Change the show presence of this buddy jQuery(friend + ' span.jm_presence, ' + chat + ' span.jm_presence').attr('class', 'jm_presence jm_images jm_' + show); // Update the presence counter self.updateRoster(); // Update groups visibility self.updateGroups(); JappixConsole.log('Presence displayed for user: ' + xid); } catch(e) { JappixConsole.error('JappixMini.displayPresence', e); } }; /** * Handles the MUC main elements * @public * @param {object} pr * @return {undefined} */ self.handleMUC = function(pr) { try { // We get the xml content var xml = pr.getNode(); var from = JappixCommon.fullXID(JappixCommon.getStanzaFrom(pr)); var room = JappixCommon.bareXID(from); var hash = hex_md5(room); var resource = JappixCommon.thisResource(from); // Is it a valid server presence? var valid = false; if(!resource || (resource == unescape(jQuery('#jappix_mini #chat-' + hash + '[data-type="groupchat"]').attr('data-nick')))) { valid = true; } // Password required? if(valid && jQuery(xml).find('error[type="auth"] not-authorized').size()) { // Create a new prompt self.openPrompt(JappixCommon.printf(JappixCommon._e("This room (%s) is protected with a password."), room)); // When prompt submitted jQuery('#jappix_popup div.jm_prompt form').submit(function() { try { // Read the value var password = self.closePrompt(); // Any submitted chat to join? if(password) { // Send the password self.presence('', '', '', '', from, password, true, self.handleMUC); // Focus on the pane again self.switchPane('chat-' + hash, hash); } } catch(e) {} finally { return false; } }); return; } // Nickname conflict? else if(valid && jQuery(xml).find('error[type="cancel"] conflict').size()) { // New nickname var nickname = resource + '_'; // Send the new presence self.presence('', '', '', '', room + '/' + nickname, '', true, self.handleMUC); // Update the nickname marker jQuery('#jappix_mini #chat-' + hash).attr('data-nick', escape(nickname)); } // Handle normal presence else { // Start the initial timer jQuery('#jappix_mini #chat-' + hash).oneTime('10s', function() { jQuery(this).attr('data-init', 'true'); }); // Trigger presence handler self.handlePresence(pr); } } catch(e) { JappixConsole.error('JappixMini.handleMUC', e); } }; /** * Updates the user presence * @public * @param {string} type * @param {string} show * @param {number} priority * @param {string} status * @param {string} to * @param {string} password * @param {boolean} limit_history * @param {function} handler * @return {undefined} */ self.presence = function(type, show, priority, status, to, password, limit_history, handler) { try { var pr = new JSJaCPresence(); // Add the attributes if(to) pr.setTo(to); if(type) pr.setType(type); if(show) pr.setShow(show); if(status) pr.setStatus(status); if(priority) { pr.setPriority(priority); } else if(MINI_PRIORITY && !to) { pr.setPriority(MINI_PRIORITY); } // Special presence elements if(password || limit_history) { var x = pr.appendNode('x', {'xmlns': NS_MUC}); // Any password? if(password) { x.appendChild(pr.buildNode('password', {'xmlns': NS_MUC}, password)); } // Any history limit? if(limit_history) { x.appendChild(pr.buildNode('history', {'maxstanzas': 10, 'seconds': 86400, 'xmlns': NS_MUC})); } } // Send the packet if(handler) { con.send(pr, handler); } else { con.send(pr); } JappixConsole.info('Presence sent (to: ' + (to || 'none') + ', show: ' + (show || 'none') + ', type: ' + (type || 'none') + ')'); } catch(e) { JappixConsole.error('JappixMini.presence', e); } }; /** * Sends a given message * @public * @param {object} aForm * @return {boolean} */ self.sendMessage = function(aForm) { try { var body = jQuery.trim(aForm.body.value); var xid = aForm.xid.value; var type = aForm.type.value; var hash = hex_md5(xid); if(body && xid) { // Send the message var aMsg = new JSJaCMessage(); // If the roster does not give us any nick the user may have send us a nickname to use with his first message // @see http://xmpp.org/extensions/xep-0172.html var known_roster_entry = jQuery('#jappix_mini a.jm_friend[data-xid="' + xid + '"]'); if(known_roster_entry.size() === 0) { var subscription = known_roster_entry.attr('data-sub'); // The other may not know my nickname if we do not have both a roster entry, or if he doesn't have one if(('both' != subscription) && ('from' != subscription)) { aMsg.setNick(MINI_NICKNAME); } } // Message data aMsg.setTo(xid); aMsg.setType(type); aMsg.setBody(body); // Chatstate aMsg.appendNode('active', {'xmlns': NS_CHATSTATES}); // Send it! self.enqueue(aMsg); // Clear the input aForm.body.value = ''; // Display the message we sent if(type != 'groupchat') { self.displayMessage(type, body, JappixCommon.getXID(), 'me', hash, JappixDateUtils.getCompleteTime(), JappixDateUtils.getTimeStamp(), 'user-message'); } JappixConsole.log('Message (' + type + ') sent to: ' + xid); } } catch(e) { JappixConsole.error('JappixMini.sendMessage', e); } finally { return false; } }; /** * Enqueues a stanza (to be sent over the network) * @public * @param {object} stanza * @return {undefined} */ self.enqueue = function(stanza) { try { // Send stanza over the network or enqueue it? if(JappixCommon.isConnected()) { con.send(stanza); } else { MINI_QUEUE.push( stanza.xml() ); JappixConsole.log('Enqueued an event (to be sent when connectivity is back).'); } } catch(e) { JappixConsole.error('JappixMini.enqueue', e); } }; /** * Dequeues stanzas and send them over the network * @public * @return {undefined} */ self.dequeue = function() { try { var stanza_str, stanza_childs, stanza; // Execute deferred tasks while(MINI_QUEUE.length) { stanza_str = MINI_QUEUE.shift(); stanza_childs = JappixCommon.XMLFromString(stanza_str).childNodes; if(stanza_childs && stanza_childs[0]) { stanza = JSJaCPacket.wrapNode(stanza_childs[0]); con.send(stanza); } JappixConsole.log('Dequeued a stanza.'); } } catch(e) { JappixConsole.error('JappixMini.dequeue', e); } }; /** * Serializes and store the queue storage * @public * @return {undefined} */ self.serializeQueue = function() { try { JappixDataStore.setDB(MINI_HASH, 'jappix-mini', 'queue', jQuery.toJSON(MINI_QUEUE)); } catch(e) { JappixConsole.error('JappixMini.serializeQueue', e); } }; /** * Unserializes and update the queue storage * @public * @return {undefined} */ self.unserializeQueue = function() { try { var start_body, end_body, start_args, end_args; var s_queue = JappixDataStore.getDB(MINI_HASH, 'jappix-mini', 'queue'); JappixDataStore.removeDB(MINI_HASH, 'jappix-mini', 'queue'); if(s_queue) { MINI_QUEUE = jQuery.evalJSON(s_queue); } } catch(e) { JappixConsole.error('JappixMini.unserialize', e); } }; /** * Generates the asked smiley image * @public * @param {string} image * @param {string} text * @return {string} */ self.smiley = function(image, text) { try { return ' ' + JappixCommon.encodeQuotes(text) + ' '; } catch(e) { JappixConsole.error('JappixMini.smiley', e); return null; } }; /** * Notifies incoming chat messages * @public * @param {string} hash * @return {undefined} */ self.notifyMessage = function(hash) { try { // Define the paths var tab = '#jappix_mini #chat-' + hash + ' a.jm_chat-tab'; var notify = tab + ' span.jm_notify'; var notify_middle = notify + ' span.jm_notify_middle'; // Notification box not yet added? if(!JappixCommon.exists(notify)) { jQuery(tab).append( '' + '' + '0' + '' + '' ); } // Increment the notification number var number = parseInt(jQuery(notify_middle).text()); jQuery(notify_middle).text(number + 1); // Update the notification counters self.notifyCounters(); } catch(e) { JappixConsole.error('JappixMini.notifyMessage', e); } }; /** * Notifies the user from a session error * @public * @return {undefined} */ self.notifyError = function() { try { // Replace the Jappix Mini DOM content jQuery('#jappix_mini').html( '
' + '' + '' + JappixCommon._e("Error") + '' + '' + '
' ); } catch(e) { JappixConsole.error('JappixMini.notifyError', e); } }; /** * Updates the global counter with the new notifications * @public * @return {undefined} */ self.notifyCounters = function() { try { // Count the number of notifications var number = 0; jQuery('#jappix_mini span.jm_notify span.jm_notify_middle').each(function() { number = number + parseInt(jQuery(this).text()); }); // Update the notification link counters jQuery('#jappix_mini a.jm_switch').removeClass('jm_notifnav'); if(number) { // Left? if(jQuery('#jappix_mini div.jm_conversation:visible:first').prevAll().find('span.jm_notify').size()) jQuery('#jappix_mini a.jm_switch.jm_left').addClass('jm_notifnav'); // Right? if(jQuery('#jappix_mini div.jm_conversation:visible:last').nextAll().find('span.jm_notify').size()) jQuery('#jappix_mini a.jm_switch.jm_right').addClass('jm_notifnav'); } // No saved title? Abort! if(MINI_TITLE === null) { return; } // Page title code var title = MINI_TITLE; // No new stuffs? Reset the title! if(number) { title = '[' + number + '] ' + title; } // Apply the title document.title = title; } catch(e) { JappixConsole.error('JappixMini.notifyCounters', e); } }; /** * Clears the notifications * @public * @param {string} hash * @return {boolean} */ self.clearNotifications = function(hash) { try { // Not focused? if(!JappixCommon.isFocused()) { return false; } // Remove the notifications counter jQuery('#jappix_mini #chat-' + hash + ' span.jm_notify').remove(); // Update the global counters self.notifyCounters(); return true; } catch(e) { JappixConsole.error('JappixMini.clearNotifications', e); return false; } }; /** * Updates the roster counter * @public * @return {undefined} */ self.updateRoster = function() { try { // Update online counter jQuery('#jappix_mini a.jm_button span.jm_counter').text(jQuery('#jappix_mini a.jm_online').size()); } catch(e) { JappixConsole.error('JappixMini.updateRoster', e); } }; /** * Updates the visibility of roster groups * @public * @return {undefined} */ self.updateGroups = function() { try { jQuery('.jm_grouped_roster').filter(':not(:has(.jm_friend.jm_online:visible))').hide(); } catch(e) { JappixConsole.error('JappixMini.updateGroups', e); } }; /** * Updates the chat overflow * @public * @return {undefined} */ self.updateOverflow = function() { try { // Process overflow var number_visible = parseInt((jQuery(window).width() - 380) / 140); var number_visible_dom = jQuery('#jappix_mini div.jm_conversation:visible').size(); if(number_visible <= 0) { number_visible = 1; } // Need to reprocess? if(number_visible != number_visible_dom) { // Show hidden chats jQuery('#jappix_mini div.jm_conversation:hidden').show(); // Get total number of chats var number_total = jQuery('#jappix_mini div.jm_conversation').size(); // Must add the overflow switcher? if(number_visible < number_total) { // Create the overflow handler? if(!jQuery('#jappix_mini a.jm_switch').size()) { // Add the navigation links jQuery('#jappix_mini div.jm_conversations').before( '' + '' + '' ); jQuery('#jappix_mini div.jm_conversations').after( '' + '' + '' ); // Add the click events self.overflowEvents(); } // Show first visible chats var first_visible = jQuery('#jappix_mini div.jm_conversation:visible:first').index(); var index_visible = number_visible - first_visible - 1; jQuery('#jappix_mini div.jm_conversation:visible:gt(' + index_visible + ')').hide(); // Close the opened chat if(jQuery('#jappix_mini div.jm_conversation:hidden a.jm_pane.jm_clicked').size()) { self.switchPane(); } // Update navigation buttons jQuery('#jappix_mini a.jm_switch').removeClass('jm_nonav'); if(!jQuery('#jappix_mini div.jm_conversation:visible:first').prev().size()) { jQuery('#jappix_mini a.jm_switch.jm_left').addClass('jm_nonav'); } if(!jQuery('#jappix_mini div.jm_conversation:visible:last').next().size()) { jQuery('#jappix_mini a.jm_switch.jm_right').addClass('jm_nonav'); } } // Must remove the overflow switcher? else { jQuery('#jappix_mini a.jm_switch').remove(); jQuery('#jappix_mini div.jm_conversation:hidden').show(); } } } catch(e) { JappixConsole.error('JappixMini.updateOverflow', e); } }; /** * Click events on the chat overflow * @public * @return {undefined} */ self.overflowEvents = function() { try { jQuery('#jappix_mini a.jm_switch').click(function() { var this_sel = jQuery(this); // Nothing to do? if(this_sel.hasClass('jm_nonav')) { return false; } var hide_this = ''; var show_this = ''; // Go left? if(this_sel.is('.jm_left')) { show_this = jQuery('#jappix_mini div.jm_conversation:visible:first').prev(); if(show_this.size()) { hide_this = jQuery('#jappix_mini div.jm_conversation:visible:last'); } } // Go right? else { show_this = jQuery('#jappix_mini div.jm_conversation:visible:last').next(); if(show_this.size()) { hide_this = jQuery('#jappix_mini div.jm_conversation:visible:first'); } } // Update overflow content if(show_this && show_this.size()) { // Hide if(hide_this && hide_this.size()) { hide_this.hide(); // Close the opened chat if(hide_this.find('a.jm_pane').hasClass('jm_clicked')) { self.switchPane(); } } // Show show_this.show(); // Update navigation buttons jQuery('#jappix_mini a.jm_switch').removeClass('jm_nonav'); if((this_sel.is('.jm_right') && !show_this.next().size()) || (this_sel.is('.jm_left') && !show_this.prev().size())) { this_sel.addClass('jm_nonav'); } // Update notification counters self.notifyCounters(); } return false; }); } catch(e) { JappixConsole.error('JappixMini.overflowEvents', e); } }; /** * Creates the Jappix Mini DOM content * @public * @param {string} domain * @param {string} user * @param {string} password * @return {undefined} */ self.create = function(domain, user, password) { try { // Try to restore the DOM var dom = JappixDataStore.getDB(MINI_HASH, 'jappix-mini', 'dom'); var suspended = false; var resumed = false; // Reset DOM storage (free memory) JappixDataStore.removeDB(MINI_HASH, 'jappix-mini', 'dom'); // Invalid stored DOM? if(dom && isNaN(jQuery(dom).find('a.jm_pane.jm_button span.jm_counter').text())) { dom = null; } // Old DOM? (saved session) if(dom) { // Attempt to resume connection con = new JSJaCHttpBindingConnection(); self.setupCon(con); resumed = con.resume(); // Read the old nickname MINI_NICKNAME = JappixDataStore.getDB(MINI_HASH, 'jappix-mini', 'nickname'); // Marker suspended = true; MINI_ROSTER_INIT = true; } // New DOM? else { dom = '
' + '
' + '
' + '' + '' + '' + JappixCommon._e("Please wait...") + '' + '' + '
' + '
'; } // Create the DOM jQuery('body').append(''); // Hide the roster picker panels jQuery('#jappix_mini a.jm_status.active, #jappix_mini a.jm_join.active').removeClass('active'); jQuery('#jappix_mini div.jm_status_picker').hide(); jQuery('#jappix_mini div.jm_chan_suggest').remove(); // Chat navigation overflow events self.overflowEvents(); // Delay to fix DOM lag bug (CSS file not yet loaded) jQuery('#jappix_mini').everyTime(100, function() { var this_sel = jQuery(this); if(this_sel.is(':visible')) { JappixConsole.info('CSS loaded asynchronously.'); this_sel.stopTime(); // Re-process chat overflow self.updateOverflow(); // Adapt roster height self.adaptRoster(); // Update current pixel streams self.updatePixStream(); } }); // Auto-check if ads should be added if(ADS_ENABLE === 'on' && GADS_CLIENT && GADS_SLOT) { jQuery('#jappix_mini div.jm_conversations').everyTime('60s', function() { JappixConsole.debug('JappixMini.create[timer]', 'Auto-updating ads...'); self.updatePixStream(); JappixConsole.debug('JappixMini.create[timer]', 'Done auto-updating ads.'); }); } // CSS refresh (Safari display bug when restoring old DOM) jQuery('#jappix_mini div.jm_starter').css('float', 'right'); jQuery('#jappix_mini div.jm_conversations, #jappix_mini div.jm_conversation, #jappix_mini a.jm_switch').css('float', 'left'); // The click events jQuery('#jappix_mini a.jm_button').click(function() { // Using a try/catch override IE issues try { // Presence counter var counter = '#jappix_mini a.jm_pane.jm_button span.jm_counter'; // Cannot open the roster? if(jQuery(counter).text() == JappixCommon._e("Please wait...")) { return false; } // Not yet connected? if(jQuery(counter).text() == JappixCommon._e("Chat")) { // Remove the animated bubble jQuery('#jappix_mini div.jm_starter span.jm_animate').remove(); // Add a waiting marker jQuery(counter).text(JappixCommon._e("Please wait...")); // Launch the connection! self.connect(domain, user, password); return false; } // Normal actions if(!jQuery(this).hasClass('jm_clicked')) { self.showRoster(); } else { self.hideRoster(); } } catch(e) {} finally { return false; } }); jQuery('#jappix_mini a.jm_status').click(function() { // Using a try/catch override IE issues try { var this_sel = jQuery(this); var is_active = this_sel.hasClass('active'); jQuery('#jappix_mini div.jm_actions a').blur().removeClass('active'); if(is_active) { jQuery('#jappix_mini div.jm_status_picker').hide(); } else { jQuery('#jappix_mini div.jm_chan_suggest').remove(); jQuery('#jappix_mini div.jm_status_picker').show(); this_sel.addClass('active'); } } catch(e) {} finally { return false; } }); jQuery('#jappix_mini div.jm_status_picker a').click(function() { // Using a try/catch override IE issues try { var this_sel = jQuery(this); // Generate an array of presence change XIDs var pr_xid = ['']; jQuery('#jappix_mini div.jm_conversation[data-type="groupchat"]').each(function() { var this_sub_sel = jQuery(this); pr_xid.push(unescape(this_sub_sel.attr('data-xid')) + '/' + unescape(this_sub_sel.attr('data-nick'))); }); // Loop on XIDs var new_status = this_sel.data('status'); jQuery.each(pr_xid, function(key, value) { switch(new_status) { case 'available': self.presence('', '', '', '', value); break; case 'away': self.presence('', 'away', '', '', value); break; case 'dnd': self.presence('', 'dnd', '', '', value); break; case 'unavailable': self.disconnect(); break; default: self.presence('', '', '', '', value); break; } }); // Switch the status if(new_status != 'unavailable') { jQuery('#jappix_mini a.jm_status span').removeClass('jm_available jm_away jm_dnd jm_unavailable') .addClass('jm_' + this_sel.data('status')); jQuery('#jappix_mini div.jm_status_picker').hide(); jQuery('#jappix_mini a.jm_status').blur().removeClass('active'); } } catch(e) {} finally { return false; } }); jQuery('#jappix_mini div.jm_actions a.jm_join').click(function() { // Using a try/catch override IE issues try { var this_sel = jQuery(this); // Any suggested chat/groupchat? if((MINI_SUGGEST_CHATS && MINI_SUGGEST_CHATS.length) || (MINI_SUGGEST_GROUPCHATS && MINI_SUGGEST_GROUPCHATS.length)) { var is_active = this_sel.hasClass('active'); jQuery('#jappix_mini div.jm_actions a').blur().removeClass('active'); if(is_active) { jQuery('#jappix_mini div.jm_chan_suggest').remove(); } else { // Button style jQuery('#jappix_mini div.jm_status_picker').hide(); this_sel.addClass('active'); // Generate selector code var chans_html = ''; // Generate the groupchat links HTML for(var i = 0; i < MINI_SUGGEST_GROUPCHATS.length; i++) { // Empty value? if(!MINI_SUGGEST_GROUPCHATS[i]) { continue; } // Using a try/catch override IE issues try { var chat_room = JappixCommon.bareXID(JappixCommon.generateXID(MINI_SUGGEST_GROUPCHATS[i], 'groupchat')); var chat_pwd = MINI_SUGGEST_PASSWORDS[i] || ''; chans_html += '' + '' + '' + JappixCommon.getXIDNick(chat_room).htmlEnc() + '' + ''; } catch(e) {} } // Any separation space to add? if(chans_html) { chans_html += '
'; } // Generate the chat links HTML for(var j = 0; j < MINI_SUGGEST_CHATS.length; j++) { // Empty value? if(!MINI_SUGGEST_CHATS[j]) { continue; } // Using a try/catch override IE issues try { // Read current chat values var chat_xid = JappixCommon.bareXID(JappixCommon.generateXID(MINI_SUGGEST_CHATS[j], 'chat')); var chat_hash = hex_md5(chat_xid); var chat_nick = jQuery('#jappix_mini a#friend-' + chat_hash).attr('data-nick'); // Get current chat nickname if(!chat_nick) { chat_nick = JappixCommon.getXIDNick(chat_xid); } else { chat_nick = unescape(chat_nick); } // Generate HTML for current chat chans_html += '' + '' + '' + JappixCommon.getXIDNick(chat_nick).htmlEnc() + '' + ''; } catch(e) {} } // Any separation space to add? if(chans_html) { chans_html += '
'; } // Append selector code jQuery('#jappix_mini div.jm_actions').append( '
' + chans_html + '' + '' + '' + JappixCommon._e("Other") + '' + '' + '
' ); // Click events jQuery('#jappix_mini div.jm_chan_suggest a').click(function() { // Using a try/catch override IE issues try { var this_sub_sel = jQuery(this); // Chat? if(this_sub_sel.is('.jm_suggest_chat')) { var current_chat = unescape(this_sub_sel.attr('data-xid')); self.chat('chat', current_chat, this_sub_sel.find('span.jm_chan_name').text(), hex_md5(current_chat)); } // Groupchat? else if(this_sub_sel.is('.jm_suggest_groupchat')) { var current_groupchat = unescape(this_sub_sel.attr('data-xid')); var current_password = this_sub_sel.attr('data-pwd') || null; if(current_password) current_password = unescape(current_password); self.chat('groupchat', current_groupchat, this_sub_sel.find('span.jm_chan_name').text(), hex_md5(current_groupchat), current_password); } // Default prompt? else { self.groupchatPrompt(); } } catch(e) {} finally { return false; } }); // Adapt chan suggest height self.adaptRoster(); } } // Default action else { self.groupchatPrompt(); } } catch(e) {} finally { return false; } }); // Updates the roster with only searched terms jQuery('#jappix_mini div.jm_roster div.jm_search input.jm_searchbox').keyup(function(e) { var this_sel = jQuery(this); // Avoid buddy navigation to be reseted if((e.keyCode == 38) || (e.keyCode == 40)) { return; } // Escape key pressed? if(e.keyCode == 27) { this_sel.val(''); } // Save current value this_sel.attr('data-value', this_sel.val()); // Don't filter at each key up (faster for computer) var _this = this; JappixCommon.typewatch()(function() { // Using a try/catch to override IE issues try { // Get values var search = jQuery(_this).val(); var regex = new RegExp('((^)|( ))' + JappixCommon.escapeRegex(search), 'gi'); // Reset results jQuery('#jappix_mini a.jm_friend.jm_hover').removeClass('jm_hover'); jQuery('#jappix_mini div.jm_roster div.jm_grouped').show(); // If there is no search, we don't need to loop over buddies if(!search) { jQuery('#jappix_mini div.jm_roster div.jm_buddies a.jm_online').show(); // Update groups visibility self.updateGroups(); return; } // Filter buddies jQuery('#jappix_mini div.jm_roster div.jm_buddies a.jm_online').each(function() { var this_sub_sel = jQuery(this); var nick = unescape(this_sub_sel.data('nick')); if(nick.match(regex)) { this_sub_sel.show(); } else { this_sub_sel.hide(); } }); // Filter groups jQuery('#jappix_mini div.jm_roster div.jm_grouped').each(function() { var this_sub_sel = jQuery(this); if(!this_sub_sel.find('a.jm_online:visible').size()) { this_sub_sel.hide(); } }); // Focus on the first buddy jQuery('#jappix_mini div.jm_roster div.jm_buddies a.jm_online:visible:first').addClass('jm_hover'); } catch(e) {} finally { return false; } }, 500); }); // Roster navigation jQuery(document).keydown(function(e) { // Cannot work if roster is not opened if(jQuery('#jappix_mini div.jm_roster').is(':hidden')) { return; } // Up/Down keys if((e.keyCode == 38) || (e.keyCode == 40)) { // Hover the last/first buddy if(!jQuery('#jappix_mini a.jm_online.jm_hover').size()) { if(e.keyCode == 38) { jQuery('#jappix_mini a.jm_online:visible:last').addClass('jm_hover'); } else { jQuery('#jappix_mini a.jm_online:visible:first').addClass('jm_hover'); } } // Hover the previous/next buddy else if(jQuery('#jappix_mini a.jm_online:visible').size() > 1) { var hover_index = jQuery('#jappix_mini a.jm_online:visible').index(jQuery('a.jm_hover')); // Up (decrement) or down (increment)? if(e.keyCode == 38) { hover_index--; } else { hover_index++; } if(!hover_index) { hover_index = 0; } // No buddy before/after? if(!jQuery('#jappix_mini a.jm_online:visible').eq(hover_index).size()) { if(e.keyCode == 38) { hover_index = jQuery('#jappix_mini a.jm_online:visible:last').index(); } else { hover_index = 0; } } // Hover the previous/next buddy jQuery('#jappix_mini a.jm_friend.jm_hover').removeClass('jm_hover'); jQuery('#jappix_mini a.jm_online:visible').eq(hover_index).addClass('jm_hover'); } // Scroll to the hovered buddy (if out of limits) jQuery('#jappix_mini div.jm_roster div.jm_buddies').scrollTo(jQuery('#jappix_mini a.jm_online.jm_hover'), 0, {margin: true}); return false; } // Enter key if((e.keyCode == 13) && jQuery('#jappix_mini a.jm_friend.jm_hover').size()) { jQuery('#jappix_mini a.jm_friend.jm_hover').click(); return false; } }); // Chat type re-focus jQuery(document).keypress(function(evt) { // Cannot work if an input/textarea is already focused or chat is not opened var path = '#jappix_mini div.jm_conversation div.jm_chat-content'; if(jQuery('input, textarea').is(':focus') || !jQuery(path).is(':visible')) { return; } // May cause some compatibility issues try { // Get key value var key_value = jQuery.trim(String.fromCharCode(evt.which)); // Re-focus on opened chat? if(key_value) { // Path to chat input var path_input = path + ' input.jm_send-messages'; // Use a timer to override the DOM lag issue jQuery(document).oneTime(10, function() { // Get input values select_input = jQuery(path_input); value_input = select_input.val(); // Append pressed key value select_input.val(value_input + key_value); select_input.focus(); // Put cursor at the end of input select_input[0].selectionStart = select_input[0].selectionEnd = value_input.length + 1; }); } } catch(e) {} }); // Hides the roster when clicking away of Jappix Mini jQuery(document).click(function(evt) { if(!jQuery(evt.target).parents('#jappix_mini').size() && !JappixCommon.exists('#jappix_popup')) { self.hideRoster(); } }); // Hides all panes double clicking away of Jappix Mini jQuery(document).dblclick(function(evt) { if(!jQuery(evt.target).parents('#jappix_mini').size() && !JappixCommon.exists('#jappix_popup')) { self.switchPane(); } }); // Suspended session resumed? if(suspended) { // Initialized marker MINI_INITIALIZED = true; // Not resumed? (need to reconnect) if(!resumed) { // Restore previous reconnect counter var reconnect = JappixDataStore.getDB(MINI_HASH, 'jappix-mini', 'reconnect'); if(!isNaN(reconnect)) { MINI_RECONNECT = parseInt(reconnect); } // Restore queued functions self.unserializeQueue(); // Simulate a network error to get the same silent reconnect effect self.disconnected(); } // Restore chat input values jQuery('#jappix_mini div.jm_conversation input.jm_send-messages').each(function() { var this_sub_sel = jQuery(this); var chat_value = this_sub_sel.attr('data-value'); if(chat_value) { this_sub_sel.val(chat_value); } }); // Restore roster filter value var search_box = jQuery('#jappix_mini div.jm_roster div.jm_search input.jm_searchbox'); var filter_value = search_box.attr('data-value'); if(filter_value) { search_box.val(filter_value).keyup(); } // Restore buddy events self.eventsBuddy('#jappix_mini a.jm_friend'); // Restore chat click events jQuery('#jappix_mini div.jm_conversation').each(function() { var this_sub_sel = jQuery(this); self.chatEvents(this_sub_sel.attr('data-type'), unescape(this_sub_sel.attr('data-xid')), this_sub_sel.attr('data-hash')); }); // Restore init marker on all groupchats jQuery('#jappix_mini div.jm_conversation[data-type="groupchat"]').attr('data-init', 'true'); // Scroll down to the last message var scroll_hash = jQuery('#jappix_mini div.jm_conversation:has(a.jm_pane.jm_clicked)').attr('data-hash'); var scroll_position = JappixDataStore.getDB(MINI_HASH, 'jappix-mini', 'scroll'); // Any scroll position? if(scroll_position) { scroll_position = parseInt(scroll_position); } if(scroll_hash) { // Use a timer to override the DOM lag issue jQuery(document).oneTime(200, function() { self.messageScroll(scroll_hash, scroll_position); }); } // Update notification counters self.notifyCounters(); } // Can auto-connect? else if(MINI_AUTOCONNECT) { self.connect(domain, user, password); } // Cannot auto-connect? else { // Chat text jQuery('#jappix_mini a.jm_pane.jm_button span.jm_counter').text(JappixCommon._e("Chat")); // Must animate? if(MINI_ANIMATE) { // Add content jQuery('#jappix_mini div.jm_starter').prepend( '' ); } } } catch(e) { JappixConsole.error('JappixMini.create', e); } }; /** * Buddy events * @public * @param {string} path * @return {undefined} */ self.eventsBuddy = function(path) { try { var selector = jQuery(path); // Restore buddy click events selector.click(function() { // Using a try/catch override IE issues try { var this_sel = jQuery(this); self.chat('chat', unescape(this_sel.attr('data-xid')), unescape(this_sel.attr('data-nick')), this_sel.attr('data-hash')); } catch(e) {} finally { return false; } }); // Restore buddy hover events selector.hover(function() { jQuery('#jappix_mini a.jm_friend.jm_hover').removeClass('jm_hover'); jQuery(this).addClass('jm_hover'); }, function() { jQuery(this).removeClass('jm_hover'); }); // Restore buddy mousedown events selector.mousedown(function() { jQuery('#jappix_mini a.jm_friend.jm_hover').removeClass('jm_hover'); jQuery(this).addClass('jm_hover'); }); // Restore buddy focus events selector.focus(function() { jQuery('#jappix_mini a.jm_friend.jm_hover').removeClass('jm_hover'); jQuery(this).addClass('jm_hover'); }); // Restore buddy blur events selector.blur(function() { jQuery(this).removeClass('jm_hover'); }); } catch(e) { JappixConsole.error('JappixMini.eventsBuddy', e); } }; /** * Displays a given message * @public * @param {string} type * @param {string} body * @param {string} xid * @param {string} nick * @param {string} hash * @param {string} time * @param {string} stamp * @param {string} message_type * @return {undefined} */ self.displayMessage = function(type, body, xid, nick, hash, time, stamp, message_type) { try { // Generate path var path = '#chat-' + hash; // Can scroll? var cont_scroll = document.getElementById('received-' + hash); var can_scroll = false; if(!cont_scroll.scrollTop || ((cont_scroll.clientHeight + cont_scroll.scrollTop) == cont_scroll.scrollHeight)) { can_scroll = true; } // Remove the previous message border if needed var last = jQuery(path + ' div.jm_group:last'); var last_stamp = parseInt(last.attr('data-stamp')); var last_b = jQuery(path + ' b:last'); var last_xid = last_b.attr('data-xid'); var last_type = last.attr('data-type'); var grouped = false; var header = ''; if((last_xid == xid) && (message_type == last_type) && ((stamp - last_stamp) <= 1800)) { grouped = true; } else { // Write the message date if(nick) header += '' + time + ''; // Write the buddy name at the top of the message group if(type == 'groupchat') header += '' + nick.htmlEnc() + ''; else if(nick == 'me') header += '' + JappixCommon._e("You") + ''; else header += '' + nick.htmlEnc() + ''; } // Apply the /me command var me_command = false; if(body.match(/^\/me /i)) { body = body.replace(/^\/me /i, nick + ' '); // Marker me_command = true; } // HTML-encode the message body = body.htmlEnc(); // Apply the smileys body = body.replace(/(;-?\))(\s|$)/gi, self.smiley('wink', '$1')) .replace(/(:-?3)(\s|$)/gi, self.smiley('waii', '$1')) .replace(/(:-?\()(\s|$)/gi, self.smiley('unhappy', '$1')) .replace(/(:-?P)(\s|$)/gi, self.smiley('tongue', '$1')) .replace(/(:-?O)(\s|$)/gi, self.smiley('surprised', '$1')) .replace(/(:-?\))(\s|$)/gi, self.smiley('smile', '$1')) .replace(/(\^_?\^)(\s|$)/gi, self.smiley('happy', '$1')) .replace(/(:-?D)(\s|$)/gi, self.smiley('grin', '$1')); // Format the text body = body.replace(/(^|\s|>|\()((\*)([^<>'"\*]+)(\*))($|\s|<|\))/gi, '$1$2$6') .replace(/(^|\s|>|\()((\/)([^<>'"\/]+)(\/))($|\s|<|\))/gi, '$1$2$6') .replace(/(^|\s|>|\()((_)([^<>'"_]+)(_))($|\s|<|\))/gi, '$1$2$6'); // Filter the links body = JappixLinks.apply(body, 'mini'); // Generate the message code if(me_command) { body = '' + body + ''; } body = '

' + body + '

'; // Create the message if(grouped) { jQuery('#jappix_mini #chat-' + hash + ' div.jm_received-messages div.jm_group:last').append(body); } else { jQuery('#jappix_mini #chat-' + hash + ' div.jm_chatstate_typing').before('
' + header + body + '
'); } // Scroll to this message if(can_scroll) { self.messageScroll(hash); } } catch(e) { JappixConsole.error('JappixMini.displayMessage', e); } }; /** * Switches to a given point * @public * @param {string} element * @param {string} hash * @return {undefined} */ self.switchPane = function(element, hash) { try { // Hide every item self.hideRoster(); jQuery('#jappix_mini a.jm_pane').removeClass('jm_clicked'); jQuery('#jappix_mini div.jm_chat-content').hide(); // Show the asked element if(element && (element != 'roster')) { var current = '#jappix_mini #' + element; // Navigate to this chat if(jQuery(current).size() && jQuery(current).is(':hidden')) { var click_nav = ''; // Before or after? if(jQuery('#jappix_mini div.jm_conversation:visible:first').prevAll().is('#' + element)) click_nav = jQuery('#jappix_mini a.jm_switch.jm_left'); else click_nav = jQuery('#jappix_mini a.jm_switch.jm_right'); // Click previous or next if(click_nav) { while(jQuery(current).is(':hidden') && !click_nav.hasClass('jm_nonav')) click_nav.click(); } } // Show it jQuery(current + ' a.jm_pane').addClass('jm_clicked'); jQuery(current + ' div.jm_chat-content').show(); // Use a timer to override the DOM lag issue jQuery(document).oneTime(10, function() { jQuery(current + ' input.jm_send-messages').focus(); }); // Scroll to the last message & adapt chat if(hash) { self.messageScroll(hash); self.updatePixStream(hash); } } } catch(e) { JappixConsole.error('JappixMini.switchPane', e); } }; /** * Scrolls to the last chat message * @public * @param {string} hash * @param {string} position * @return {undefined} */ self.messageScroll = function(hash, position) { try { var id = document.getElementById('received-' + hash); // No defined position? if(!position) { position = id.scrollHeight; } id.scrollTop = position; } catch(e) { JappixConsole.error('JappixMini.messageScroll', e); } }; /** * Prompts the user with a given text * @public * @param {string} text * @param {string} value * @return {undefined} */ self.openPrompt = function(text, value) { try { // Initialize var prompt = '#jappix_popup div.jm_prompt'; var input = prompt + ' form input'; var value_input = input + '[type="text"]'; // Remove the existing prompt self.closePrompt(); // Add the prompt jQuery('body').append( '
' + '
' + '
' + text + '' + '' + '' + '
' + '
' + '
' + '
' ); // Vertical center var vert_pos = '-' + ((jQuery(prompt).height() / 2) + 10) + 'px'; jQuery(prompt).css('margin-top', vert_pos); // Apply the value? if(value) { jQuery(value_input).val(value); } // Focus on the input jQuery(document).oneTime(10, function() { jQuery(value_input).focus(); }); // Cancel event jQuery(input + '[type="reset"]').click(function() { try { self.closePrompt(); } catch(e) {} finally { return false; } }); } catch(e) { JappixConsole.error('JappixMini.openPrompt', e); } }; /** * Returns the prompt value * @public * @return {string} */ self.closePrompt = function() { try { // Read the value var value = jQuery('#jappix_popup div.jm_prompt form input').val(); // Remove the popup jQuery('#jappix_popup').remove(); return value; } catch(e) { JappixConsole.error('JappixMini.closePrompt', e); } }; /** * Opens the new groupchat prompt * @public * @return {undefined} */ self.groupchatPrompt = function() { try { // Create a new prompt self.openPrompt(JappixCommon._e("Please enter the group chat address to join.")); // When prompt submitted jQuery('#jappix_popup div.jm_prompt form').submit(function() { try { // Read the value var join_this = self.closePrompt(); // Any submitted chat to join? if(join_this) { // Get the chat room to join chat_room = JappixCommon.bareXID(JappixCommon.generateXID(join_this, 'groupchat')); // Create a new groupchat self.chat('groupchat', chat_room, JappixCommon.getXIDNick(chat_room), hex_md5(chat_room)); } } catch(e) {} finally { return false; } }); } catch(e) { JappixConsole.error('JappixMini.groupchatPrompt', e); } }; /** * Manages and creates a chat * @public * @param {string} type * @param {string} xid * @param {string} nick * @param {string} hash * @param {string} pwd * @param {boolean} show_pane * @return {boolean} */ self.chat = function(type, xid, nick, hash, pwd, show_pane) { try { var current = '#jappix_mini #chat-' + hash; var nickname = null; // Not yet added? if(!JappixCommon.exists(current)) { // Groupchat nickname if(type == 'groupchat') { // Random nickname? if(!MINI_NICKNAME && MINI_RANDNICK) MINI_NICKNAME = self.randomNick(); nickname = MINI_NICKNAME; // No nickname? if(!nickname) { // Create a new prompt self.openPrompt(JappixCommon.printf(JappixCommon._e("Please enter your nickname to join %s."), xid)); // When prompt submitted jQuery('#jappix_popup div.jm_prompt form').submit(function() { try { // Read the value var nickname = self.closePrompt(); // Update the stored one if(nickname) { MINI_NICKNAME = nickname; } // Launch it again! self.chat(type, xid, nick, hash, pwd); } catch(e) {} finally { return false; } }); return; } } // Create the HTML markup var html = '
' + '
' + '
' + '' + nick + ''; // Check if the chat/groupchat exists var groupchat_exists = false; var chat_exists = false; if((type == 'groupchat') && MINI_GROUPCHATS && MINI_GROUPCHATS.length) { for(var g in MINI_GROUPCHATS) { if(xid == JappixCommon.bareXID(JappixCommon.generateXID(MINI_GROUPCHATS[g], 'groupchat'))) { groupchat_exists = true; break; } } } if((type == 'chat') && MINI_CHATS && MINI_CHATS.length) { for(var c in MINI_CHATS) { if(xid == JappixCommon.bareXID(JappixCommon.generateXID(MINI_CHATS[c], 'chat'))) { chat_exists = true; break; } } } // Any close button to display? if(((type == 'groupchat') && !groupchat_exists) || ((type == 'chat') && !chat_exists) || ((type != 'groupchat') && (type != 'chat'))) { html += ''; } html += '
' + '
' + '
' + '
' + JappixCommon.printf(JappixCommon._e("%s is typing..."), nick.htmlEnc()) + '
' + '
' + '
' + '' + '' + '' + '
' + '
' + '' + '' + nick.htmlEnc() + '' + '' + '
'; jQuery('#jappix_mini div.jm_conversations').prepend(html); // Get the presence of this friend if(type != 'groupchat') { var selector = jQuery('#jappix_mini a#friend-' + hash + ' span.jm_presence'); // Default presence var show = 'available'; // Read the presence if(selector.hasClass('jm_unavailable') || !selector.size()) { show = 'unavailable'; } else if(selector.hasClass('jm_chat')) { show = 'chat'; } else if(selector.hasClass('jm_away')) { show = 'away'; } else if(selector.hasClass('jm_xa')) { show = 'xa'; } else if(selector.hasClass('jm_dnd')) { show = 'dnd'; } // Create the presence marker jQuery(current + ' a.jm_chat-tab').prepend(''); } // The chat events self.chatEvents(type, xid, hash); // Join the groupchat if(type == 'groupchat') { // Add nickname & init values jQuery(current).attr('data-nick', escape(nickname)) .attr('data-init', 'false'); // Send the first groupchat presence self.presence('', '', '', '', xid + '/' + nickname, pwd, true, self.handleMUC); } } // Focus on our pane if(show_pane !== false) { jQuery(document).oneTime(10, function() { self.switchPane('chat-' + hash, hash); }); } // Update chat overflow self.updateOverflow(); } catch(e) { JappixConsole.error('JappixMini.chat', e); } finally { return false; } }; /** * Events on the chat tool * @public * @param {string} type * @param {string} xid * @param {string} hash * @return {undefined} */ self.chatEvents = function(type, xid, hash) { try { var current_sel = jQuery('#jappix_mini #chat-' + hash); // Submit the form current_sel.find('form').submit(function() { return self.sendMessage(this); }); // Click on the tab current_sel.find('a.jm_chat-tab').click(function() { // Using a try/catch override IE issues try { // Not yet opened: open it! if(!jQuery(this).hasClass('jm_clicked')) { // Show it! self.switchPane('chat-' + hash, hash); // Clear the eventual notifications self.clearNotifications(hash); } else { self.switchPane(); } } catch(e) {} finally { return false; } }); // Click on the close button current_sel.find('div.jm_actions').click(function(evt) { // Using a try/catch override IE issues try { // Close button? if(jQuery(evt.target).is('a.jm_close')) { // Gone chatstate if(type != 'groupchat') { self.sendChatstate('gone', xid, hash); } current_sel.stopTime().remove(); // Quit the groupchat? if(type == 'groupchat') { // Send an unavailable presence self.presence('unavailable', '', '', '', xid + '/' + unescape(current_sel.attr('data-nick'))); // Remove this groupchat! self.removeGroupchat(xid); } // Update chat overflow self.updateOverflow(); } else { // Minimize current chat current_sel.find('a.jm_chat-tab.jm_clicked').click(); } } catch(e) {} finally { return false; } }); // Focus on the chat input current_sel.find('input.jm_send-messages').focus(function() { self.clearNotifications(hash); }) // Change on the chat input .keyup(function() { var this_sel = jQuery(this); this_sel.attr('data-value', this_sel.val()); }) // Chat tabulate or escape press .keydown(function(e) { // Tabulate? if(e.keyCode == 9) { self.switchChat(); return false; } // Escape? if(e.keyCode == 27) { if(current_sel.find('a.jm_close').size()) { // Open next/previous chat if(current_sel.next('div.jm_conversation').size()) { current_sel.next('div.jm_conversation').find('a.jm_pane').click(); } else if(current_sel.prev('div.jm_conversation').size()) { current_sel.prev('div.jm_conversation').find('a.jm_pane').click(); } // Close current chat current_sel.find('a.jm_close').click(); } return false; } }); // Focus/Blur events jQuery('#jappix_mini #chat-' + hash + ' input.jm_send-messages').focus(function() { // Store active chat MINI_ACTIVE = hash; }) .blur(function() { // Reset active chat if(MINI_ACTIVE == hash) { MINI_ACTIVE = null; } }); // Chatstate events self.eventsChatstate(xid, hash, type); } catch(e) { JappixConsole.error('JappixMini.chatEvents', e); } }; /** * Opens the next chat * @public * @return {undefined} */ self.switchChat = function() { try { if(jQuery('#jappix_mini div.jm_conversation').size() <= 1) { return; } // Get the current chat index var chat_index = jQuery('#jappix_mini div.jm_conversation:has(a.jm_pane.jm_clicked)').index(); chat_index++; if(!chat_index) { chat_index = 0; } // No chat after? if(!jQuery('#jappix_mini div.jm_conversation').eq(chat_index).size()) { chat_index = 0; } // Avoid disabled chats while(jQuery('#jappix_mini div.jm_conversation').eq(chat_index).hasClass('jm_disabled')) { chat_index++; } // Show the next chat var chat_hash = jQuery('#jappix_mini div.jm_conversation').eq(chat_index).attr('data-hash'); if(chat_hash) { self.switchPane('chat-' + chat_hash, chat_hash); } } catch(e) { JappixConsole.error('JappixMini.switchChat', e); } }; /** * Shows the roster * @public * @return {undefined} */ self.showRoster = function() { try { self.switchPane('roster'); jQuery('#jappix_mini div.jm_roster').show(); jQuery('#jappix_mini a.jm_button').addClass('jm_clicked'); // Update groups visibility self.updateGroups(); jQuery(document).oneTime(10, function() { jQuery('#jappix_mini div.jm_roster div.jm_search input.jm_searchbox').focus(); }); } catch(e) { JappixConsole.error('JappixMini.showRoster', e); } }; /** * Hides the roster * @public * @return {undefined} */ self.hideRoster = function() { try { // Close the roster pickers jQuery('#jappix_mini a.jm_status.active, #jappix_mini a.jm_join.active').click(); // Hide the roster box jQuery('#jappix_mini div.jm_roster').hide(); jQuery('#jappix_mini a.jm_button').removeClass('jm_clicked'); // Clear the search box and show all online contacts jQuery('#jappix_mini div.jm_roster div.jm_search input.jm_searchbox').val('').attr('data-value', ''); jQuery('#jappix_mini div.jm_roster div.jm_grouped').show(); jQuery('#jappix_mini div.jm_roster div.jm_buddies a.jm_online').show(); jQuery('#jappix_mini a.jm_friend.jm_hover').removeClass('jm_hover'); } catch(e) { JappixConsole.error('JappixMini.hideRoster', e); } }; /** * Removes a groupchat from DOM * @public * @param {string} xid * @return {undefined} */ self.removeGroupchat = function(xid) { try { // Remove the groupchat private chats & the groupchat buddies from the roster jQuery('#jappix_mini div.jm_conversation[data-origin="' + escape(JappixCommon.cutResource(xid)) + '"], #jappix_mini div.jm_roster div.jm_grouped[data-xid="' + escape(xid) + '"]').remove(); // Update the presence counter self.updateRoster(); } catch(e) { JappixConsole.error('JappixMini.removeGroupchat', e); } }; /** * Initializes Jappix Mini * @public * @return {undefined} */ self.initialize = function() { try { // Update the marker MINI_INITIALIZED = true; // Send the initial presence self.presence(); // Join the groupchats (first) for(var i = 0; i < MINI_GROUPCHATS.length; i++) { // Empty value? if(!MINI_GROUPCHATS[i]) { continue; } // Using a try/catch override IE issues try { // Current chat room var chat_room = JappixCommon.bareXID(JappixCommon.generateXID(MINI_GROUPCHATS[i], 'groupchat')); // Open the current chat self.chat('groupchat', chat_room, JappixCommon.getXIDNick(chat_room), hex_md5(chat_room), MINI_PASSWORDS[i], MINI_SHOWPANE); } catch(e) {} } // Join the chats (then) for(var j = 0; j < MINI_CHATS.length; j++) { // Empty value? if(!MINI_CHATS[j]) { continue; } // Using a try/catch override IE issues try { // Current chat user var chat_xid = JappixCommon.bareXID(JappixCommon.generateXID(MINI_CHATS[j], 'chat')); var chat_hash = hex_md5(chat_xid); var chat_nick = jQuery('#jappix_mini a#friend-' + chat_hash).attr('data-nick'); if(!chat_nick) { chat_nick = JappixCommon.getXIDNick(chat_xid); } else { chat_nick = unescape(chat_nick); } // Open the current chat self.chat('chat', chat_xid, chat_nick, chat_hash); } catch(e) {} } // Must show the roster? if(!MINI_AUTOCONNECT && !MINI_GROUPCHATS.length && !MINI_CHATS.length) { jQuery(document).oneTime(10, function() { self.showRoster(); }); } } catch(e) { JappixConsole.error('JappixMini.initialize', e); } }; /** * Displays a list of roster buddy * @public * @param {object} buddy_arr * @return {boolean} */ self.addListBuddy = function(buddy_arr) { try { var c, b, nick, hash, xid, subscription; var buddy_str = ''; // Loop on groups for(c in buddy_arr) { buddy_arr[c] = buddy_arr[c].sort(); // Group: start if(c != MINI_ROSTER_NOGROUP) { buddy_str += '
'; buddy_str += '
' + c.htmlEnc() + '
'; } // Loop on buddies for(b in buddy_arr[c]) { // Current buddy data buddy_str += self.codeAddBuddy( buddy_arr[c][b][0], buddy_arr[c][b][1], buddy_arr[c][b][2], buddy_arr[c][b][3], false ); } // Group: end if(c != MINI_ROSTER_NOGROUP) { buddy_str += '
'; } } // Append code jQuery('#jappix_mini div.jm_roster div.jm_buddies').html(buddy_str); // Events on these buddies self.eventsBuddy('#jappix_mini a.jm_friend'); return true; } catch(e) { JappixConsole.error('JappixMini.addListBuddy', e); return false; } }; /** * Displays a roster buddy * @public * @param {string} xid * @param {string} hash * @param {string} nick * @param {string} groupchat * @param {string} subscription * @param {string} group * @return {boolean} */ self.addBuddy = function(xid, hash, nick, groupchat, subscription, group) { try { var bare_xid = JappixCommon.bareXID(xid); // Element var element = '#jappix_mini a#friend-' + hash; // Yet added? if(JappixCommon.exists(element)) { jQuery(element).remove(); } // Generate the path var path = '#jappix_mini div.jm_roster div.jm_buddies'; // Generate the groupchat group path if(groupchat) { path = '#jappix_mini div.jm_roster div.jm_grouped_groupchat[data-xid="' + escape(bare_xid) + '"]'; // Must add a groupchat group? if(!JappixCommon.exists(path)) { jQuery('#jappix_mini div.jm_roster div.jm_buddies').append( '
' + '
' + JappixCommon.getXIDNick(groupchat).htmlEnc() + '
' + '
' ); } } else if(group) { path = '#jappix_mini div.jm_roster div.jm_grouped_roster[data-name="' + escape(group) + '"]'; // Must add a roster group? if(!JappixCommon.exists(path)) { jQuery('#jappix_mini div.jm_roster div.jm_buddies').append( '
' + '
' + group.htmlEnc() + '
' + '
' ); } } // Append this buddy content var code = self.codeAddBuddy( nick, hash, xid, subscription ); if(groupchat || group) { jQuery(path).append(code); } else { jQuery(path).prepend(code); } // Need to hide this buddy? if(jQuery('#jappix_mini div.jm_actions a.jm_toggle_view.jm_toggled').size()) { jQuery(element).filter('.jm_offline').hide(); } // Events on this buddy self.eventsBuddy(element); return true; } catch(e) { JappixConsole.error('JappixMini.addBuddy', e); return false; } }; /** * Returns the code for a single buddy to add * @public * @param {string} nick * @param {string} hash * @param {string} xid * @param {string} subscription * @return {string} */ self.codeAddBuddy = function(nick, hash, xid, subscription) { var buddy_str = ''; try { // Buddy: start buddy_str += '' + '' + '' ); } // Play the sound var audio_select = document.getElementById('jm_audio').getElementsByTagName('audio')[0]; // Avoids Safari bug (2011 and less versions) try { audio_select.load(); } finally { audio_select.play(); } } catch(e) { JappixConsole.error('JappixMini.soundPlay', e); } finally { return false; } }; /** * Adapts chat size * @public * @param {string} conversation_path * @return {undefined} */ self.adaptChat = function(conversation_path) { try { var conversation_sel = jQuery('#jappix_mini div.jm_conversation'); if(conversation_path) { conversation_sel = conversation_sel.filter(conversation_path); } if(conversation_sel.size()) { // Reset before doing anything else... conversation_sel.find('div.jm_received-messages').css({ 'max-height': 'none', 'margin-top': 0 }); // Update sizes of chat var pix_stream_sel = conversation_sel.find('div.jm_pix_stream'); var received_messages_sel = conversation_sel.find('div.jm_received-messages'); var pix_stream_height = pix_stream_sel.height(); if(pix_stream_sel.find('*').size() && pix_stream_height > 0) { received_messages_sel.css({ 'margin-top': pix_stream_height, 'max-height': (received_messages_sel.height() - pix_stream_sel.height()) }); } } } catch(e) { JappixConsole.error('JappixMini.adaptChat', e); } }; /** * Updates given pixel stream * @public * @param {string} hash * @return {undefined} */ self.updatePixStream = function(hash) { try { // Feature supported? (we rely on local storage) if(window.localStorage !== undefined) { // Select chat(s) var conversation_path = '#chat-' + hash; var conversation_sel = jQuery('#jappix_mini div.jm_conversation'); var conversation_all_sel = conversation_sel; if(hash) { conversation_sel = conversation_sel.filter(conversation_path); } else { conversation_sel = conversation_sel.filter(':has(div.jm_chat-content:visible):first'); if(conversation_sel.size()) { conversation_path = '#' + conversation_sel.attr('id'); } else { conversation_path = null; } } // Parse stored dates var stamp_now = JappixDateUtils.getTimeStamp(); var stamp_start = JappixDataStore.getPersistent(MINI_HASH, 'pixel-stream', 'start'); var stamp_end = JappixDataStore.getPersistent(MINI_HASH, 'pixel-stream', 'end'); var in_schedule = false; var to_reschedule = true; if(stamp_start && stamp_end && !isNaN(stamp_start) && !isNaN(stamp_end)) { stamp_start = parseInt(stamp_start, 10); stamp_end = parseInt(stamp_end, 10); in_schedule = (stamp_now >= stamp_start && stamp_end >= stamp_now); to_reschedule = (stamp_now >= stamp_end + MINI_PIXEL_STREAM_INTERVAL); } // Should add ads? if(in_schedule || to_reschedule) { // Store new schedules if(to_reschedule) { JappixDataStore.setPersistent(MINI_HASH, 'pixel-stream', 'start', stamp_now); JappixDataStore.setPersistent(MINI_HASH, 'pixel-stream', 'end', stamp_now + MINI_PIXEL_STREAM_DURATION); } // Process HTML code if(conversation_path && ADS_ENABLE === 'on' && GADS_CLIENT && GADS_SLOT) { var pix_stream_sel = conversation_sel.find('div.jm_pix_stream'); if(!pix_stream_sel.find('*').size()) { JappixConsole.info('JappixMini.updatePixStream', 'Loading pixel stream...'); var pix_stream_other_added = conversation_all_sel.find('div.jm_pix_stream ins.adsbygoogle:first').clone(); if(pix_stream_other_added.size()) { JappixConsole.log('JappixMini.updatePixStream', 'Copy existing pixel stream from DOM'); pix_stream_sel.html(pix_stream_other_added); } else { JappixConsole.log('JappixMini.updatePixStream', 'Fetch fresh pixel stream from server'); pix_stream_sel.html( '' + '' ); } jQuery.getScript('//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js', function() { self.adaptChat(conversation_path); JappixConsole.info('JappixMini.updatePixStream', 'Finished loading pixel stream'); }); } else { JappixConsole.info('JappixMini.updatePixStream', 'Pixel stream already loaded'); } } else { self.resetPixStream(); } } else { self.resetPixStream(); } // Update chat height if(conversation_path) { self.adaptChat(conversation_path); } } } catch(e) { JappixConsole.error('JappixMini.updatePixStream', e); } }; /** * Resets all pixel streams * @public * @return {undefined} */ self.resetPixStream = function() { try { jQuery('#jappix_mini div.jm_pix_stream').empty(); } catch(e) { JappixConsole.error('JappixMini.resetPixStream', e); } }; /** * Returns whether browser is legacy/unsupported or not (IE 7 and less) * @public * @return {undefined} */ self.isLegacy = function() { try { return BrowserDetect.browser == 'Explorer' && BrowserDetect.version <= 7; } catch(e) { JappixConsole.error('JappixMini.isLegacy', e); } }; /** * Loads the Jappix Mini stylesheet * @public * @return {boolean} */ self.loadStylesheet = function() { try { var css_url = []; var css_html = ''; // Do we know the optimized Get API path? if(JAPPIX_MINI_CSS) { css_url.push(JAPPIX_MINI_CSS); } else { // Fallback to non-optimized way, used with standalone Jappix Mini css_url.push(JAPPIX_STATIC + 'stylesheets/mini.css'); } // Append final stylesheet HTML for(var u in css_url) { css_html += ''; } jQuery('head').append(css_html); return true; } catch(e) { JappixConsole.error('JappixMini.loadStylesheet', e); return false; } }; /** * Plugin configurator * @public * @param {object} config_args * @return {undefined} */ self.configure = function(config_args) { try { if(typeof config_args !== 'object') { config_args = {}; } // Read configuration subs connection_config = config_args.connection || {}; application_config = config_args.application || {}; application_network_config = application_config.network || {}; application_interface_config = application_config.interface || {}; application_user_config = application_config.user || {}; application_chat_config = application_config.chat || {}; application_groupchat_config = application_config.groupchat || {}; // Apply new configuration (falling back to defaults if not set) MINI_AUTOCONNECT = application_network_config.autoconnect || MINI_AUTOCONNECT; MINI_SHOWPANE = application_interface_config.showpane || MINI_SHOWPANE; MINI_ANIMATE = application_interface_config.animate || MINI_ANIMATE; MINI_RANDNICK = application_user_config.random_nickname || MINI_RANDNICK; MINI_GROUPCHAT_PRESENCE = application_groupchat_config.show_presence || MINI_GROUPCHAT_PRESENCE; MINI_DISABLE_MOBILE = application_interface_config.no_mobile || MINI_DISABLE_MOBILE; MINI_NICKNAME = application_user_config.nickname || MINI_NICKNAME; MINI_DOMAIN = connection_config.domain || MINI_DOMAIN; MINI_USER = connection_config.user || MINI_USER; MINI_PASSWORD = connection_config.password || MINI_PASSWORD; MINI_RECONNECT_MAX = application_network_config.reconnect_max || MINI_RECONNECT_MAX; MINI_RECONNECT_INTERVAL = application_network_config.reconnect_interval || MINI_RECONNECT_INTERVAL; MINI_CHATS = application_chat_config.open || MINI_CHATS; MINI_GROUPCHATS = application_groupchat_config.open || MINI_GROUPCHATS; MINI_SUGGEST_CHATS = application_chat_config.suggest || MINI_CHATS; MINI_SUGGEST_GROUPCHATS = application_groupchat_config.suggest || MINI_SUGGEST_GROUPCHATS; MINI_SUGGEST_PASSWORDS = application_groupchat_config.suggest_passwords || MINI_SUGGEST_PASSWORDS; MINI_PASSWORDS = application_groupchat_config.open_passwords || MINI_PASSWORDS; MINI_PRIORITY = connection_config.priority || MINI_PRIORITY; MINI_RESOURCE = connection_config.resource || MINI_RESOURCE; MINI_ERROR_LINK = application_interface_config.error_link || MINI_ERROR_LINK; } catch(e) { JappixConsole.error('JappixMini.configure', e); } }; /** * Plugin processor * @public * @param {boolean} autoconnect * @param {boolean} show_pane * @param {string} domain * @param {string} user * @param {string} password * @param {number} priority * @return {undefined} */ self.process = function(autoconnect, show_pane, domain, user, password, priority) { try { // Disabled on mobile? if(MINI_DISABLE_MOBILE && JappixCommon.isMobile()) { JappixConsole.log('Jappix Mini disabled on mobile.'); return; } // Legacy browser? (unsupported) if(self.isLegacy()) { JappixConsole.warn('Jappix Mini cannot load on this browser (unsupported because too old)'); return; } // Save infos to reconnect MINI_DOMAIN = domain; MINI_USER = user; MINI_PASSWORD = password; MINI_HASH = 'jm.' + hex_md5(MINI_USER + '@' + MINI_DOMAIN); if(priority !== undefined) { MINI_PRIORITY = priority; } // Anonymous mode? if(!user || !password) { MINI_ANONYMOUS = true; } else { MINI_ANONYMOUS = false; } // Autoconnect (only if storage available to avoid floods)? if(autoconnect && JappixDataStore.hasDB()) { MINI_AUTOCONNECT = true; } else { MINI_AUTOCONNECT = false; } // Show pane? if(show_pane) { MINI_SHOWPANE = true; } else { MINI_SHOWPANE = false; } // Remove Jappix Mini jQuery('#jappix_mini').remove(); // Reconnect? if(MINI_RECONNECT) { JappixConsole.log('Trying to reconnect (try: ' + MINI_RECONNECT + ')!'); return self.create(domain, user, password); } // Load the Mini stylesheet self.loadStylesheet(); // Disables the browser HTTP-requests stopper jQuery(document).keydown(function(e) { if((e.keyCode == 27) && !JappixSystem.isDeveloper()) { return false; } }); // Save the page title MINI_TITLE = document.title; // Adapts the content to the window size jQuery(window).resize(function() { self.adaptRoster(); self.updateOverflow(); }); // Logouts when Jappix is closed if(BrowserDetect.browser == 'Opera') { // Emulates onbeforeunload on Opera (link clicked) jQuery('a[href]:not([onclick])').click(function() { var this_sel = jQuery(this); // Link attributes var href = this_sel.attr('href') || ''; var target = this_sel.attr('target') || ''; // Not new window or JS link if(href && !href.match(/^#/i) && !target.match(/_blank|_new/i)) { self.saveSession(); } }); // Emulates onbeforeunload on Opera (form submitted) jQuery('form:not([onsubmit])').submit(self.saveSession); } jQuery(window).bind('beforeunload', self.saveSession); // Create the Jappix Mini DOM content self.create(domain, user, password); JappixConsole.log('Welcome to Jappix Mini! Happy coding in developer mode!'); } catch(e) { JappixConsole.error('JappixMini.process', e); } }; /** * Plugin launcher * @public * @param {object} args * @return {undefined} */ self.launch = function(args) { try { // Configure the app self.configure(args); // Initialize the app! self.process( MINI_AUTOCONNECT, MINI_SHOWPANE, MINI_DOMAIN, MINI_USER, MINI_PASSWORD, MINI_PRIORITY ); } catch(e) { JappixConsole.error('JappixMini.launch', e); } }; /** * Return class scope */ return self; })(); /* Legacy compatibility layer */ var launchMini = JappixMini.process;