|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638 |
- AjaxIM = function(options, actions) {
- if(this instanceof AjaxIM) {
- var self = this;
-
- // === {{{ defaults }}} ===
- //
- // These are the available settings for Ajax IM, and the associated
- // defaults:
- //
- // * {{{pollServer}}} is the default URL to which all actions refer. It is
- // possible to specify certain action URLs separately (as is used with the
- // NodeJS server).
- // * {{{theme}}} is the name of the theme folder that defines the HTML and
- // CSS of the IM bar and chat boxes. Usually, themes are deposited in the
- // provided "themes" folder and specified by that path, e.g. {{{themes/default}}}.
- // Theme files within the theme folder must be named {{{theme.html}}} and
- // {{{theme.css}}}.
- var defaults = {
- pollServer: '',
- theme: 'themes/default'
- };
-
- // === {{{AjaxIM.}}}**{{{settings}}}** ===
- //
- // These are the settings for the IM. If particular options are not specified,
- // the defaults (see above) will be used. //These options will be defined
- // upon calling the initialization function, and not set directly.//
- this.settings = $.extend(defaults, options);
-
- // === {{{AjaxIM.}}}**{{{actions}}}** ===
- //
- // Each individual action that the IM engine can execute is predefined here.
- // By default, it merely appends the action onto the end of the {{{pollServer}}} url,
- // however, it is possible to define actions individually. //The alternative actions
- // will be defined upon calling the initialization function, and not set directly.//
- //
- // Should you define an action at a different URL, Ajax IM will determine whether
- // or not this URL is within the current domain. If it is within a subdomain of
- // the current domain, it will set the document.domain variable for you,
- // to match a broader hostname scope; the action will continue to use {{{$.post}}}
- // (the default AJAX method for Ajax IM).
- //
- // On the other hand, should you choose a URL outside the current domain
- // Ajax IM will switch to {{{$.getJSON}}} (a get request) to avoid
- // cross-domain scripting issues. This means that a server on a different
- // port or at a different address will need to be able to handle GET
- // requests rather than POST requests (such as how the Node.JS Ajax IM
- // server works).
- this.actions = $.extend({
- noop: this.settings.pollServer + '/app/noop',
- listen: this.settings.pollServer + '/app/listen',
- send: this.settings.pollServer + '/app/message',
- status: this.settings.pollServer + '/app/status',
- signoff: this.settings.pollServer + '/app/signoff'
- }, actions);
-
- // If Socket.IO is available, create a socket
- self.socket = null;
- $.getScript(this.settings.pollServer+'/socket.io/socket.io.js', function(){
- self.socket = io(self.settings.pollServer);
- self.socket.on('client', function(event) {
- event = $.extend(true, {}, event);
- self.dispatchEvent(event);
- });
- var event = {type: 'hello', from: this.username, sessionID: cookies.get('sessionid')};
- self.sendEvent(event);
- });
-
- // We load the theme dynamically based on the passed
- // settings. If the theme is set to false, we assume
- // that the user is going to load it himself.
- this.themeLoaded = false;
- if(this.settings.theme) {
- if(typeof document.createStyleSheet == 'function')
- document.createStyleSheet(this.settings.theme + '/theme.css');
- else
- $('body').append('<link rel="stylesheet" href="' +
- this.settings.theme + '/theme.css" />');
- $('<div>').appendTo('body').load(this.settings.theme + '/theme.html #imjs-bar, .imjs-tooltip',
- function() {
- self.themeLoaded = true;
- self.setup();
- }
- );
- } else {
- this.themeLoaded = true;
- this.setup();
- }
-
- // Allow a chatbox to be minimized
- $(document).on('click', '.imjs-chatbox', function(e) {
- e.preventDefault();
- return false;
- });
-
- $(document).on('click', '.imjs-chatbox .imjs-minimize', function() {
- $(this).parents('.imjs-selected').click();
- });
-
- // Setup message sending for all chatboxes
- $(document).on('keydown', '.imjs-chatbox .imjs-input', function(event) {
- var obj = $(this);
- // if(event.keyCode == 13 && !($.browser.msie && $.browser.version < 8)) {
- if(event.keyCode == 13) {
- self.send(obj.parents('.imjs-chatbox').data('username'), obj.val());
- }
- }).on('keyup', '.imjs-chatbox .imjs-input', function(event) {
- if(event.keyCode == 13) {
- // if($.browser.msie && $.browser.version < 8) {
- if(false) {
- var obj = $(this);
- self.send(obj.parents('.imjs-chatbox').data('username'), obj.val());
- }
-
- var obj = $(this);
- obj.val('');
- obj.height(obj.data('height'));
- }
- }).on('keypress', '.imjs-chatbox .imjs-input', function(e) {
- var obj = $(this);
- obj.height(0);
- if(true) obj.height(0);
- if(this.scrollHeight > obj.height() || this.scrollHeight < obj.height()) {
- obj.height(this.scrollHeight);
- }
- });
-
- $(document).on('click', '.imjs-msglog', function() {
- var chatbox = $(this).parents('.imjs-chatbox');
- chatbox.find('.imjs-input').focus();
- });
-
- // Create a chatbox when a buddylist item is clicked
- $(document).on('click', '.imjs-friend', function() {
- var chatbox = self._createChatbox($(this).data('friend'));
-
- if(chatbox.parents('.imjs-tab').data('state') != 'active') {
- chatbox.parents('.imjs-tab').click();
- store.set(self.username + '-activeTab', $(this).data('friend'));
- }
-
- chatbox.find('.imjs-input').focus();
- if(!(input = chatbox.find('.imjs-input')).data('height')) {
- // store the height for resizing later
- if (!input.height()) {
- input.height(16);
- }
- input.data('height', input.height());
- }
- });
-
- // Setup and hide the scrollers
- $('.imjs-scroll').css('display', 'none');
- $(document).on('click', '#imjs-scroll-right', function() {
- var hiddenTab = $(this)
- .prevAll('#imjs-bar li.imjs-tab:hidden')
- .filter(function() {
- return (
- $(this).data('state') != 'closed' &&
- $(this).prev('#imjs-bar li.imjs-tab:visible').length
- );
- })
- .not('.imjs-default')
- .slice(-1)
- .css('display', '');
-
- if(hiddenTab.length) {
- $('#imjs-bar li.imjs-tab:visible').eq(0).css('display', 'none');
- $(this).html(parseInt($(this).html()) - 1);
- $('#imjs-scroll-left').html(parseInt($('#imjs-scroll-left').html()) + 1);
- }
-
- return false;
- });
- $(document).on('click', '#imjs-scroll-left', function() {
- var hiddenTab = $(this)
- .nextAll('#imjs-bar li.imjs-tab:hidden')
- .filter(function() {
- return (
- $(this).data('state') != 'closed' &&
- $(this).next('#imjs-bar li.imjs-tab:visible').length
- );
- })
- .not('.imjs-default')
- .slice(-1)
- .css('display', '');
-
- if(hiddenTab.length) {
- $('#imjs-bar li.imjs-tab:visible').slice(-1).css('display', 'none');
- $(this).html(parseInt($(this).html()) - 1);
- $('#imjs-scroll-right').html(parseInt($('#imjs-scroll-right').html()) + 1);
- }
-
- return false;
- });
-
- // Setup status buttons
- $(document).on('click', '#imjs-status-panel .imjs-button', function() {
- var status = this.id.split('-')[2];
-
- $('#imjs-away-message-text, #imjs-away-message-text-arrow').animate({
- opacity: (status == 'away' ? 'show' : 'hide'),
- height: (status == 'away' ? 'show' : 'hide')
- }, 50);
-
- $('#imjs-status-panel .imjs-button').removeClass('imjs-toggled');
- $(this).addClass('imjs-toggled');
-
- if(self.current_status[0] == 'away')
- self._last_status_message = $('#imjs-away-message-text').val();
-
- $('#imjs-away-message-text').val(status == 'away'
- ? self._last_status_message ||
- AjaxIM.l10n.defaultAway
- : '');
-
- self.status(status, $('#imjs-away-message-text').val());
- return false;
- });
-
- // Allow status message to be changed
- $(document)
- .on('keyup', '#imjs-away-message-text', (function() {
- var msg_type_timer = null;
-
- return function() {
- if(msg_type_timer) clearTimeout(msg_type_timer);
-
- msg_type_timer = setTimeout(function() {
- self._last_status_message =
- self.current_status[1] = $('#imjs-away-message-text')
- .addClass('imjs-loading').val();
- self.status.apply(self, self.current_status);
- }, 250);
- };
- })());
- $(this).bind('changeStatusSuccessful changeStatusFailed', function() {
- $('#imjs-away-message-text').removeClass('imjs-loading');
- });
-
- // Setup reconnect button
- $(document).on('click', '#imjs-reconnect', function() {
- self.offline = false;
- store.remove(self.username + '-offline');
- $('#imjs-reconnect').hide();
- $('.imjs-input').attr('disabled', false);
-
- // Restore status to available
- $('#imjs-status-panel .imjs-button').removeClass('imjs-toggled');
- $('#imjs-button-available').addClass('imjs-toggled');
- $(self.statuses).each(function() {
- $('#imjs-friends').removeClass('imjs-' + this);
- });
- $('#imjs-friends').addClass('imjs-available');
- $('#imjs-away-message-text, #imjs-away-message-text-arrow')
- .css('display', 'none');
-
- // Set status
- self.current_status = ['available', ''];
- store.set(self.username + '-status', ['available', '']);
- self.status('available', '');
-
- // Reconnect
- self.storage();
- self.listen();
- });
-
- // Initialize the chatbox hash
- this.chats = {};
-
- // On window resize, check scroller visibility
- $(window).resize(function() {
- try {
- self._scrollers();
- } catch(e) {}
- });
-
- // Set up event handling
- this.onEvent('hello', this.onHello);
- this.onEvent('message', this.onMessage);
- this.onEvent('status', this.onStatus);
- this.onEvent('notice', this.onNotice);
- this.onEvent('goodbye', this.onGoodbye);
- } else {
- return AjaxIM.init(options);
- }
- };
-
- $.extend(AjaxIM.prototype, {
- // == Main ==
- setup: function() {
- var self = this;
- $(this).trigger('loadComplete');
-
- $('.imjs-scroll').css('display', 'none');
- this.initTabBar();
- this._scrollers();
-
- this.username = store.get('user');
- this._lastReconnect = 0;
-
- if(this.username && store.get(this.username + '-offline') == true) {
- this.offline = true;
-
- setTimeout(function() { self._showReconnect(); }, 0);
- return;
- }
-
- if(this.username)
- this.storage();
-
- setTimeout(function() { if (!self.socket) self.listen(); }, 2000);
- },
-
- // === {{{AjaxIM.}}}**{{{storage()}}}** ===
- //
- // Retrieves chat session data from whatever storage engine is enabled
- // (provided that one is enabled at all). If a page reloads, this function
- // is called to restore the user's chat state (existing conversations, active tab).
- // This function is called //automatically//, upon initialization of the IM engine.
- storage: function() {
- var self = this,
- chatstore = store.get(this.username + '-chats'),
- friends = store.get(this.username + '-friends'),
- status = store.get(this.username + '-status') || ['available', ''];
-
- this.chatstore = chatstore || {};
- this.friends = {};
- this.current_status = status;
-
- if(friends) {
- $.each(friends, function(friend, data) {
- self.addFriend(friend, data.status, data.group);
- });
-
- $('#imjs-friends').removeClass('imjs-not-connected')
- .addClass('imjs-' + status[0]);
-
- $('#imjs-button-' + status[0]).addClass('imjs-toggled');
- if(status[0] == 'away') {
- setTimeout(function() {
- $('#imjs-away-message-text, #imjs-away-message-text-arrow').show();
- }, 250);
- $('#imjs-away-message-text').val(this.current_status[1]);
- }
- }
-
- $.each(this.chatstore, function(username, convo) {
- if(!convo.length) return;
-
- var chatbox = self._createChatbox(username, true),
- msglog = chatbox.find('.imjs-msglog').empty();
- chatbox.data('lastDateStamp', null).css('display', 'none');
-
- if(typeof convo == 'string')
- convo = self.chatstore[username] = JSON.parse(convo);
-
- // Restore all messages, date stamps, and errors
- msglog.html(convo.join(''));
-
- $(self).trigger('chatRestored', [username, chatbox]);
- });
-
- var activeTab = store.get(this.username + '-activeTab');
- if(activeTab && activeTab in this.chats) {
- this.chats[activeTab].parents('.imjs-tab').click();
- var msglog = this.chats[activeTab].find('.imjs-msglog');
- msglog[0].scrollTop = msglog[0].scrollHeight;
- }
-
- // Set username in Friends list
- var header = $('#imjs-friends-panel .imjs-header');
- header.html(header.html().replace('{username}', this.username));
- },
-
- // === //private// {{{AjaxIM.}}}**{{{_clearSession()}}}** ===
- //
- // Clears all session data from the last known user.
- _clearSession: function() {
- var last_user = store.get('user');
- $.each(['friends', 'activeTab', 'chats', 'status', 'connected'],
- function(i, key) {
- store.remove(last_user + '-' + key);
- });
- store.set('user', '');
-
- this.chats = {};
- this.friends = {};
- this.chatstore = {};
- this.current_status = ['available', ''];
-
- $('.imjs-tab').not('.imjs-tab.imjs-default').remove();
- $('.imjs-friend-group').not('.imjs-friend-group.imjs-default').remove();
-
- delete this.username;
- },
-
- // === {{{AjaxIM.}}}**{{{listen()}}}** ===
- //
- // Queries the server for new messages.
- listen: function() {
- if(this.offline) return;
-
- var self = this;
- AjaxIM.get(
- this.actions.listen,
- {},
- function(response) {
- if($.isArray(response)) {
- $.each(response, function(key, value) {
- self._parseMessage(value);
- });
- }
- else if($.isPlainObject(response)) {
- self._parseMessage(response);
- }
-
- if (!self.socket) {
- setTimeout(function() { self.listen(); }, 0);
- }
- },
- function(error) {
- self._notConnected();
- $(self).trigger('pollFailed', ['not connected']);
-
- // Try reconnecting in n*2 seconds (max 16)
- self._reconnectIn = (self._lastReconnect < (new Date()) - 60000)
- ? 1000
- : Math.min(self._reconnectIn * 2, 16000);
- self._lastReconnect = new Date();
- if (!self.socket) {
- setTimeout(function() { self.listen(); }, self._reconnectIn);
- }
- },
- this.actions.noop
- );
- },
-
- // === //private// {{{AjaxIM.}}}**{{{_parseMessages(messages)}}}** ===
- //
- _parseMessage: function(message) {
- this.triggerEvent(message);
- },
-
- onHello: function(message) {
- var self = this;
- this._clearSession();
-
- this.username = message.username;
- this.current_status = ['available', ''];
- store.set('user', message.username);
- store.set(this.username + '-status', this.current_status);
-
- $('#imjs-friends').attr('class', 'imjs-available');
- $.each(message.friends, function() {
- var friend;
- if(this.length == 2)
- friend = this;
- else
- friend = [this.toString(), ['offline', '']];
- self.addFriend(friend[0], friend[1], 'Friends');
- });
- store.set(this.username + '-friends', this.friends);
-
- // Set username in Friends list
- var header = $('#imjs-friends-panel .imjs-header');
- header.html(header.html().replace('{username}', this.username));
-
- // Set status available
- $('#imjs-away-message-text, #imjs-away-message-text-arrow').hide();
- $('#imjs-status-panel .imjs-button').removeClass('imjs-toggled');
- $('#imjs-button-available').addClass('imjs-toggled');
- },
-
- onMessage: function(event) {
- this.incoming(event.from, event.body);
- },
-
- onStatus: function(event) {
- this._friendUpdate(event.from, event.status, event.message);
- this._storeFriends();
- },
-
- onNotice: function(event) {
- },
-
- onGoodbye: function(event) {
- this._notConnected();
- },
-
- // === {{{AjaxIM.}}}**{{{incoming(from, message)}}}** ===
- //
- // Handles a new message from another user. If a chatbox for that
- // user does not yet exist, one is created. If it does exist, but
- // is minimized, the user is notified but the chatbox is not brought
- // to the front. This function also stores the message, if a storage
- // method is set.
- //
- // ==== Parameters ====
- // * {{{from}}} is the username of the sender.
- // * {{{message}}} is the body.
- incoming: function(from, message) {
- // check if IM exists, otherwise create new window
- // TODO: If friend is not on the buddylist,
- // should add them to a temp list?
- var chatbox = this._createChatbox(from),
- tab = chatbox.parents('.imjs-tab');
-
- if(!$('#imjs-bar .imjs-selected').length) {
- tab.click();
- } else if(tab.data('state') != 'active') {
- this.notification(tab);
- }
-
- this._store(from, this._addMessage('b', chatbox, from, message));
- },
-
- // === {{{AjaxIM.}}}**{{{addFriend(username, group)}}}** ===
- //
- // Inserts a new friend into the friends list. If the group specified
- // doesn't exist, it is created. If the friend is already in this group,
- // they aren't added again, however, the friend item is returned.
- //
- // ==== Parameters ====
- // * {{{username}}} is the username of the new friend.
- // * {{{status}}} is the current status of the friend.
- // * {{{group}}} is the user group to which the friend should be added.
- addFriend: function(username, status, group) {
- var group_id = 'imjs-group-' + md5.hex(group);
-
- if(!(group_item = $('#' + group_id)).length) {
- var group_item = $('.imjs-friend-group.imjs-default').clone()
- .removeClass('imjs-default')
- .attr('id', group_id)
- .data('group', group)
- .appendTo('#imjs-friends-list');
-
- var group_header = group_item.find('.imjs-friend-group-header');
- group_header.html(group_header.html().replace('{group}', group));
- }
-
- var user_id = 'imjs-friend-' + md5.hex(username + group);
-
- if(!$('#' + user_id).length) {
- var user_item = group_item.find('ul li.imjs-default').clone()
- .removeClass('imjs-default')
- .addClass('imjs-' + status[0])
- .attr('id', user_id)
- .data('friend', username)
- .appendTo(group_item.find('ul'));
- // if(status[0] == 'offline')
- // user_item.hide();
- user_item.html(
- user_item.html()
- .replace('{username}', username)
- .replace('{status}', status[1])
- );
- user_item.find('.imjs-friend-status')
- .attr('title', status[1]);
- }
-
- this.friends[username] = {status: status, group: group};
- this._updateFriendCount();
- return this.friends[username];
- },
-
- // === //private// {{{AjaxIM.}}}**{{{_updateFriendCount()}}}** ===
- //
- // Counts the number of online friends and updates the friends count
- // in the friend tab.
- _updateFriendCount: function() {
- var friendsLength = 0;
- $.each(this.friends, function(u, f) {
- if(f.status[0] != 'offline') friendsLength++;
- });
- $('#imjs-friends .imjs-tab-text span span').html(friendsLength);
- },
-
- // === //private// {{{AjaxIM.}}}**{{{_storeFriends()}}}** ===
- //
- // If a storage method is enabled, the current state of the
- // user's friends list is stored.
- _storeFriends: function() {
- store.set(this.username + '-friends', this.friends);
- },
-
- // === //private// {{{AjaxIM.}}}**{{{_createChatbox(username)}}}** ===
- //
- // Builds a chatbox based on the default chatbox HTML and CSS defined
- // in the current theme. Should a chatbox for this user already exist,
- // a new one is not created. Instead, it is either given focus (should
- // no other windows already have focus), or a notification is issued.
- //
- // As well, if the chatbox does not exist, an associated tab will be
- // created.
- //
- // ==== Parameters ====
- // * {{{username}}} is the name of the user for whom the chatbox is intended
- // for.
- // * {{{no_stamp}}} sets whther or not to add a date stamp to the chatbox
- // upon creation.
- //
- // //Note:// New chatboxes are given an automatically generated ID in the
- // format of {{{#imjs-[md5 of username]}}}.
- _createChatbox: function(username, no_stamp) {
- var self = this,
- chatbox_id = 'imjs-' + md5.hex(username);
- if(!(chatbox = $('#' + chatbox_id)).length) {
- // add a tab
- var tab = this.addTab(username, '#' + chatbox_id);
- var chatbox = tab.find('.imjs-chatbox');
-
- chatbox.attr('id', chatbox_id);
-
- // remove default items from the message log
- var message_log = chatbox.find('.imjs-msglog').empty();
-
- // setup the chatbox header
- var cb_header = chatbox.find('.imjs-header');
- cb_header.html(cb_header.html().replace('{username}', username));
-
- if(!no_stamp) {
- // add a date stamp
- this._store(username, this._addDateStamp(chatbox));
- }
-
- // associate the username with the object and vice-versa
- this.chats[username] = chatbox;
- chatbox.data('username', username);
-
- if(username in this.friends) {
- status = this.friends[username].status;
- tab.addClass('imjs-' + status);
- }
-
- setTimeout(function() { self._scrollers(); }, 0);
- } else if(chatbox.parents('.imjs-tab').data('state') == 'closed') {
- chatbox.find('.imjs-msglog > *').addClass('imjs-msg-old');
-
- var tab = chatbox.parents('.imjs-tab');
- if(tab.css('display') == 'none')
- tab.css('display', '').removeClass('imjs-selected')
- .insertAfter('#imjs-scroll-left')
- .data('state', 'minimized');
-
- if(!no_stamp) {
- // possibly add a date stamp
- this._store(username, this._addDateStamp(chatbox));
- }
-
- if(!$('#imjs-bar .imjs-selected').length) {
- tab.click();
- } else {
- this.notification(tab);
- }
-
- setTimeout(function() { self._scrollers() }, 0);
- }
-
- return chatbox;
- },
-
- // === //private// {{{AjaxIM.}}}**{{{_addDateStamp(chatbox)}}}** //
- //
- // Adds a date/time notifier to a chatbox. These are generally
- // inserted upon creation of a chatbox, or upon the date changing
- // since the last time a date stamp was added. If a date stamp for
- // the current date already exists, a new one will not be added.
- //
- // ==== Parameters ====
- // * {{{chatbox}}} refers to the jQuery-selected chatbox DOM element.
- // * {{{time}}} is the date/time the date stamp will show. It is specified
- // in milliseconds since the Unix Epoch. This is //only// defined when
- // date stamps are being restored from storage; if not specified, the
- // current computer time will be used.
- _addDateStamp: function(chatbox, time) {
- var message_log = $(chatbox).find('.imjs-msglog');
- if(!time)
- time = (new Date()).getTime();
-
- var date_stamp = $('.imjs-tab.imjs-default .imjs-chatbox .imjs-msglog .imjs-date').clone();
- var date_stamp_time = date_stamp.find('.imjs-msg-time');
- if(date_stamp_time.length)
- date_stamp_time.html(dateFormat(time, date_stamp_time.html()));
-
- var date_stamp_date = date_stamp.find('.imjs-date-date');
- var formatted_date = dateFormat(time, date_stamp_date.html());
- if(chatbox.data('lastDateStamp') != formatted_date) {
- if(date_stamp_date.length)
- date_stamp_date.html(dateFormat(time, date_stamp_date.html()));
-
- chatbox.data('lastDateStamp', formatted_date);
- date_stamp.appendTo(message_log);
-
- return {
- replace_last: false,
- html: jQuery('<div>').append(date_stamp.clone()).html()
- };
- } else {
- //$('<div></div>').appendTo(message_log);
- return {replace_last: false, html: ''};
- }
- },
-
- // === //private// {{{AjaxIM.}}}**{{{_addError(chatbox, error)}}}** //
- //
- // Adds an error to a chatbox. These are generally inserted after
- // a user sends a message unsuccessfully. If an error message
- // was already added, another one will be added anyway.
- //
- // ==== Parameters ====
- // * {{{chatbox}}} refers to the jQuery-selected chatbox DOM element.
- // * {{{error}}} is the error message string.
- // * {{{time}}} is the date/time the error occurred. It is specified in
- // milliseconds since the Unix Epoch. This is //only// defined when
- // errors are being restored from storage; if not specified, the current
- // computer time will be used.
- _addError: function(chatbox, error, time) {
- var message_log = $(chatbox).find('.imjs-msglog');
-
- var error_item =
- $('.imjs-tab.imjs-default .imjs-chatbox .imjs-msglog .imjs-error').clone();
-
- var error_item_time = error_item.find('.imjs-msg-time');
- if(error_item_time.length) {
- if(!time)
- time = (new Date()).getTime();
- error_item_time.html(dateFormat(time, error_item_time.html()));
- }
-
- error_item.find('.imjs-error-error').html(error);
- error_item.appendTo(message_log);
-
- message_log[0].scrollTop = message_log[0].scrollHeight;
-
- return {
- replace_last: false,
- html: jQuery('<div>').append(error_item.clone()).html()
- };
- },
-
- // === //private// {{{AjaxIM.}}}**{{{_addMessage(ab, chatbox, username, message, time)}}}** //
- //
- // Adds a message to a chatbox. Depending on the {{{ab}}} value,
- // the color of the username may change as a way of visually
- // identifying users (however, this depends on the theme's CSS).
- // A timestamp is added to the message, and the chatbox is scrolled
- // to the bottom, such that the new message is visible.
- //
- // Messages will be automatically tag-escaped, so as to prevent
- // any potential cross-site scripting problems. Additionally,
- // URLs will be automatically linked.
- //
- // ==== Parameters ====
- // * {{{ab}}} refers to whether the user is "a" or "b" in a conversation.
- // For the general case, "you" are "a" and "they" are "b".
- // * {{{chatbox}}} refers to the jQuery-selected chatbox DOM element.
- // * {{{username}}} is the username of the user who sent the message.
- // * {{{time}}} is the time the message was sent in milliseconds since
- // the Unix Epoch. This is //only// defined when messages are being
- // restored from storage. For new messages, the current computer
- // time is automatically used.
- _addMessage: function(ab, chatbox, username, message, time) {
- var last_message = chatbox.find('.imjs-msglog > *:last-child');
- if(last_message.hasClass('imjs-msg-' + ab)) {
- // Last message was from the same person, so let's just add another imjs-msg-*-msg
- var message_container = (last_message.hasClass('imjs-msg-' + ab + '-container')
- ? last_message
- : last_message.find('.imjs-msg-' + ab + '-container'));
-
- var single_message =
- $('.imjs-tab.imjs-default .imjs-chatbox .imjs-msglog .imjs-msg-' + ab + '-msg')
- .clone().appendTo(message_container);
-
- single_message.html(single_message.html().replace('{username}', username));
- } else if(!last_message.length || !last_message.hasClass('imjs-msg-' + ab)) {
- var message_group = $('.imjs-tab.imjs-default .imjs-chatbox .imjs-msg-' + ab)
- .clone().appendTo(chatbox.find('.imjs-msglog'));
- message_group.html(message_group.html().replace('{username}', username));
-
- var single_message = message_group.find('.imjs-msg-' + ab + '-msg');
- }
-
- // clean up the message
- message = message.toString().replace(/</g, '<').replace(/>/g, '>')
- .replace(/(^|.*)\*([^*]+)\*(.*|$)/, '$1<strong>$2</strong>$3');
-
- // autolink URLs
- message = message.replace(
- new RegExp('([A-Za-z][A-Za-z0-9+.-]{1,120}:[A-Za-z0-9/]' +
- '(([A-Za-z0-9$_.+!*,;/?:@&~=-])|%[A-Fa-f0-9]{2}){1,333}' +
- '(#([a-zA-Z0-9][a-zA-Z0-9$_.+!*,;/?:@&~=%-]{0,1000}))?)', 'g'),
- '<a href="$1" target="_blank">$1</a>');
-
- // insert the message
- single_message.html(single_message.html().replace('{message}', message));
-
- // set the message time
- var msgtime = single_message.find('.imjs-msg-time');
- if(!time)
- time = new Date();
-
- if(typeof time != 'string')
- time = dateFormat(time, msgtime.html());
-
- msgtime.html(time);
-
- var msglog = chatbox.find('.imjs-msglog');
- msglog[0].scrollTop = msglog[0].scrollHeight;
-
- return {
- replace_last : !!message_container,
- html: jQuery('<div>').append(
- message_container
- ? last_message.clone()
- : message_group.clone()
- ).html()
- };
- },
-
- _store: function(username, msg) {
- if(!msg.html.length) return;
- if(!this.chatstore) this.chatstore = {};
-
- if(!(username in this.chatstore)) {
- this.chatstore[username] = [];
- } else if(this.chatstore[username].length > 300) {
- // If the chat store gets too long, it becomes slow to load.
- this.chatstore[username].shift();
- }
-
- if(msg.replace_last)
- this.chatstore[username].pop();
-
- this.chatstore[username].push(msg.html);
-
- store.set(this.username + '-chats', this.chatstore);
- },
-
- // === //private// {{{AjaxIM.}}}**{{{_friendUpdate(friend, status, statusMessage)}}}** ===
- //
- // Called when a friend's status is updated. This function will update all locations
- // where a status icon is displayed (chat tab, friends list), as well as insert
- // a notification, should a chatbox be open.
- //
- // ==== Parameters ====
- // * {{{friend}}} is the username of the friend.
- // * {{{status}}} is the new status code. See {{{AjaxIM.statuses}}} for a list of available
- // codes. //Note: If an invalid status is specified, no action will be taken.//
- // * {{{statusMessage}}} is a message that was, optionally, specified by the user. It will be
- // used should "you" send the user an IM while they are away, or if their status is viewed
- // in another way (such as via the friends list [**not yet implemented**]).
- _friendUpdate: function(friend, status, statusMessage) {
- if(this.chats[friend]) {
- var tab = this.chats[friend].parents('.imjs-tab');
- var tab_class = 'imjs-tab';
- if(tab.data('state') == 'active') tab_class += ' imjs-selected';
- tab_class += ' imjs-' + status;
-
- tab.attr('class', tab_class);
-
- // display the status in the chatbox
- var date_stamp =
- $('.imjs-tab.imjs-default .imjs-chatbox .imjs-msglog .imjs-date').clone();
-
- var date_stamp_time = date_stamp.find('.imjs-msg-time');
- if(date_stamp_time.length)
- date_stamp_time.html(dateFormat(date_stamp_time.html()));
-
- var date_stamp_date = date_stamp.find('.imjs-date-date').html(
- AjaxIM.l10n[
- 'chat' + status.toUpperCase().slice(0, 1) + status.slice(1)
- ].replace(/%s/g, friend));
-
- var msglog = this.chats[friend].find('.imjs-msglog');
- date_stamp.appendTo(msglog);
- msglog[0].scrollTop = msglog[0].scrollHeight;
- }
-
- if (!this.friends[friend]) {
- this.addFriend(friend, [status, statusMessage], 'Friends');
- }
- if(this.friends[friend]) {
- var friend_id = 'imjs-friend-' + md5.hex(friend + this.friends[friend].group);
- $('#' + friend_id).attr('class', 'imjs-friend imjs-' + status);
- $('#' + friend_id).find('.imjs-friend-status')
- .html(statusMessage)
- .attr('status', statusMessage);
-
- if(status == 'offline') {
- $('#' + friend_id + ':visible').slideUp();
- $('#' + friend_id + ':hidden').hide();
- } else if(!$('#' + friend_id + ':visible').length) {
- $('#' + friend_id).slideDown();
- }
-
- this.friends[friend].status = [status, statusMessage];
- this._updateFriendCount();
- }
- },
-
- // === //private// {{{AjaxIM.}}}**{{{_notConnected()}}}** ===
- //
- // Puts the user into a visible state of disconnection. Sets the
- // friends list to "not connected" and empties it; disallows new messages
- // to be sent.
- _notConnected: function() {
- $('#imjs-friends')
- .addClass('imjs-not-connected')
- .unbind('click', this.activateTab);
- if($('#imjs-friends').hasClass('imjs-selected'))
- this.activateTab($('#imjs-friends'));
- },
-
- _showReconnect: function() {
- $('#imjs-reconnect').show();
- },
-
- // === {{{AjaxIM.}}}**{{{send(to, message)}}}** ===
- //
- // Sends a message to another user. The message will be added
- // to the chatbox before it is actually sent, however, if an
- // error occurs during sending, that will be indicated immediately
- // afterward.
- //
- // After sending the message, one of three status codes should be
- // returned as a JSON object, e.g. {{{{r: 'code'}}}}:
- // * {{{ok}}} — Message was sent successfully.
- // * {{{offline}}} — The user is offline or unavailable to
- // receive messages.
- // * {{{error}}} — a problem occurred, unrelated to the user
- // being unavailable.
- //
- // ==== Parameters ====
- // * {{{to}}} is the username of the recipient.
- // * {{{message}}} is the content to be sent.
- send: function(username, body) {
- if(!body) return;
- var self = this;
-
- if(this.chats[username]) {
- // possibly add a datestamp
- this._store(username, this._addDateStamp(this.chats[username]));
- this._store(username,
- this._addMessage('a', this.chats[username],
- this.username, body));
- }
-
- $(this).trigger('sendingMessage', [username, body]);
-
- var event = {type: 'message', from: this.username, to: username, body: body};
- this.sendEvent(event, function(result) {
- if(result._status.sent) {
- $(self).trigger('sendMessageSuccessful', [username, body]);
- } else if(result.type == 'error') {
- if(result.error == 'not online')
- $(self).trigger('sendMessageFailed', ['offline', username, body]);
- else
- $(self).trigger('sendMessageFailed', [result.error, username, body]);
- }
- }, function(error) {
- self._notConnected();
- var error = self._addError(
- self.chats[username],
- 'You are currently not connected or the ' +
- 'server is not available. Please ensure ' +
- 'that you are signed in and try again.');
- self._store(error);
- $(self).trigger('sendMessageFailed',
- ['not connected', username, body]);
- });
- },
-
- // === {{{AjaxIM.}}}**{{{status(s, message)}}}** ===
- //
- // Sets the user's status and status message. It is possible to not
- // set a status message by setting it to an empty string. The status
- // will be sent to the server, where upon the server will broadcast
- // the update to all individuals with "you" on their friends list.
- //
- // ==== Parameters ====
- // * {{{s}}} is the status code, as defined by {{{AjaxIM.statuses}}}.
- // * {{{message}}} is the custom status message.
- status: function(value, message) {
- var self = this;
-
- // update status icon(s)
- if(!~this.statuses.indexOf(value))
- return;
-
- // check if selected before writing over the class!
- $(this.statuses).each(function() {
- $('#imjs-friends').removeClass('imjs-' + this);
- });
- $('#imjs-friends').addClass('imjs-' + value);
-
- $(this).trigger('changingStatus', [value, message]);
-
- if(value == 'offline') {
- self._notConnected();
- self._showReconnect();
- store.set(this.username + '-offline', true);
- self.offline = true;
- $('.imjs-input').attr('disabled', true);
-
- AjaxIM.post(
- this.actions.signoff,
- {},
- function(result) {
- if(result.type == 'success')
- $(self).trigger('changeStatusSuccessful',
- [value, null]);
- },
- function(error) {
- $(self).trigger('changeStatusFailed',
- ['not connected', value, null]);
- }
- );
- } else {
- var event = {type: 'status', status: value, message: message};
- this.sendEvent(event, function(result) {
- if(result._status.send) {
- $(self).trigger('sendMessageSuccessful', [username, body]);
- } else if(result.type == 'error') {
- if(result.error == 'not online')
- $(self).trigger('sendMessageFailed', ['offline', username, body]);
- else
- $(self).trigger('sendMessageFailed', [result.error, username, body]);
- }
- }, function(error) {
- self._notConnected();
- var error = self._addError(
- self.chats[username],
- 'You are currently not connected or the ' +
- 'server is not available. Please ensure ' +
- 'that you are signed in and try again.');
- self._store(error);
- $(self).trigger('sendMessageFailed',
- ['not connected', username, body]);
- });
- }
- },
-
- // === {{{AjaxIM.}}}**{{{statuses}}}** ===
- //
- // These are the available status codes and their associated identities:
- // * {{{offline}}} (0) — Only used when signing out/when another
- // user has signed out, as once this status is set, the user is removed
- // from the server and friends will be unable to contact the user.
- // * {{{available}}} (1) — The user is online and ready to be messaged.
- // * {{{away}}} (2) — The user is online but is not available. Others
- // may still contact this user, however, the user may not respond. Anyone
- // contacting an away user will receive a notice stating that the user is away,
- // and (if one is set) their custom status message.
- // * {{{invisible}}} (3; **not yet implemented**) — The user is online,
- // but other users are made unaware, and the user will be represented
- // as being offline. It is still possible to contact this user, and for this
- // user to contact others; no status message or notice will be sent to others
- // messaging this user.
- statuses: ['offline', 'available', 'away'],
-
- // === {{{AjaxIM.}}}**{{{initTabs()}}}** ===
- //
- // Setup the footer bar and enable tab actions. This function
- // uses {{{jQuery.live}}} to set hooks on any bar tabs created
- // in the future.
- initTabBar: function() {
- var self = this;
-
- // Set up your standard tab actions
- $(document)
- .on('click', '.imjs-tab', function() {
- return self.activateTab.call(self, $(this));
- });
-
- $(document)
- .on('click', '.imjs-tab .imjs-close', function() {
- return self.closeTab.call(self, $(this));
- });
-
- // Set up the friends list actions
- $(document).click(function(e) {
- if(~['imjs-friends'].indexOf(e.target.id) ||
- $(e.target).parents('#imjs-friends').length) {
- return;
- }
-
- if($('#imjs-friends').data('state') == 'active')
- self.activateTab.call(self, $('#imjs-friends'));
- else if($('#imjs-status').data('state') == 'active')
- self.activateTab.call(self, $('#imjs-status'));
- });
-
- $('#imjs-friends')
- .data('state', 'minimized')
- .click(function(e) {
- if(!$(this).hasClass('imjs-not-connected') &&
- e.target.id != 'imjs-friends-panel' &&
- !$(e.target).parents('#imjs-friends-panel').length)
- self.activateTab.call(self, $(this));
- })
- .mouseenter(function() {
- if($(this).hasClass('imjs-not-connected')) {
- $('.imjs-tooltip')
- .css('display', 'block')
- .find('p')
- .html(AjaxIM.l10n.notConnectedTip);
-
- var tip_left = $(this).offset().left -
- $('.imjs-tooltip').outerWidth() +
- ($(this).outerWidth() / 2);
- var tip_top = $(this).offset().top -
- $('.imjs-tooltip').outerHeight(true);
-
- $('.imjs-tooltip').css({
- left: tip_left,
- top: tip_top
- });
- }
- })
- .mouseleave(function() {
- if($(this).hasClass('imjs-not-connected')) {
- $('.imjs-tooltip').css('display', '');
- }
- });
-
- $('#imjs-friends-panel').css('display', 'none');
- },
-
- // === {{{AjaxIM.}}}**{{{activateTab()}}}** ===
- //
- // Activate a tab by setting it to the 'active' state and
- // showing any related chatbox. If a chatbox is available
- // for this tab, also focus the input box.
- //
- // //Note:// {{{this}}}, here, refers to the tab DOM element.
- activateTab: function(tab) {
- var chatbox = tab.find('.imjs-chatbox') || false,
- input;
-
- if(tab.data('state') != 'active') {
- if(tab.attr('id') != 'imjs-friends') {
- $('#imjs-bar > li')
- .not(tab)
- .not('#imjs-friends, .imjs-scroll, .imjs-default')
- .add(tab.attr('id') == 'imjs-status' ? '#imjs-friends' : '')
- .removeClass('imjs-selected')
- .each(function() {
- var self = $(this);
- if(self.data('state') != 'closed') {
- self.data('state', 'minimized');
- var chatbox = self.find('.imjs-chatbox');
- if(chatbox.length)
- chatbox.css('display', 'none');
- }
- });
- } else {
- $('#imjs-status')
- .removeClass('imjs-selected')
- .data('state', 'minimized')
- .find('.imjs-chatbox')
- .css('display', 'none');
- }
-
- if(chatbox && chatbox.css('display') == 'none')
- chatbox.css('display', '');
-
- // set the tab to active...
- tab.addClass('imjs-selected').data('state', 'active');
-
- // ...and hide and reset the notification icon
- tab.find('.imjs-notification').css('display', 'none')
- .data('count', 0);
-
- if(chatbox && (username = chatbox.data('username')))
- store.set(this.username + '-activeTab', username);
-
- $(this).trigger('tabToggled', ['activated', tab]);
- } else {
- tab.removeClass('imjs-selected').data('state', 'minimized');
-
- if(chatbox && chatbox.css('display') != 'none')
- chatbox.css('display', 'none');
-
- store.set(this.username + '-activeTab', '');
-
- $(this).trigger('tabToggled', ['minimized', tab]);
- }
-
- if(chatbox) {
- if((input = chatbox.find('.imjs-input')).length &&
- !input.data('height')) {
- input.height(0);
- if(input[0].scrollHeight > input.height() ||
- input[0].scrollHeight < input.height()) {
- input.height(input[0].scrollHeight);
- }
-
- // store the height for resizing later
- if (!input.height()) {
- input.height(16);
- }
- input.data('height', input.height());
- }
-
- try {
- var msglog = chatbox.find('.imjs-msglog');
- msglog[0].scrollTop = msglog[0].scrollHeight;
- } catch(e) {}
-
- try { chatbox.find('.imjs-input').focus(); } catch(e) {}
- }
- },
-
- // === {{{AjaxIM.}}}**{{{closeTab()}}}** ===
- //
- // Close a tab and hide any related chatbox, such that
- // the chatbox can not be reopened without reinitializing
- // the tab.
- //
- // //Note:// {{{this}}}, here, refers to the tab DOM element.
- closeTab: function(tab) {
- tab = tab.parents('.imjs-tab');
- tab.css('display', 'none')
- .removeClass('imjs-selected')
- .data('state', 'closed');
-
- delete this.chatstore[tab.find('.imjs-chatbox').data('username')];
- store.set(this.username + '-chats', this.chatstore);
-
- $(this).trigger('tabToggled', ['closed', tab]);
-
- this._scrollers();
-
- return false;
- },
-
- // === {{{AjaxIM.}}}**{{{addTab(label, action, closable)}}}** ===
- //
- // Adds a tab to the tab bar, with the label {{{label}}}. When
- // clicked, it will call a callback function, {{{action}}}. If
- // {{{action}}} is a string, it is assumed that the string is
- // referring to a chatbox ID.
- //
- // ==== Parameters ====
- // * {{{label}}} is the text that will be displayed on the tab.\\
- // * {{{action}}} is the callback function, if it is a non-chatbox
- // tab, or a string if it //is// a chatbox tab.\\
- // * {{{closable}}} is a boolean value that determines whether or not
- // it is possible for a user to close this tab.
- //
- // //Note:// New tabs are given an automatically generated ID
- // in the format of {{{#imjs-tab-[md5 of label]}}}.
- addTab: function(label, action, closable) {
- var tab = $('.imjs-tab.imjs-default').clone().insertAfter('#imjs-scroll-left');
- tab.removeClass('imjs-default')
- .attr('id', 'imjs-tab-' + md5.hex(label))
- .html(tab.html().replace('{label}', label))
- .data('state', 'minimized');
-
- var notification = tab.find('.imjs-notification');
- notification.css('display', 'none')
- .data('count', 0)
- .data('default-text', notification.html())
- .html(notification.html().replace('{count}', '0'));
-
- if(closable === false)
- tab.find('.imjs-close').eq(0).remove();
-
- if(typeof action != 'string') {
- tab.find('.imjs-chatbox').remove();
- tab.click(action);
- }
-
- return tab;
- },
-
- // === {{{AjaxIM.}}}**{{{notification(tab)}}}** ===
- //
- // Displays a notification on a tab. Generally, this is called when
- // a tab is minimized to let the user know that there is an update
- // for them. The way the notification is displayed depends on the
- // theme CSS.
- //
- // ==== Parameters ====
- // * {{{tab}}} is the jQuery-selected tab DOM element.
- notification: function(tab) {
- var notify = tab.find('.imjs-notification');
- var notify_count = notify.data('count') + 1;
-
- notify.data('count', notify_count)
- .html(notify.data('default-text').replace('{count}', notify_count))
- .css('display', '');
- },
-
- // === //private// {{{AjaxIM.}}}**{{{_scrollers()}}}** ===
- //
- // Document me!
- _scrollers: function() {
- var needScrollers = false;
- $('#imjs-scroll-left').nextAll('.imjs-tab')
- .filter(function() {
- return $(this).data('state') != 'closed';
- })
- .each(function(i, tab) {
- tab = $(tab).css('display', '');
-
- var tab_pos = tab.position();
- if(tab_pos.top >= $('#imjs-bar').height() ||
- tab_pos.left < 0 ||
- tab_pos.right > $(document).width()) {
- $('.imjs-scroll').css('display', '');
- tab.css('display', 'none');
- needScrollers = true;
- }
- });
-
- if(!needScrollers) {
- $('.imjs-scroll').css('display', 'none');
- }
-
- if($('#imjs-scroll-left').css('display') != 'none' &&
- $('#imjs-scroll-right').position().top >= $('#imjs-bar').height()) {
- $('#imjs-bar li.imjs-tab:visible').slice(-1).css('display', 'none');
- }
-
- if($('#imjs-bar li.imjs-tab:visible').length) {
- while($('.imjs-selected').css('display') == 'none')
- $('#imjs-scroll-right').click();
- }
-
- this._scrollerIndex();
- },
-
- _scrollerIndex: function() {
- var hiddenRight = $('#imjs-bar li.imjs-tab:visible').slice(-1)
- .nextAll('#imjs-bar li.imjs-tab:hidden')
- .not('.imjs-default')
- .filter(function() {
- return $(this).data('state') != 'closed'
- }).length;
-
- var hiddenLeft = $('#imjs-bar li.imjs-tab:visible').eq(0)
- .prevAll('#imjs-bar li.imjs-tab:hidden')
- .not('.imjs-default')
- .filter(function() {
- return $(this).data('state') != 'closed'
- }).length;
-
- $('#imjs-scroll-left').html(hiddenLeft);
- $('#imjs-scroll-right').html(hiddenRight);
- },
-
- unconfirmedEvents: {},
- eventId: 1,
-
- createEvent: function() {
- var event = {};
- event.id = this.eventId++;;
- this.unconfirmedEvents[event.id] = evt;
- },
-
- sendEvent: function(event, successFunc, failureFunc) {
- event.id = this.eventId++;
- var evt = $.extend({}, event);
- evt['_status'] = {
- successFunc: successFunc,
- failureFunc: failureFunc
- };
- this.unconfirmedEvents[event.id] = evt;
- if (this.socket) {
- this.socket.emit('server', event);
- } else {
- var self = this;
- var url = null;
- switch (event.type) {
- case 'message':
- url = this.actions.send;
- break;
- case 'status':
- url = this.actions.status;
- break;
- case 'signoff':
- url = this.actions.signoff;
- break;
- default:
- break;
- }
-
- AjaxIM.post(url, event,
- function(result) {
- if (result) {
- for (var e=0; e < result.length; ++e) {
- self.dispatchEvent(events[e]);
- }
- }
- },
- function(error) {
- if (self.unconfirmedEvents[event.id]) {
- event = self.unconfirmedEvents[event.id];
- event['_status']['sent'] = false;
- self.dispatchEvent(event);
- }
- }
- );
- }
- },
-
- dispatchEvent: function(event) {
- if (event.id && this.unconfirmedEvents[event.id]) {
- event['_status'] = $.extend({}, this.unconfirmedEvents[event.id]['_status'], event['_status']);
- delete this.unconfirmedEvents[event.id];
- if (event['_status']['sent']) {
- event['_status']['successFunc'](event);
- } else {
- event['_status']['failureFunc'](event);
- }
- } else {
- this.triggerEvent(event);
- }
- },
-
- // poor man's Backbone.js Events
- eventHandlers: {},
-
- /**
- * Add a callback to listen for an event type.
- */
- onEvent: function(eventType, callback) {
- if (!this.eventHandlers[eventType]) {
- this.eventHandlers[eventType] = [];
- }
- this.eventHandlers[eventType].push(callback);
- },
-
- /**
- * Trigger an event on all interested callbacks.
- */
- triggerEvent: function(event) {
- if (this.eventHandlers[event.type]) {
- for (var e=0; e < this.eventHandlers[event.type].length; ++e) {
- this.eventHandlers[event.type][e].call(this, event);
- }
- }
- }
- })
-
- // == Static functions and variables ==
- //
- // The following functions and variables are available outside of an initialized
- // {{{AjaxIM}}} object.
-
- // === {{{AjaxIM.}}}**{{{client}}}** ===
- //
- // Once {{{AjaxIM.init()}}} is called, this will be set to the active AjaxIM
- // object. Only one AjaxIM object instance can exist at a time. This variable
- // can and should be accessed directly.
- AjaxIM.client = null;
-
- // === {{{AjaxIM.}}}**{{{init(options, actions)}}}** ===
- //
- // Initialize the AjaxIM client object and engine. Here, you can define your
- // options and actions as outlined at the top of this documentation.
- //
- // ==== Parameters ====
- // * {{{options}}} is the hash of custom settings to initialize Ajax IM with.
- // * {{{actions}}} is the hash of any custom action URLs.
- AjaxIM.init = function(options, actions) {
- if(!AjaxIM.client)
- AjaxIM.client = new AjaxIM(options, actions);
-
- return AjaxIM.client;
- };
-
-
- // === {{{AjaxIM.}}}**{{{request(url, data, successFunc, failureFunc)}}}** ===
- //
- // Wrapper around {{{$.jsonp}}}, the JSON-P library for jQuery, and {{{$.ajax}}},
- // jQuery's ajax library. Allows either function to be called, automatically,
- // depending on the request's URL array (see {{{AjaxIM.actions}}}).
- //
- // ==== Parameters ====
- // {{{url}}} is the URL of the request.
- // {{{data}}} are any arguments that go along with the request.
- // {{{success}}} is a callback function called when a request has completed
- // without issue.
- // {{{_ignore_}}} is simply to provide compatability with {{{$.post}}}.
- // {{{failure}}} is a callback function called when a request hasn't not
- // completed successfully.
- AjaxIM.post = function(url, data, successFunc, failureFunc, urlnoop) {
- AjaxIM.request(url, 'POST', data, successFunc, failureFunc, urlnoop);
- };
-
- AjaxIM.get = function(url, data, successFunc, failureFunc, urlnoop) {
- AjaxIM.request(url, 'GET', data, successFunc, failureFunc, urlnoop);
- };
-
- AjaxIM.request = function(url, type, data, successFunc, failureFunc, noopurl) {
- var errorTypes = ['timeout', 'error', 'notmodified', 'parseerror'];
- if(typeof failureFunc != 'function')
- failureFunc = function(){};
-
- var jsonp = (url.substring(0, 1) !== '/');
- var success = false;
- data['sessionid'] = cookies.get('sessionid');
- $.ajax({
- url: url,
- data: data,
- dataType: jsonp? 'jsonp': 'json',
- type: type,
- cache: false,
- timeout: 299000
- }).done(function(data) {
- success = true;
- _dbg(JSON.stringify(data));
- successFunc(data);
- }).fail(function(jqXHR, textStatus) {
- _dbg(textStatus);
- failureFunc(textStatus);
- });
-
- if (jsonp) {
- setTimeout(function() {
- var failfn = function() {
- if (!success) {
- var textStatus = 'error';
- _dbg(textStatus);
- failureFunc(textStatus);
- }
- };
- if (noopurl) {
- var noopfn = function() {
- var noopdone = false;
- var event = {type: 'noop'};
- $.ajax({
- url: noopurl,
- data: event,
- dataType: 'jsonp',
- type: type,
- cache: false,
- timeout: 299000
- }).done(function(data) {
- noopdone = true;
- if (!success) {
- setTimeout(noopfn, 3000);
- }
- }).fail(function(jqXHR, textStatus) {
- // since JSONP, never called
- });
- setTimeout(function() {
- if (!noopdone) {
- failfn();
- }
- }, 3000);
- };
- noopfn();
- } else {
- failfn();
- }
- }, 3000);
- }
-
- // This prevents Firefox from spinning indefinitely
- // while it waits for a response.
- /*
- if(url == 'jsonp' && $.browser.mozilla) {
- $.jsonp({
- 'url': 'about:',
- timeout: 0
- });
- }
- */
- };
-
- // === {{{AjaxIM.}}}**{{{incoming(data)}}}** ===
- //
- // Never call this directly. It is used as a connecting function between
- // client and server for Comet.
- //
- // //Note:// There are two {{{AjaxIM.incoming()}}} functions. This one is a
- // static function called outside of the initialized AjaxIM object; the other
- // is only called within the initalized AjaxIM object.
- AjaxIM.incoming = function(data) {
- if(!AjaxIM.client)
- return false;
-
- if(data.length)
- AjaxIM.client._parseMessages(data);
- };
-
- AjaxIM.eventID = 1;
-
- // === {{{AjaxIM.}}}**{{{l10n}}}** ===
- //
- // Text strings used by Ajax IM. Should you want to translate Ajax IM into
- // another language, merely change these strings.
- //
- // {{{%s}}} denotes text that will be automatically replaced when the string is
- // used.
- AjaxIM._ = function(str) {
- if(str in AjaxIM.l10n) return AjaxIM.l10n[str];
- return str;
- };
-
- AjaxIM.l10n = {
- dayNames: [
- "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
- "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
- ],
- monthNames: [
- "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
- "January", "February", "March", "April", "May", "June", "July", "August", "September",
- "October", "November", "December"
- ],
-
- chatOffline: '%s signed off.',
- chatAvailable: '%s became available.',
- chatAway: '%s went away.',
-
- notConnected: 'You are currently not connected or the server is not available. ' +
- 'Please ensure that you are signed in and try again.',
- notConnectedTip: 'You are currently not connected.',
-
- defaultAway: 'I\'m away.'
- };
-
- AjaxIM.debug = true;
- function _dbg(msg) {
- if(AjaxIM.debug && window.console) console.log(msg);
- }
-
- function uid(n){
- var chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', nn='';
- for(var c=0; c < n; c++){
- nn += chars.substr(0|Math.random() * chars.length, 1);
- }
- return nn;
- }
|