/* Jappix - An open social platform These are the vCard JS scripts for Jappix ------------------------------------------------- License: AGPL Author: Valérian Saliou, Maranda */ // Bundle var vCard = (function () { /** * Alias of this * @private */ var self = {}; /** * Opens the vCard popup * @public * @return {boolean} */ self.open = function() { try { // Popup HTML content var html = '
' + Common._e("Your profile") + '
' + '
' + '' + Common._e("Identity") + '' + '' + Common._e("Profile image") + '' + '' + Common._e("Others") + '' + '
' + '
' + '
' + '
' + '' + Common._e("Personal") + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
' + '
' + '' + Common._e("Contact") + '' + '' + '' + '' + '' + '' + '' + '
' + '
' + '
' + '
' + '' + Common._e("New") + '' + '' + '' + '
' + Interface.generateFileShare() + '
' + '
' + '
' + '' + Common._e("Current") + '' + '
' + '' + Common._e("Delete") + '' + '
' + Common._e("What a pity! You have no profile image defined in your identity card!") + '
' + '
' + '
' + Common._e("Please wait while your avatar is uploaded...") + '
' + '
' + Common._e("Here it is! A new beautiful profile image!") + '
' + '
' + Common._e("The image file is not supported or has a bad size.") + '
' + '
' + '
' + '
' + '' + Common._e("Address") + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
' + '
' + '' + Common._e("Biography") + '' + '' + '
' + '
' + '
' + '

' + Common._e("Important notice") + '

' + '

' + Common._e("Be careful with the information you store into your profile, because it might be accessible by everyone (even someone you don't want to).") + '

' + '

' + Common._e("Not everything is private on XMPP; this is one of those things, your public profile (vCard).") + '

' + '

' + Common.printf(Common._e("It is strongly recommended to upload a profile image (%s maximum), like a picture of yourself, because that makes you easily recognizable by your friends."), JAPPIX_MAX_UPLOAD) + '

' + '

' + Common._e("Enable my public profile") + ' »

' + '
' + '
' + '
' + '
' + '' + Common._e("Save") + '' + '' + Common._e("Cancel") + '' + '
'; // Create the popup Popup.create('vcard', html); // Associate the events self.instance(); // We get the VCard informations self.get(Common.getXID(), 'user'); } catch(e) { Console.error('vCard.open', e); } finally { return false; } }; /** * Closes the vCard popup * @public * @return {boolean} */ self.close = function() { try { // Destroy the popup Popup.destroy('vcard'); // Create the welcome end popup? if(Welcome.is_done) { // Open popup Me.open(); // Unavoidable popup $('#me').addClass('unavoidable'); } } catch(e) { Console.error('vCard.close', e); } finally { return false; } }; /** * Switches the vCard popup tabs * @public * @param {string} id * @return {boolean} */ self.switchTab = function(id) { try { $('#vcard .one-lap').removeClass('lap-active'); $('#vcard #lap' + id).addClass('lap-active'); $('#vcard .tab a').removeClass('tab-active'); $('#vcard .tab a[data-key="' + id + '"]').addClass('tab-active'); } catch(e) { Console.error('vCard.switchTab', e); } finally { return false; } }; /** * Waits for the avatar upload reply * @public * @return {undefined} */ self.waitAvatarUpload = function() { try { // Reset the avatar info $('#vcard .avatar-info').hide().stopTime(); // Show the wait info $('#vcard .avatar-wait').show(); } catch(e) { Console.error('vCard.waitAvatarUpload', e); } }; /** * Handles the avatar upload reply * @public * @param {object} responseXML * @return {undefined} */ self.handleAvatarUpload = function(responseXML) { try { // Data selector var dData = $(responseXML).find('jappix'); // Not current upload session? if(parseInt(dData.attr('id')) != parseInt($('#vcard-avatar input[name="id"]').val())) { return; } // Reset the avatar info $('#vcard .avatar-info').hide().stopTime(); // Process the returned data if(!dData.find('error').size()) { // Read the values var aType = dData.find('type').text(); var aBinval = dData.find('binval').text(); // We remove everything that isn't useful right here $('#vcard .no-avatar').hide(); $('#vcard .avatar').remove(); // We display the delete button $('#vcard .avatar-delete').show(); // We tell the user it's okay $('#vcard .avatar-ok').show(); // Timer $('#vcard .avatar-info').oneTime('10s', function() { $(this).hide(); }); // We put the base64 values in a hidden input to be sent $('#USER-PHOTO-TYPE').val(aType); $('#USER-PHOTO-BINVAL').val(aBinval); // We display the avatar! $('#vcard .avatar-container').replaceWith('
'); } // Any error? else { $('#vcard .avatar-error').show(); // Timer $('#vcard .avatar-info').oneTime('10s', function() { $(this).hide(); }); Console.error('Error while uploading the avatar', dData.find('error').text()); } } catch(e) { Console.error('vCard.handleAvatarUpload', e); } }; /** * Deletes the encoded avatar of an user * @public * @return {boolean} */ self.deleteAvatar = function() { try { // We remove the avatar displayed elements $('#vcard .avatar-info').stopTime(); $('#vcard .avatar-info, #vcard .avatar-wait, #vcard .avatar-error, #vcard .avatar-ok, #vcard .avatar-delete').hide(); $('#vcard .avatar').remove(); // We reset the input value $('#USER-PHOTO-TYPE, #USER-PHOTO-BINVAL').val(''); // We show the avatar-uploading request $('#vcard .no-avatar').show(); } catch(e) { Console.error('vCard.deleteAvatar', e); } finally { return false; } }; /** * Creates a special vCard input * @public * @param {string} id * @param {string} type * @return {undefined} */ self.createInput = function(id, type) { try { // Generate the new ID id = 'USER-' + id; // Can append the content if((type == 'user') && !Common.exists('#vcard #' + id)) { $('#vcard .content').append(''); } } catch(e) { Console.error('vCard.createInput', e); } }; /** * Gets the vCard of a XID * @public * @param {string} to * @param {string} type * @return {undefined} */ self.get = function(to, type) { try { // Generate a special ID var id = genID(); // New IQ var iq = new JSJaCIQ(); iq.setID(id); iq.setType('get'); iq.appendNode('vCard', {'xmlns': NS_VCARD}); // Send the IQ to the good user if(type == 'user') { // Show the wait icon $('#vcard .wait').show(); // Apply the session ID $('#vcard').attr('data-vcard', id); // Send the IQ con.send(iq, self.handleUser); } else { // Show the wait icon $('#userinfos .wait').show(); // Apply the session ID $('#userinfos').attr('data-vcard', id); // Send the IQ iq.setTo(to); con.send(iq, self.handleBuddy); } } catch(e) { Console.error('vCard.get', e); } }; /** * Handles the current connected user's vCard * @public * @param {object} iq * @return {undefined} */ self.handleUser = function(iq) { try { self.handle(iq, 'user'); } catch(e) { Console.error('vCard.handleUser', e); } }; /** * Handles an external buddy vCard * @public * @param {object} iq * @return {undefined} */ self.handleBuddy = function(iq) { try { self.handle(iq, 'buddy'); } catch(e) { Console.error('vCard.handleBuddy', e); } }; /** * Handles a vCard stanza * @public * @param {object} iq * @param {string} type * @return {undefined} */ self.handle = function(iq, type) { try { // Extract the data var iqID = iq.getID(); var iqFrom = Common.fullXID(Common.getStanzaFrom(iq)); var iqNode = iq.getNode(); // Define some paths var path_vcard = '#vcard[data-vcard="' + iqID + '"]'; var path_userInfos = '#userinfos[data-vcard="' + iqID + '"]'; // End if the session does not exist if(((type == 'user') && !Common.exists(path_vcard)) || ((type == 'buddy') && !Common.exists(path_userInfos))) { return; } // We retrieve main values var values_yet = []; $(iqNode).find('vCard').children().each(function() { var this_sel = $(this); // Read the current parent node name var tokenname = (this).nodeName.toUpperCase(); // Node with a parent if(this_sel.children().size()) { this_sel.children().each(function() { // Get the node values var currentID = tokenname + '-' + (this).nodeName.toUpperCase(); var currentText = $(this).text(); // Not yet added? if(!Utils.existArrayValue(values_yet, currentID)) { // Create an input if it does not exist self.createInput(values_yet, type); // Userinfos viewer popup if((type == 'buddy') && currentText) { if(currentID == 'EMAIL-USERID') { $(path_userInfos + ' #BUDDY-' + currentID).html('' + currentText.htmlEnc() + ''); } else { $(path_userInfos + ' #BUDDY-' + currentID).text(currentText.htmlEnc()); } } // Profile editor popup else if(type == 'user') { $(path_vcard + ' #USER-' + currentID).val(currentText); } // Avoid duplicating the value values_yet.push(currentID); } }); } // Node without any parent else { // Get the node values var currentText = $(this).text(); // Not yet added? if(!Utils.existArrayValue(values_yet, tokenname)) { // Create an input if it does not exist self.createInput(tokenname, type); // Userinfos viewer popup if((type == 'buddy') && currentText) { // URL modification if(tokenname == 'URL') { // No http:// or https:// prefix, we should add it if(!currentText.match(/^https?:\/\/(.+)/)) { currentText = 'http://' + currentText; } currentText = '' + currentText.htmlEnc() + ''; } // Description modification else if(tokenname == 'DESC') { currentText = Filter.message(currentText, Name.getBuddy(iqFrom).htmlEnc(), true); } // Other stuffs else { currentText = currentText.htmlEnc(); } $(path_userInfos + ' #BUDDY-' + tokenname).html(currentText); } // Profile editor popup else if(type == 'user') { $(path_vcard + ' #USER-' + tokenname).val(currentText); } // Avoid duplicating the value values_yet.push(tokenname); } } }); // Update the stored avatar if(type == 'buddy') { // Get the avatar XML var xml = DataStore.getPersistent('global', 'avatar', iqFrom); // If there were no stored avatar previously if($(Common.XMLFromString(xml)).find('type').text() == 'none') { xml = xml.replace(/false<\/forced>/gi, 'true'); DataStore.setPersistent(Common.getXID(), 'avatar', iqFrom, xml); } // Handle the user avatar Avatar.handle(iq); } // The avatar values targets var aBinval, aType, aContainer; if(type == 'user') { aBinval = $('#USER-PHOTO-BINVAL').val(); aType = $('#USER-PHOTO-TYPE').val(); aContainer = path_vcard + ' .avatar-container'; } else { aBinval = $(iqNode).find('BINVAL:first').text(); aType = $(iqNode).find('TYPE:first').text(); aContainer = path_userInfos + ' .avatar-container'; } // We display the avatar if retrieved if(aBinval) { // No type? if(!aType) { aType = 'image/png'; } if(type == 'user') { // We move all the things that we don't need in that case $(path_vcard + ' .no-avatar').hide(); $(path_vcard + ' .avatar-delete').show(); $(path_vcard + ' .avatar').remove(); } var avatar_src = ('data:' + aType + ';base64,' + aBinval); // We display the avatar we have just received $(aContainer).replaceWith('
'); } else if(type == 'buddy') { $(aContainer).replaceWith('
'); } // Do someting depending of the type if(type == 'user') { $(path_vcard + ' .wait').hide(); $(path_vcard + ' .finish:first').removeClass('disabled'); } else { UserInfos.vCard(); } Console.log('vCard received: ' + iqFrom); } catch(e) { Console.error('vCard.handle', e); } }; /** * Sends the vCard of the user * @public * @return {boolean} */ self.send = function() { try { // Send both vcard-temp + vCard4 self._sendLegacy(); self._sendForward(); // Send the user nickname & avatar over PEP if(Features.enabledPEP()) { self._sendPubsub(); } // Close the vCard stuffs self.close(); // Get our new avatar Avatar.get(Common.getXID(), 'force', 'true', 'forget'); Console.log('vCard sent.'); } catch(e) { Console.error('vCard.send', e); } finally { return false; } }; /** * Generate XML tree * @private * @return {boolean} */ self._generateTree = function(namespace, stanza, node) { try { var selector = $('#vcard .vcard-item'); var get_id_fn = function(this_sel) { var id = this_sel.attr('id'); return id ? id.replace(/^USER-(.+)/, '$1') : null; }; if(namespace === NS_IETF_VCARD4) { selector = selector.filter('[data-vcard4]'); get_id_fn = function(this_sel) { return this_sel.attr('data-vcard4') || null; }; } // We send the identity part of the form selector.each(function() { var this_sel = $(this); var item_id = get_id_fn(this_sel); var item_value = this_sel.val(); if(item_value && item_id) { if(item_id.indexOf('-') !== -1) { var tagname = Common.explodeThis('-', item_id, 0); var cur_node; if(node.getElementsByTagName(tagname).length > 0) { cur_node = node.getElementsByTagName(tagname).item(0); } else { cur_node = node.appendChild(stanza.buildNode(tagname, {'xmlns': namespace})); } cur_node.appendChild( stanza.buildNode( Common.explodeThis('-', item_id, 1), {'xmlns': namespace}, item_value ) ); } else { node.appendChild(stanza.buildNode(item_id, {'xmlns': namespace}, item_value)); } } }); return true; } catch(e) { Console.error('vCard._generateTree', e); return false; } }; /** * Configure given Pubsub node * @private * @param {string} namespace * @return {undefined} */ self._configureNode = function(namespace) { try { Pubsub.setup(null, namespace, '1', '1', 'open', 'whitelist'); } catch(e) { Console.error('vCard._configureNode', e); } }; /** * Send legacy vCard * @private * @return {undefined} */ self._sendLegacy = function() { try { var iq = new JSJaCIQ(); iq.setType('set'); var vCard = iq.appendNode('vCard', { 'xmlns': NS_VCARD }); self._generateTree(NS_VCARD, iq, vCard); con.send(iq); } catch(e) { Console.error('vCard._sendLegacy', e); } }; /** * Send forward version vCard (vCard 4) * @private * @return {undefined} */ self._sendForward = function() { try { var iq = new JSJaCIQ(); iq.setType('set'); // Build Pubsub headers var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB}); var publish = pubsub.appendChild(iq.buildNode('publish', { 'node': NS_XMPP_VCARD4, 'xmlns': NS_PUBSUB })); var item = publish.appendChild(iq.buildNode('item', { 'xmlns': NS_PUBSUB, 'id': 'current' })); // Generate vCard4 tree var vcard = item.appendChild(iq.buildNode('vcard', { 'xmlns': NS_IETF_VCARD4 })); self._generateTree(NS_IETF_VCARD4, iq, vcard); con.send(iq); // Make it publicly-viewable self._configureNode(NS_XMPP_VCARD4); } catch(e) { Console.error('vCard._sendForward', e); } }; /** * Send other Pubsub items * @private * @return {undefined} */ self._sendPubsub = function() { try { // Generate some values var photo_bin = $('#USER-PHOTO-BINVAL').val(); var photo_data = Base64.decode(photo_bin) || ''; // Data to be sent var send_data = {}; send_data[NS_NICK] = $('#USER-NICKNAME').val(); send_data[NS_URN_ADATA] = photo_bin; send_data[NS_URN_AMETA] = { 'type': $('#USER-PHOTO-TYPE').val(), 'id': (hex_sha1(photo_data) || ''), 'bytes': (photo_data.length || '') }; // Generate the XML $.each(send_data, function(namespace, data) { var iq = new JSJaCIQ(); iq.setType('set'); var pubsub = iq.appendNode('pubsub', {'xmlns': NS_PUBSUB}); var publish = pubsub.appendChild(iq.buildNode('publish', {'node': namespace, 'xmlns': NS_PUBSUB})); if(data) { var item; if(namespace === NS_NICK) { item = publish.appendChild(iq.buildNode('item', {'xmlns': NS_PUBSUB})); // Nickname element item.appendChild(iq.buildNode('nick', {'xmlns': NS_NICK}, data)); } else if(namespace === NS_URN_ADATA || namespace === NS_URN_AMETA) { item = publish.appendChild(iq.buildNode('item', {'xmlns': NS_PUBSUB})); // Apply the SHA-1 hash if(send_data[NS_URN_AMETA].id) { item.setAttribute('id', send_data[NS_URN_AMETA].id); } // Append XML nodes depending on namespace if(namespace === NS_URN_ADATA) { item.appendChild(iq.buildNode('data', {'xmlns': NS_URN_ADATA}, data)); } else if(namespace === NS_URN_AMETA) { var metadata = item.appendChild(iq.buildNode('metadata', {'xmlns': NS_URN_AMETA})); if(data) { var meta_info = metadata.appendChild(iq.buildNode('info', {'xmlns': NS_URN_AMETA})); if(data.type) meta_info.setAttribute('type', data.type); if(data.id) meta_info.setAttribute('id', data.id); if(data.bytes) meta_info.setAttribute('bytes', data.bytes); } } } } con.send(iq); // Make node publicly-viewable self._configureNode(namespace); }); } catch(e) { Console.error('vCard._sendPubsub', e); } }; /** * Plugin launcher * @public * @return {undefined} */ self.instance = function() { try { // Focus on the first input $(document).oneTime(10, function() { $('#vcard input:first').focus(); }); // Keyboard events $('#vcard input[type="text"]').keyup(function(e) { // Enter pressed: send the vCard if((e.keyCode == 13) && !$('#vcard .finish.save').hasClass('disabled')) { return self.send(); } }); // Click events $('#vcard .tab a').click(function() { var this_sel = $(this); // Yet active? if(this_sel.hasClass('tab-active')) { return false; } // Switch to the good tab var key = parseInt(this_sel.attr('data-key')); return self.switchTab(key); }); $('#vcard .avatar-delete').click(function() { return self.deleteAvatar(); }); $('#vcard .bottom .finish').click(function() { var this_sel = $(this); if(this_sel.is('.cancel')) { return self.close(); } if(this_sel.is('.save') && !this_sel.hasClass('disabled')) { return self.send(); } return false; }); // Avatar upload vars var avatar_options = { dataType: 'xml', beforeSubmit: self.waitAvatarUpload, success: self.handleAvatarUpload }; // Avatar upload form submit event $('#vcard-avatar').submit(function() { if($('#vcard .wait').is(':hidden') && $('#vcard .avatar-info.avatar-wait').is(':hidden') && $('#vcard-avatar input[type="file"]').val()) { $(this).ajaxSubmit(avatar_options); } return false; }); // Avatar upload input change event $('#vcard-avatar input[type="file"]').change(function() { if($('#vcard .wait').is(':hidden') && $('#vcard .avatar-info.avatar-wait').is(':hidden') && $(this).val()) { $('#vcard-avatar').ajaxSubmit(avatar_options); } return false; }); // Placeholders $('#vcard-avatar input[type="text"]').placeholder(); } catch(e) { Console.error('vCard.instance', e); } }; /** * Return class scope */ return self; })();