1// Browser detection mostly taken from prototype.js 1.5.1.1.
2var is_ie     = !!(window.attachEvent && !window.opera);
3var is_khtml  = !!(navigator.appName.match("Konqueror") || navigator.appVersion.match("KHTML"));
4var is_gecko  = navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1;
5var is_ie7    = navigator.userAgent.indexOf('MSIE 7') > 0;
6var is_ie6    = navigator.userAgent.indexOf('MSIE 6') > 0;
7var is_opera  = !!window.opera;
8var is_webkit = navigator.userAgent.indexOf('AppleWebKit/') > -1;
9
10/**
11 * This class is the client part of phpFreeChat
12 * (depends on prototype library)
13 * @author Stephane Gully
14 */
15var pfcClient = Class.create();
16
17//defining the rest of the class implmentation
18pfcClient.prototype = {
19
20  initialize: function()
21  {
22    // load the graphical user interface builder
23    this.gui = new pfcGui();
24    // load the resources manager (labels and urls)
25    this.res = new pfcResource();
26
27    this.nickname = pfc_nickname;
28    this.nickid   = pfc_nickid;
29    this.usermeta = $H();
30    this.chanmeta = $H();
31    this.nickwhoisbox = $H();
32
33    // this array contains all the sent commands
34    // use the up and down arrow key to navigate through the history
35    this.cmdhistory   = Array();
36    this.cmdhistoryid = -1;
37    this.cmdhistoryissearching = false;
38
39    /*
40    this.channels      = Array();
41    this.channelids    = Array();
42    */
43    this.privmsgs      = Array();
44    this.privmsgids    = Array();
45
46    this.timeout            = null;
47    this.timeout_time       = new Date().getTime();
48
49    this.refresh_delay       = pfc_refresh_delay;
50    this.refresh_delay_steps = pfc_refresh_delay_steps;
51    this.last_response_time = new Date().getTime();
52    this.last_request_time  = new Date().getTime();
53    this.last_activity_time = new Date().getTime();
54
55    /* unique client id for each windows used to identify a open window
56     * this id is passed every time the JS communicate with server
57     * (2 clients can use the same session: then only the nickname is shared) */
58    this.clientid      = pfc_clientid;
59
60    this.isconnected   = false;
61    this.nicklist      = $H();
62    this.nickcolor     = Array();
63    this.colorlist     = Array();
64
65    this.blinktmp     = Array();
66    this.blinkloop    = Array();
67    this.blinktimeout = Array();
68  },
69
70  loadChat: function() {
71    new Ajax.Request(pfc_server_script_url, {
72      method: 'get',
73      parameters: {pfc_ajax: 1, f: 'loadChat'},
74      onSuccess: function(transport) {
75        eval( transport.responseText );
76      }
77    });
78  },
79
80  connectListener: function()
81  {
82    this.el_words     = $('pfc_words');
83    this.el_handle    = $('pfc_handle');
84    this.el_container = $('pfc_container');
85//    this.el_online    = $('pfc_online');
86    this.el_errors    = $('pfc_errors');
87
88    this.detectactivity = new DetectActivity(this.el_container);
89    // restore the window title when user come back to the active zone
90    if (pfc_notify_window) this.detectactivity.onunactivate = this.gui.unnotifyWindow.bindAsEventListener(this.gui);
91
92    /* the events callbacks */
93    this.el_words.onkeypress = this.callbackWords_OnKeypress.bindAsEventListener(this);
94// don't use this line because when doing completeNick the "return false" doesn't work (focus is lost)
95//    Event.observe(this.el_words,     'keypress',  this.callbackWords_OnKeypress.bindAsEventListener(this), false);
96    Event.observe(this.el_words,     'keydown',   this.callbackWords_OnKeydown.bindAsEventListener(this), false);
97    Event.observe(this.el_words,     'keyup',     this.callbackWords_OnKeyup.bindAsEventListener(this), false);
98    Event.observe(this.el_words,     'mouseup',   this.callbackWords_OnMouseup.bindAsEventListener(this), false);
99    Event.observe(this.el_words,     'focus',     this.callbackWords_OnFocus.bindAsEventListener(this), false);
100    Event.observe(document.body,     'unload',    this.callback_OnUnload.bindAsEventListener(this), false);
101  },
102
103  refreshGUI: function()
104  {
105    this.minmax_status = pfc_start_minimized;
106    var cookie = getCookie('pfc_minmax_status');
107    if (cookie != null)
108      this.minmax_status = (cookie == 'true');
109
110    cookie = getCookie('pfc_nickmarker');
111    this.nickmarker = (cookie == 'true');
112    if (cookie == '' || cookie == null)
113      this.nickmarker = pfc_nickmarker;
114
115    cookie = getCookie('pfc_clock');
116    this.clock = (cookie == 'true');
117    if (cookie == '' || cookie == null)
118      this.clock = pfc_clock;
119
120    cookie = getCookie('pfc_showsmileys');
121    this.showsmileys = (cookie == 'true');
122    if (cookie == '' || cookie == null)
123      this.showsmileys = pfc_showsmileys;
124
125    cookie = getCookie('pfc_showwhosonline');
126    this.showwhosonline = (cookie == 'true');
127    if (cookie == '' || cookie == null)
128      this.showwhosonline = pfc_showwhosonline;
129
130    // '' means no forced color, let CSS choose the text color
131    this.current_text_color = '';
132    cookie = getCookie('pfc_current_text_color');
133    if (cookie != null)
134      this.switch_text_color(cookie);
135
136    cookie = getCookie('pfc_issoundenable');
137    this.issoundenable = (cookie == 'true');
138    if (cookie == '' || cookie == null)
139      this.issoundenable = pfc_startwithsound;
140
141    this.refresh_loginlogout();
142    this.refresh_minimize_maximize();
143    this.refresh_Smileys();
144    this.refresh_sound();
145    this.refresh_nickmarker();
146  },
147
148  /**
149   * Show a popup dialog to ask user to choose a nickname
150   */
151  askNick: function(nickname,error_text)
152  {
153    // ask to choose a nickname
154    if (nickname == '' || nickname == undefined) nickname = this.nickname;
155
156    // build a dhtml prompt box
157    var pfcp = this.getPrompt();//new pfcPrompt($('pfc_container'));
158    pfcp.callback = function(v) { pfc.askNickResponse(v); }
159    pfcp.prompt((error_text != undefined ? '<span style="color:red">'+error_text+'</span><br/>' : '')+this.res.getLabel('Please enter your nickname'), nickname);
160    pfcp.focus();
161  },
162  askNickResponse: function(newnick)
163  {
164    if (newnick)
165    {
166      if (this.isconnected)
167        this.sendRequest('/nick "'+newnick+'"');
168      else
169        this.sendRequest('/connect "'+newnick+'"');
170    }
171  },
172
173  /**
174   * Reacte to the server response
175   */
176  handleResponse: function(cmd, resp, param)
177  {
178    // display some debug messages
179    if (pfc_debug)
180      if (cmd != "update")
181      {
182        var param2 = param;
183        if (cmd == "who" || cmd == "who2")
184        {
185          param2 = $H(param2);
186          param2.set('meta', $H(param2.get('meta')));
187          param2.get('meta').set('users', $H(param2.get('meta').get('users')));
188          trace('handleResponse: '+cmd + "-"+resp+"-"+param2.inspect());
189        }
190        else
191        if (cmd == "whois" || cmd == "whois2")
192        {
193          param2 = $H(param2);
194          trace('handleResponse: '+cmd + "-"+resp+"-"+param2.inspect());
195        }
196        else
197        if (cmd == "getnewmsg" || cmd == "join")
198        {
199          param2 = $A(param2);
200          trace('handleResponse: '+cmd + "-"+resp+"-"+param2.inspect());
201        }
202        else
203          trace('handleResponse: '+cmd + "-"+resp+"-"+param);
204      }
205
206    if (cmd != "update")
207    {
208       // speed up timeout if activity
209       this.last_activity_time = new Date().getTime();
210       var delay = this.calcDelay();
211       if (this.timeout_time - new Date().getTime() > delay)
212       {
213          clearTimeout(this.timeout);
214          this.timeout = setTimeout('pfc.updateChat(true)', delay);
215          this.timeout_time = new Date().getTime() + delay;
216       }
217    }
218
219    if (cmd == "connect")
220    {
221      if (resp == "ok")
222      {
223        this.nickname = param[0];
224        this.isconnected = true;
225
226        // start the polling system
227        this.updateChat(true);
228      }
229      else
230        this.isconnected = false;
231      this.refresh_loginlogout();
232    }
233    else if (cmd == "quit")
234    {
235      if (resp =="ok")
236      {
237        // stop updates
238        this.updateChat(false);
239        this.isconnected = false;
240        this.refresh_loginlogout();
241      }
242    }
243    else if (cmd == "join" || cmd == "join2")
244    {
245      if (resp =="ok")
246      {
247        // create the new channel
248        var tabid = param[0];
249        var name  = param[1];
250        this.gui.createTab(name, tabid, "ch");
251        if (cmd != "join2" || this.gui.tabs.length == 1) this.gui.setTabById(tabid);
252        this.refresh_Smileys();
253        this.refresh_WhosOnline();
254      }
255      else if (resp == "max_channels")
256      {
257        this.displayMsg( cmd, this.res.getLabel('Maximum number of joined channels has been reached') );
258      }
259      else
260        alert(cmd + "-"+resp+"-"+param);
261    }
262    else if (cmd == "leave")
263    {
264      if (resp =="ok")
265      {
266        // remove the channel
267        var tabid = param;
268        this.gui.removeTabById(tabid);
269
270        // synchronize the channel client arrays
271        /*
272        var index = -1;
273        index = this.channelids.indexOf(tabid);
274        this.channelids = this.channelids.without(tabid);
275        this.channels   = this.channels.without(this.channels[index]);
276        */
277
278        // synchronize the privmsg client arrays
279        index = -1;
280        index = indexOf(this.privmsgids, tabid);
281        this.privmsgids = without(this.privmsgids, tabid);
282        this.privmsgs   = without(this.privmsgs, this.privmsgs[index]);
283
284      }
285    }
286    else if (cmd == "privmsg" || cmd == "privmsg2")
287    {
288      if (resp == "ok")
289      {
290        // create the new channel
291        var tabid = param[0];
292        var name  = param[1];
293        this.gui.createTab(name, tabid, "pv");
294        if (cmd != "privmsg2" || this.gui.tabs.length == 1) this.gui.setTabById(tabid);
295
296        this.privmsgs.push(name);
297        this.privmsgids.push(tabid);
298
299      }
300      else if (resp == "max_privmsg")
301      {
302        this.displayMsg( cmd, this.res.getLabel('Maximum number of private chat has been reached') );
303      }
304      else if (resp == "unknown")
305      {
306        // speak to unknown user
307        this.displayMsg( cmd, this.res.getLabel('You are trying to speak to a unknown (or not connected) user') );
308      }
309      else if (resp == "speak_to_myself")
310      {
311        this.displayMsg( cmd, this.res.getLabel('You are not allowed to speak to yourself') );
312      }
313      else
314        alert(cmd + "-"+resp+"-"+param);
315    }
316    else if (cmd == "nick")
317    {
318      // give focus the the input text box if wanted
319      if (pfc_focus_on_connect) this.el_words.focus();
320
321      if (resp == "connected" || resp == "notchanged")
322      {
323        cmd = '';
324      }
325
326      if (resp == "ok" || resp == "notchanged" || resp == "changed" || resp == "connected")
327      {
328        this.setUserMeta(this.nickid, 'nick', param);
329        this.el_handle.innerHTML = this.getUserMeta(this.nickid, 'nick').escapeHTML();
330        this.nickname = this.getUserMeta(this.nickid, 'nick');
331        this.updateNickBox(this.nickid);
332
333        // clear the possible error box generated by the bellow displayMsg(...) function
334        this.clearError(Array(this.el_words));
335      }
336      else if (resp == "isused")
337      {
338        this.setError(this.res.getLabel('Chosen nickname is already used'), Array());
339        this.askNick(param,this.res.getLabel('Chosen nickname is already used'));
340      }
341      else if (resp == "notallowed")
342      {
343        // When frozen_nick is true and the nickname is already used, server will return
344        // the 'notallowed' status. It will display a message and stop chat update.
345        // If the chat update is not stopped, this will loop forever
346        // as long as the forced nickname is not changed.
347
348        // display a message
349        this.setError(this.res.getLabel('Chosen nickname is not allowed'), Array());
350        // then stop chat updates
351        this.updateChat(false);
352        this.isconnected = false;
353        this.refresh_loginlogout();
354      }
355    }
356    else if (cmd == "update")
357    {
358    }
359    else if (cmd == "version")
360    {
361      if (resp == "ok")
362      {
363        this.displayMsg( cmd, this.res.getLabel('phpfreechat current version is %s',param) );
364      }
365    }
366    else if (cmd == "help")
367    {
368      if (resp == "ok")
369      {
370        this.displayMsg( cmd, param);
371      }
372    }
373    else if (cmd == "rehash")
374    {
375      if (resp == "ok")
376      {
377        this.displayMsg( cmd, this.res.getLabel('Configuration has been rehashed') );
378      }
379      else if (resp == "ko")
380      {
381        this.displayMsg( cmd, this.res.getLabel('A problem occurs during rehash') );
382      }
383    }
384    else if (cmd == "banlist")
385    {
386      if (resp == "ok" || resp == "ko")
387      {
388        this.displayMsg( cmd, param );
389      }
390    }
391    else if (cmd == "unban")
392    {
393      if (resp == "ok" || resp == "ko")
394      {
395        this.displayMsg( cmd, param );
396      }
397    }
398    else if (cmd == "auth")
399    {
400      if (resp == "ban")
401      {
402        alert(param);
403      }
404      if (resp == "frozen")
405      {
406        alert(param);
407      }
408      else if (resp == "nick")
409      {
410        this.displayMsg( cmd, param );
411      }
412    }
413    else if (cmd == "debug")
414    {
415      if (resp == "ok" || resp == "ko")
416      {
417        this.displayMsg( cmd, param );
418      }
419    }
420    else if (cmd == "clear")
421    {
422      var tabid     = this.gui.getTabId();
423      var container = this.gui.getChatContentFromTabId(tabid);
424      container.innerHTML = "";
425    }
426    else if (cmd == "identify")
427    {
428      this.displayMsg( cmd, param );
429    }
430    else if (cmd == "checknickchange")
431    {
432      this.displayMsg( cmd, param );
433    }
434    else if (cmd == "whois" || cmd == "whois2")
435    {
436      param = $H(param);
437      var nickid = param.get('nickid');
438      if (resp == "ok")
439      {
440        this.setUserMeta(nickid, param);
441        this.updateNickBox(nickid);
442      }
443      if (cmd == "whois")
444      {
445        // display the whois info
446        var um = this.getAllUserMeta(nickid);
447        var um_keys = um.keys();
448        var msg = '';
449        for (var i=0; i<um_keys.length; i++)
450        {
451          var k = um_keys[i];
452          var v = um.get(k);
453          if (v &&
454              // these parameter are used internaly (don't display it)
455              k != 'nickid' &&
456              k != 'floodtime' &&
457              k != 'flood_nbmsg' &&
458              k != 'flood_nbchar')
459            msg = msg + '<strong>' + k + '</strong>: ' + v + '<br/>';
460        }
461        this.displayMsg( cmd, msg );
462      }
463    }
464    else if (cmd == "who" || cmd == "who2")
465    {
466      param = $H(param);
467      var chan   = param.get('chan');
468      var chanid = param.get('chanid');
469      var meta   = $H(param.get('meta'));
470      meta.set('users', $H(meta.get('users')));
471      if (resp == "ok")
472      {
473        this.setChanMeta(chanid,meta);
474        // send /whois commands for unknown users
475        for (var i=0; i<meta.get('users').get('nickid').length; i++)
476        {
477          var nickid = meta.get('users').get('nickid')[i];
478          var nick   = meta.get('users').get('nick')[i];
479          var um = this.getAllUserMeta(nickid);
480          if (!um) this.sendRequest('/whois2 "'+nickid+'"');
481        }
482
483        // update the nick list display on the current channel
484        this.updateNickListBox(chanid);
485      }
486      if (cmd == "who")
487      {
488        // display the whois info
489        var cm = this.getAllChanMeta(chanid);
490        var cm_keys = cm.keys();
491        var msg = '';
492        for (var i=0; i<cm_keys.length; i++)
493        {
494          var k = cm_keys[i];
495          var v = cm[k];
496          if (k != 'users')
497          {
498            msg = msg + '<strong>' + k + '</strong>: ' + v + '<br/>';
499          }
500        }
501        this.displayMsg( cmd, msg );
502      }
503    }
504    else if (cmd == "getnewmsg")
505    {
506      if (resp == "ok")
507      {
508        this.handleComingRequest(param);
509      }
510    }
511    else if (cmd == "send")
512    {
513    }
514    else
515      alert(cmd + "-"+resp+"-"+param);
516  },
517
518  getAllUserMeta: function(nickid)
519  {
520    if (nickid && this.usermeta.get(nickid))
521      return this.usermeta.get(nickid);
522    else
523      return null;
524  },
525
526  getUserMeta: function(nickid, key)
527  {
528    if (nickid && key && this.usermeta.get(nickid) && this.usermeta.get(nickid).get(key))
529      return this.usermeta.get(nickid).get(key);
530    else
531      return '';
532  },
533
534  setUserMeta: function(nickid, key, value)
535  {
536    if (nickid && key)
537    {
538      if (!this.usermeta.get(nickid)) this.usermeta.set(nickid, $H());
539      if (value)
540        this.usermeta.get(nickid).set(key, value);
541      else
542        this.usermeta.set(nickid, $H(key));
543    }
544  },
545
546  getAllChanMeta: function(chanid)
547  {
548    if (chanid && this.chanmeta.get(chanid))
549      return this.chanmeta.get(chanid);
550    else
551      return null;
552  },
553
554  getChanMeta: function(chanid, key)
555  {
556    if (chanid && key && this.chanmeta.get(chanid) && this.chanmeta.get(chanid).get(key))
557      return this.chanmeta.get(chanid).get(key);
558    else
559      return '';
560  },
561
562  setChanMeta: function(chanid, key, value)
563  {
564    if (chanid && key)
565    {
566      if (!this.chanmeta.get(chanid)) this.chanmeta.set(chanid, $H());
567      if (value)
568        this.chanmeta.get(chanid).set(key,value);
569      else
570        this.chanmeta.set(chanid, $H(key));
571    }
572  },
573
574  doSendMessage: function()
575  {
576    var w = this.el_words;
577    var wval = w.value;
578
579    // Append the string to the history.
580    this.cmdhistory.push(wval);
581    this.cmdhistoryid = this.cmdhistory.length;
582    this.cmdhistoryissearching = false;
583
584    // Send the string to the server.
585    re = new RegExp("^(\/[a-zA-Z0-9]+)( (.*)|)");
586    if (wval.match(re))
587    {
588      // A user command.
589      cmd = wval.replace(re, '$1');
590      param = wval.replace(re, '$3');
591      this.sendRequest(cmd +' '+ param.substr(0, pfc_max_text_len + 2*this.clientid.length));
592    }
593    else
594    {
595      // A classic 'send' command.
596
597      // Empty messages with only spaces.
598      rx = new RegExp('^[ ]*$','g');
599      wval = wval.replace(rx,'');
600
601      // Truncate the text length.
602      wval = wval.substr(0,pfc_max_text_len);
603
604      // Colorize the text with current_text_color.
605      if (this.current_text_color != '' && wval.length != '')
606        wval = '[color=#' + this.current_text_color + '] ' + wval + ' [/color]';
607
608      this.sendRequest('/send '+ wval);
609    }
610    w.value = '';
611    return false;
612  },
613
614  /**
615   * Try to complete a nickname like on IRC when pressing the TAB key.
616   * Nicks with spaces may not work under certain circumstances.
617   * Replacing spaces with alternate spaces (e.g., &nbsp;) helps.
618   * Gecko browsers convert the &nbsp; to regular spaces, so no help for these browsers.
619   * Note: IRC does not allow nicks with spaces, so it's much easier for those clients. :)
620   * @author Gerard Pinzone
621   */
622  completeNick: function()
623  {
624    var w = this.el_words;
625    var selStart = w.value.length; // Default for browsers that don't support selection/caret position commands.
626    var selEnd = selStart;
627
628    // Get selection/caret position.
629    if (w.setSelectionRange)
630    {
631      // We don't rely on the stored values for browsers that support
632      // the selectionStart and selectionEnd commands.
633      selStart = w.selectionStart;
634      selEnd   = w.selectionEnd;
635    }
636    else if (w.createTextRange && document.selection)
637    {
638      // We must rely on the stored values for IE browsers.
639      selStart = (w.selStart != null) ? w.selStart : w.value.length;
640      selEnd   = (w.selEnd != null) ? w.selEnd : w.value.length;
641    }
642
643    var begin          = w.value.lastIndexOf(' ', selStart - 1) + 1;
644    var end            = (w.value.indexOf(' ', selStart) >= 0) ? w.value.indexOf(' ', selStart) : w.value.length;
645    var nick_src       = w.value.substring(begin, end);
646    var non_nick_begin = w.value.substring(0, begin);
647    var non_nick_end   = w.value.substring(end, w.value.length);
648
649    if (nick_src != '')
650    {
651      var tabid = this.gui.getTabId();
652      var n_list = this.getChanMeta(tabid, 'users')['nick'];
653      var nick_match = false;
654      for (var i = 0; i < n_list.length; i++)
655      {
656        var nick_tmp = n_list[i];
657        // replace spaces in nicks with &nbsp;
658        nick_tmp = nick_tmp.replace(/ /g, '\240');
659        if (nick_tmp.indexOf(nick_src) == 0)
660        {
661          if (! nick_match)
662          {
663            nick_match = true;
664            nick_replace = nick_tmp;
665          }
666          else
667          {
668            // more than one possibility for completion
669            var nick_len = Math.min(nick_tmp.length, nick_replace.length);
670            // only keep characters that are common to all matches
671            var j = 0;
672            for (j = 0; j < nick_len; j++)
673              if (nick_tmp.charAt(j) != nick_replace.charAt(j))
674                break;
675
676            nick_replace = nick_replace.substr(0, j);
677          }
678        }
679      }
680      if (nick_match)
681      {
682        w.value = non_nick_begin + nick_replace + non_nick_end;
683        w.selStart = w.selEnd = non_nick_begin.length + nick_replace.length;
684
685        // Move cursor to end of completed nick.
686        if (w.setSelectionRange)
687          w.setSelectionRange(w.selEnd, w.selEnd); // Gecko
688        else
689          this.setSelection(w);  // IE
690      }
691    }
692  },
693
694  /**
695   * Cycle to older entry in history
696   */
697  historyUp: function()
698  {
699    // Write the previous command in the history.
700    if (this.cmdhistory.length > 0)
701    {
702      var w = this.el_words;
703      if (this.cmdhistoryissearching == false && w.value != "")
704        this.cmdhistory.push(w.value);
705      this.cmdhistoryissearching = true;
706      this.cmdhistoryid = this.cmdhistoryid - 1;
707      if (this.cmdhistoryid < 0)
708        this.cmdhistoryid = 0; // stop at oldest entry
709      w.value = this.cmdhistory[this.cmdhistoryid];
710    }
711  },
712
713  /**
714   * Cycle to newer entry in history
715   */
716  historyDown: function()
717  {
718    // Write the next command in the history.
719    if (this.cmdhistory.length > 0)
720    {
721      var w = this.el_words;
722      if (this.cmdhistoryissearching == false && w.value != "")
723        this.cmdhistory.push(w.value);
724      this.cmdhistoryissearching = true;
725      this.cmdhistoryid = this.cmdhistoryid + 1;
726      if (this.cmdhistoryid >= this.cmdhistory.length)
727      {
728        this.cmdhistoryid = this.cmdhistory.length; // stop at newest entry + 1
729        w.value = ""; // blank input box
730      }
731      else
732        w.value = this.cmdhistory[this.cmdhistoryid];
733    }
734  },
735
736  /**
737   * Handle the pressed keys.
738   * see also callbackWords_OnKeydown
739   */
740  callbackWords_OnKeypress: function(evt)
741  {
742    // All browsers except for IE should use "evt.which."
743    var code = (evt.which) ? evt.which : evt.keyCode;
744    if (code == Event.KEY_RETURN) /* ENTER key */
745    {
746      return this.doSendMessage();
747    }
748    else
749    {
750      // Allow other key defaults.
751      return true;
752    }
753  },
754
755  /**
756   * Handle the pressed keys.
757   * see also callbackWords_OnKeypress
758   * WARNING: Suppressing defaults on the keydown event
759   *          may prevent keypress and/or keyup events
760   *          from firing.
761   */
762  callbackWords_OnKeydown: function(evt)
763  {
764    if (!this.isconnected) return false;
765    this.clearError(Array(this.el_words));
766    var code = (evt.which) ? evt.which : evt.keyCode
767    if (code == 38 && (is_gecko || is_ie || is_opera || is_webkit)) // up arrow key
768    {
769      /* TODO: Fix up arrow issue in Opera - may be a bug in Opera. See TAB handler comments below. */
770      /* Konqueror cannot use this feature due to keycode conflicts. */
771
772      // Write the previous command in the history.
773      this.historyUp();
774
775      if (evt.returnValue) // IE
776        evt.returnValue = false;
777      if (evt.preventDefault) // DOM
778        evt.preventDefault();
779      return false; // should work in all browsers
780    }
781    else if (code == 40 && (is_gecko || is_ie || is_opera || is_webkit)) // down arrow key
782    {
783      /* Konqueror cannot use this feature due to keycode conflicts. */
784
785      // Write the previous command in the history.
786      this.historyDown();
787
788      if (evt.returnValue) // IE
789        evt.returnValue = false;
790      if (evt.preventDefault) // DOM
791        evt.preventDefault();
792      return false; // should work in all browsers
793    }
794    else if (code == 9) /* TAB key */
795    {
796      // Do nickname completion like on IRC / Unix command line.
797      this.completeNick();
798
799      if (is_opera)
800      {
801        // Fixes Opera's loss of focus after TAB key is pressed.
802        // This is most likely due to a bug in Opera
803        // that executes the default key operation BEFORE the
804        // keydown and keypress event handler.
805        // This is probably the reason for the "up arrow" issue above.
806        //window.setTimeout(function(){evt.target.focus();}, 0);
807        evt.target.onblur = function() { this.focus(); this.onblur = null; };
808      }
809
810      if (evt.returnValue) // IE
811        evt.returnValue = false;
812      if (evt.preventDefault) // DOM
813        evt.preventDefault();
814      return false; // Should work in all browsers.
815    }
816    else
817    {
818      // Allow other key defaults.
819      return true;
820    }
821  },
822  callbackWords_OnKeyup: function(evt)
823  {
824    // Needed for IE since the text box loses selection/caret position on blur
825    this.storeSelectionPos(this.el_words);
826  },
827  callbackWords_OnMouseup: function(evt)
828  {
829    // Needed for IE since the text box loses selection/caret position on blur
830    this.storeSelectionPos(this.el_words);
831  },
832  callbackWords_OnFocus: function(evt)
833  {
834    //    if (this.el_handle && this.el_handle.value == '' && !this.minmax_status)
835    //      this.el_handle.focus();
836
837    // Needed for IE since the text box loses selection/caret position on blur
838    this.setSelection(this.el_words);
839  },
840  callback_OnUnload: function(evt)
841  {
842    /* don't disconnect users when they reload the window
843     * this event doesn't only occurs when the page is closed but also when the page is reloaded */
844    if (pfc_quit_on_closedwindow)
845    {
846      if (!this.isconnected) return false;
847      this.sendRequest('/quit');
848    }
849  },
850
851
852  /**
853   * hide error area and stop blinking fields
854   */
855  clearError: function(ids)
856  {
857    this.el_errors.style.display = 'none';
858    for (var i=0; i<ids.length; i++)
859      this.blink(ids[i].id, 'stop');
860  },
861
862  /**
863   * show error area and assign to it an error message and start the blinking of given fields
864   */
865  setError: function(str, ids)
866  {
867    this.el_errors.innerHTML = str;
868    this.el_errors.style.display = 'block';
869    for (var i=0; i<ids.length; i++)
870      this.blink(ids[i].id, 'start');
871  },
872
873  /**
874   * blink routines used by Error functions
875   */
876  blink: function(id, action)
877  {
878    clearTimeout(this.blinktimeout[id]);
879    if ($(id) == null) return;
880    if (action == 'start')
881    {
882      this.blinktmp[id] = $(id).style.backgroundColor;
883      clearTimeout(this.blinktimeout[id]);
884      this.blinktimeout[id] = setTimeout('pfc.blink(\'' + id + '\',\'loop\')', 500);
885    }
886    if (action == 'stop')
887    {
888      $(id).style.backgroundColor = this.blinktmp[id];
889    }
890    if (action == 'loop')
891    {
892      if (this.blinkloop[id] == 1)
893      {
894        $(id).style.backgroundColor = '#FFDFC0';
895        this.blinkloop[id] = 2;
896      }
897      else
898      {
899        $(id).style.backgroundColor = '#FFFFFF';
900        this.blinkloop[id] = 1;
901      }
902      this.blinktimeout[id] = setTimeout('pfc.blink(\'' + id + '\',\'loop\')', 500);
903    }
904  },
905
906  displayMsg: function( cmd, msg )
907  {
908    this.setError(msg, Array());
909
910    // @todo find a better crossbrowser way to display messages
911/*
912    // get the current selected tab container
913    var tabid     = this.gui.getTabId();
914    var container = this.gui.getChatContentFromTabId(tabid);
915
916    // to fix IE6 display bug
917    // http://sourceforge.net/tracker/index.php?func=detail&aid=1545403&group_id=158880&atid=809601
918    div = document.createElement('div');
919    // div.style.padding = "2px 5px 2px 5px"; // this will clear the screen in IE6
920    div.innerHTML = '<div class="pfc_info pfc_info_'+cmd+'" style="margin:5px">'+msg+'</div>';
921
922    // finaly append this to the message list
923    container.appendChild(div);
924    this.gui.scrollDown(tabid, div);
925*/
926  },
927
928  handleComingRequest: function( cmds )
929  {
930    var msg_html = $H();
931    var max_msgid = $H();
932
933    //alert(cmds.inspect());
934
935    for(var mid = 0; mid < cmds.length ; mid++)
936    {
937      var id          = cmds[mid][0];
938      var date        = cmds[mid][1];
939      var time        = cmds[mid][2];
940      var sender      = cmds[mid][3];
941      var recipientid = cmds[mid][4];
942      var cmd         = cmds[mid][5];
943      var param       = cmds[mid][6];
944      var fromtoday   = cmds[mid][7];
945      var oldmsg      = cmds[mid][8];
946
947      // format and post message
948      var line = '';
949      line += '<div id="pfc_msg_'+recipientid+'_'+id+'" class="pfc_cmd_'+ cmd +' pfc_message';
950      line  += (id % 2 == 0) ? ' pfc_evenmsg' : ' pfc_oddmsg';
951      if (oldmsg == 1) line += ' pfc_oldmsg';
952      line += '">';
953      line += '<span class="pfc_date';
954      if (fromtoday == 1) line += ' pfc_invisible';
955      line += '">'+ date +'</span> ';
956      line += '<span class="pfc_heure">'+ time +'</span> ';
957      if (cmd == 'send')
958      {
959        line += ' <span class="pfc_nick">';
960        line += '&#x2039;';
961        line += '<span ';
962        line += 'onclick="pfc.insert_text(\'' + sender.escapeHTML().replace("'", '\\\'') + ', \',\'\',false)" ';
963        line += 'class="pfc_nickmarker pfc_nick_'+ _to_utf8(sender).md5() +'">';
964        line += sender.escapeHTML();
965        line += '</span>';
966        line += '&#x203A;';
967        line += '</span> ';
968      }
969      if (cmd == 'notice')
970        line += '<span class="pfc_words">* ' + this.parseMessage(param) +'</span> ';
971      else if (cmd == 'me')
972        line += '<span class="pfc_words">* '+ sender.escapeHTML() + ' ' + this.parseMessage(param) +'</span> ';
973      else
974        line += '<span class="pfc_words">'+ this.parseMessage(param) +'</span> ';
975      line += '</div>';
976
977      if (oldmsg == 0)
978        if (cmd == 'send' || cmd == 'me')
979        {
980          // notify the hidden tab a message has been received
981          // don't notify anything if this is old messages
982          var tabid = recipientid;
983          if (this.gui.getTabId() != tabid)
984            this.gui.notifyTab(tabid);
985          // notify the window (change the title)
986          if (!this.detectactivity.isActive() && pfc_notify_window)
987            this.gui.notifyWindow();
988        }
989
990      if (msg_html.get(recipientid) == null)
991        msg_html.set(recipientid, line);
992      else
993        msg_html.set(recipientid, msg_html.get(recipientid) + line);
994
995      // remember the max message id in order to clean old lines
996      if (!max_msgid.get(recipientid)) max_msgid.set(recipientid, 0);
997      if (max_msgid.get(recipientid) < id) max_msgid.set(recipientid, id);
998    }
999
1000    // loop on all recipients and post messages
1001    var keys = msg_html.keys();
1002    for( var i=0; i<keys.length; i++)
1003    {
1004      var recipientid  = keys[i];
1005      var tabid        = recipientid;
1006      // create the tab if it doesn't exists yet
1007      var recipientdiv = this.gui.getChatContentFromTabId(tabid);
1008
1009      // create a dummy div to avoid konqueror bug when setting nickmarkers
1010      var m = document.createElement('div');  // do not setup a inline element (ex: span) because the element height will be wrong on FF2 -> scrollDown(..) will be broken
1011      m.innerHTML = msg_html.get(recipientid);
1012      this.colorizeNicks(m);
1013      this.refresh_clock(m);
1014      // finaly append this to the message list
1015      recipientdiv.appendChild(m);
1016      this.gui.scrollDown(tabid, m);
1017
1018      // delete the old messages from the client (save some memory)
1019      var limit_msgid = max_msgid.get(recipientid) - pfc_max_displayed_lines;
1020      var elt = $('pfc_msg_'+recipientid+'_'+limit_msgid);
1021      while (elt)
1022      {
1023        // delete this element to save browser memory
1024        if(elt.parentNode)
1025          elt.parentNode.removeChild(elt);
1026        else if(elt.parentElement)  // older IE browsers (<6.0) may not support parentNode
1027          elt.parentElement.removeChild(elt);
1028        else  // if all else fails
1029          elt.innerHTML = '';
1030        limit_msgid--;
1031        elt = $('pfc_msg_'+recipientid+'_'+limit_msgid);
1032      }
1033    }
1034
1035  },
1036
1037  calcDelay: function()
1038  {
1039    var lastact = new Date().getTime() - this.last_activity_time;
1040    var dlist = this.refresh_delay_steps.slice();
1041    var delay = dlist.shift();
1042    if (this.refresh_delay > delay) delay = this.refresh_delay;
1043    var limit;
1044    while (typeof (limit = dlist.shift()) != "undefined")
1045    {
1046      var d = dlist.shift();
1047      if (d < delay) continue;
1048      if (lastact > limit) delay = d;
1049    }
1050    return delay;
1051  },
1052
1053  /**
1054   * Call the ajax request function
1055   * Will query the server
1056   */
1057  sendRequest: function(cmd, recipientid)
1058  {
1059    // do not send another ajax requests if the last one is not yet finished
1060    if (cmd == '/update' && this.pfc_ajax_connected) return;
1061
1062    var delay = this.calcDelay();
1063
1064    if (cmd != "/update")
1065    {
1066      // setup a new timeout to update the chat in 5 seconds (in refresh_delay more exactly)
1067      clearTimeout(this.timeout);
1068      this.timeout = setTimeout('pfc.updateChat(true)', delay);
1069      this.timeout_time = new Date().getTime() + delay;
1070
1071      if (pfc_debug)
1072        trace('sendRequest: '+cmd);
1073    }
1074
1075    // prepare the command string
1076    var rx = new RegExp('(^\/[^ ]+) *(.*)','ig');
1077    if (!recipientid) recipientid = this.gui.getTabId();
1078    cmd = cmd.replace(rx, '$1 '+this.clientid+' '+(recipientid==''?'0':recipientid)+' $2');
1079
1080    // send the real ajax request
1081    var url = pfc_server_script_url;
1082    new Ajax.Request(url, {
1083      method: 'post',
1084      parameters: {'pfc_ajax':1, 'f':'handleRequest', 'cmd': cmd },
1085      onCreate: function(transport) {
1086        this.pfc_ajax_connected = true;
1087        // request time counter used by ping indicator
1088        this.last_request_time = new Date().getTime();
1089      }.bind(this),
1090      onSuccess: function(transport) {
1091        if (!transport.status) return; // fix strange behavior on KHTML
1092
1093        // request time counter used by ping indicator
1094        this.last_response_time = new Date().getTime();
1095        // evaluate the javascript response
1096        eval( transport.responseText );
1097      }.bind(this),
1098      onComplete: function(transport) {
1099        this.pfc_ajax_connected = false;
1100
1101        // calculate the ping and display it
1102        this.ping = Math.abs(this.last_response_time - this.last_request_time);
1103        if ($('pfc_ping')) $('pfc_ping').innerHTML = this.ping+'ms'+' ['+parseInt(this.calcDelay() / 1000)+'s]';
1104      }.bind(this)
1105    });
1106  },
1107
1108  /**
1109   * update function to poll the server each 'refresh_delay' time
1110   */
1111  updateChat: function(start)
1112  {
1113    clearTimeout(this.timeout);
1114    if (start)
1115    {
1116      this.sendRequest('/update');
1117
1118      // setup the next update
1119      var delay = this.calcDelay();
1120      this.timeout = setTimeout('pfc.updateChat(true)', delay);
1121      this.timeout_time = new Date().getTime() + delay;
1122    }
1123  },
1124
1125  /**
1126   * Stores the caret/selection position for IE 6.x and 7.x
1127   * Returns true if text range start and end values were updated.
1128   * Code based on: http://www.bazon.net/mishoo/articles.epl?art_id=1292
1129   */
1130  storeSelectionPos: function(obj)
1131  {
1132    // We don't need to store the start and end positions if the browser
1133    // supports the Gecko selection model. However, these values may be
1134    // useful for debugging. Also, Opera recognizes Gecko and IE range
1135    // commands, so we need to ensure Opera only uses the Gecko model.
1136    /* WARNING: Do not use this for textareas. They require a more
1137                complex algorithm. */
1138    if (obj.setSelectionRange)
1139    {
1140      obj.selStart = obj.selectionStart;
1141      obj.selEnd   = obj.selectionEnd;
1142
1143      return true;
1144    }
1145
1146    // IE
1147    else if (obj.createTextRange && document.selection)
1148    {
1149      // Determine current selection start position.
1150      var range = document.selection.createRange();
1151      var isCollapsed = range.compareEndPoints("StartToEnd", range) == 0;
1152      if (!isCollapsed)
1153        range.collapse(true);
1154      var b = range.getBookmark();
1155      obj.selStart = b.charCodeAt(2) - b.charCodeAt(0) - 1;
1156
1157      // Determine current selection end position.
1158      range = document.selection.createRange();
1159      isCollapsed = range.compareEndPoints("StartToEnd", range) == 0;
1160      if (!isCollapsed)
1161        range.collapse(false);
1162      b = range.getBookmark();
1163      obj.selEnd = b.charCodeAt(2) - b.charCodeAt(0) - 1;
1164
1165      return true;
1166    }
1167
1168    // Browser does not support selection range processing.
1169    else
1170      return false;
1171  },
1172
1173  /**
1174   * Sets the selection/caret in the object based on the
1175   * object's selStart and selEnd parameters.
1176   * This should only be needed for IE only.
1177   */
1178  setSelection: function(obj)
1179  {
1180    // This part of the function is included to prevent
1181    // Opera from executing the IE portion.
1182    /* WARNING: Do not attempt to use this function as
1183       a wrapper for the Gekco based setSelectionRange.
1184       It causes problems in Opera when executed from
1185       the event trigger onFocus. */
1186    if (obj.setSelectionRange)
1187    {
1188      return null;
1189    }
1190    // IE
1191    else if (obj.createTextRange)
1192    {
1193      var range = obj.createTextRange();
1194      range.collapse(true);
1195      range.moveStart("character", obj.selStart);
1196      range.moveEnd("character", obj.selEnd - obj.selStart);
1197      range.select();
1198
1199      return range;
1200    }
1201    // Browser does not support selection range processing.
1202    else
1203      return null;
1204  },
1205
1206  /**
1207   * insert a smiley
1208   */
1209  insertSmiley: function(smiley)
1210  {
1211    var w = this.el_words;
1212
1213    if (w.setSelectionRange)
1214    {
1215      // Gecko
1216      var s = w.selectionStart;
1217      var e = w.selectionEnd;
1218      w.value = w.value.substring(0, s) + smiley + w.value.substr(e);
1219      w.setSelectionRange(s + smiley.length, s + smiley.length);
1220      w.focus();
1221    }
1222    else if (w.createTextRange)
1223    {
1224      // IE
1225      w.focus();
1226
1227      // Get range based on stored values.
1228      var range = this.setSelection(w);
1229
1230      range.text = smiley;
1231
1232      // Move caret position to end of smiley and collapse selection, if any.
1233      // Check if internally kept values for selection are initialized.
1234      w.selStart = (w.selStart) ? w.selStart + smiley.length : smiley.length;
1235      w.selEnd   = w.selStart;
1236    }
1237    else
1238    {
1239      // Unsupported browsers get smiley at end of string like old times.
1240      w.value += smiley;
1241      w.focus();
1242    }
1243  },
1244
1245  updateNickBox: function(nickid)
1246  {
1247    // @todo optimize this function because it is called lot of times so it could cause CPU consuming on client side
1248    var chanids = this.chanmeta.keys();
1249    for(var i = 0; chanids.length > i; i++)
1250    {
1251      this.updateNickListBox(chanids[i]);
1252    }
1253  },
1254
1255  /**
1256   * fill the nickname list with connected nicknames
1257   */
1258  updateNickListBox: function(chanid)
1259  {
1260    var className = (!is_ie7 && !is_ie6) ? 'class' : 'className';
1261
1262    var nickidlst = this.getChanMeta(chanid,'users').get('nickid');
1263    var nickdiv = this.gui.getOnlineContentFromTabId(chanid);
1264    var ul = document.createElement('ul');
1265    ul.setAttribute(className, 'pfc_nicklist');
1266    for (var i=0; i<nickidlst.length; i++)
1267    {
1268      var nickid = nickidlst[i];
1269      var li = this.buildNickItem(nickid);
1270      li.setAttribute(className, 'pfc_nickitem_'+nickid);
1271      ul.appendChild(li);
1272    }
1273    var fc = nickdiv.firstChild;
1274    if (fc)
1275      nickdiv.replaceChild(ul,fc);
1276    else
1277      nickdiv.appendChild(ul,fc);
1278    this.colorizeNicks(nickdiv);
1279  },
1280
1281  getNickWhoisBox: function(nickid)
1282  {
1283    if (!this.nickwhoisbox.get(nickid))
1284      this.updateNickWhoisBox(nickid);
1285    return this.nickwhoisbox.get(nickid);
1286  },
1287
1288  updateNickWhoisBox_ignored_field: function(k)
1289  {
1290      return ( k == 'nickid' ||
1291               k == 'nick' || // useless because it is displayed in the box title
1292               k == 'isadmin' || // useless because of the gold shield icon
1293               k == 'floodtime' ||
1294               k == 'flood_nbmsg' ||
1295               k == 'flood_nbchar'
1296               );
1297  },
1298
1299  updateNickWhoisBox_append_html: function(nickid, div)
1300  {
1301      // this methode can be overloaded to append customized data to the whoisbox
1302  },
1303
1304  updateNickWhoisBox_prepend_html: function(nickid, div)
1305  {
1306      // this methode can be overloaded to prepend customized data to the whoisbox
1307  },
1308
1309  updateNickWhoisBox: function(nickid)
1310  {
1311    var className = (!is_ie7 && !is_ie6) ? 'class' : 'className';
1312
1313    var usermeta = this.getAllUserMeta(nickid);
1314    var div  = document.createElement('div');
1315    div.setAttribute(className, 'pfc_nickwhois');
1316
1317    var p = document.createElement('p');
1318    p.setAttribute(className, 'pfc_nickwhois_header');
1319    div.appendChild(p);
1320
1321    // add the close button
1322    var img = document.createElement('img');
1323    img.setAttribute(className, 'pfc_nickwhois_close');
1324    img.pfc_parent = div;
1325    img.onclick = function(evt){
1326      this.pfc_parent.style.display = 'none';
1327      return false;
1328    }
1329    img.setAttribute('src', this.res.getFileUrl('images/close-whoisbox.gif'));
1330    img.alt = this.res.getLabel('Close');
1331    p.appendChild(img);
1332    p.appendChild(document.createTextNode(usermeta.get('nick'))); // append the nickname text in the title
1333
1334    this.updateNickWhoisBox_prepend_html(nickid,div);
1335
1336    // add the whois information table
1337    var table = document.createElement('table');
1338    var tbody = document.createElement('tbody');
1339    table.appendChild(tbody);
1340    var um_keys = usermeta.keys();
1341    var msg = '';
1342    for (var i=0; i<um_keys.length; i++)
1343    {
1344      var k = um_keys[i];
1345      var v = usermeta.get(k);
1346      if (v && !this.updateNickWhoisBox_ignored_field(k))
1347      {
1348        var tr = document.createElement('tr');
1349        if (pfc_nickmeta_key_to_hide.indexOf(k) != -1)
1350        {
1351          var td2 = document.createElement('td');
1352          td2.setAttribute(className, 'pfc_nickwhois_c2');
1353          td2.setAttribute('colspan', 2);
1354          td2.innerHTML = v;
1355          tr.appendChild(td2);
1356        }
1357        else
1358        {
1359          var td1 = document.createElement('td');
1360          td1.setAttribute(className, 'pfc_nickwhois_c1');
1361          var td2 = document.createElement('td');
1362          td2.setAttribute(className, 'pfc_nickwhois_c2');
1363          td1.innerHTML = k;
1364          td2.innerHTML = v;
1365          tr.appendChild(td1);
1366          tr.appendChild(td2);
1367        }
1368        tbody.appendChild(tr);
1369      }
1370    }
1371    div.appendChild(table);
1372
1373    this.updateNickWhoisBox_append_html(nickid,div);
1374
1375    // add the privmsg link (do not add it if the nick is yours)
1376    if (pfc.getUserMeta(nickid,'nick') != this.nickname)
1377    {
1378      var p = document.createElement('p');
1379      p.setAttribute(className, 'pfc_nickwhois_pv');
1380      var a = document.createElement('a');
1381      a.setAttribute('href', '');
1382      a.pfc_nickid = nickid;
1383      a.pfc_parent = div;
1384      a.onclick = function(evt){
1385        var nick = pfc.getUserMeta(this.pfc_nickid,'nick');
1386        pfc.sendRequest('/privmsg "'+nick+'"');
1387        this.pfc_parent.style.display = 'none';
1388        return false;
1389      }
1390      var img = document.createElement('img');
1391      img.setAttribute('src', this.res.getFileUrl('images/openpv.gif'));
1392      img.alt = document.createTextNode(this.res.getLabel('Private message'));
1393      a.appendChild(img);
1394      a.appendChild(document.createTextNode(this.res.getLabel('Private message')));
1395      p.appendChild(a);
1396      div.appendChild(p);
1397    }
1398
1399    this.nickwhoisbox.set(nickid, div);
1400  },
1401
1402  buildNickItem_create_image: function(nickid)
1403  {
1404      var className = (!is_ie7 && !is_ie6) ? 'class' : 'className';
1405      var isadmin = this.getUserMeta(nickid, 'isadmin');
1406      var img = document.createElement('img');
1407      if (isadmin)
1408        img.setAttribute('src', this.res.getFileUrl('images/user-admin.gif'));
1409      else
1410        img.setAttribute('src', this.res.getFileUrl('images/user.gif'));
1411      img.style.marginRight = '5px';
1412      img.setAttribute(className, 'pfc_nickbutton');
1413      return img;
1414  },
1415
1416  buildNickItem_modify_nick_style: function(nickid, span)
1417  {
1418      // this method can be overloaded to change the nick style (color, font ...)
1419      // example: span.style.color = 'red';
1420  },
1421
1422  buildNickItem: function(nickid)
1423  {
1424    var className = (!is_ie7 && !is_ie6) ? 'class' : 'className';
1425
1426    var nick = this.getUserMeta(nickid, 'nick');
1427    var isadmin = this.getUserMeta(nickid, 'isadmin');
1428    if (isadmin == '') isadmin = false;
1429
1430    var li = document.createElement('li');
1431
1432    var a = document.createElement('a');
1433    a.setAttribute('href','#');
1434    a.pfc_nick   = nick;
1435    a.pfc_nickid = nickid;
1436    a.onclick = function(evt){
1437      var d = pfc.getNickWhoisBox(this.pfc_nickid);
1438      document.body.appendChild(d);
1439      d.style.display = 'block';
1440      d.style.zIndex = '400';
1441      d.style.position = 'absolute';
1442      d.style.left = (mousePosX(evt)-7)+'px';
1443      d.style.top  = (mousePosY(evt)-7)+'px';
1444      return false;
1445    }
1446    li.appendChild(a);
1447
1448    var img = this.buildNickItem_create_image(nickid);
1449    if (img) a.appendChild(img);
1450
1451    // nobr is not xhtml valid but it's a workeround
1452    // for IE which doesn't support 'white-space: pre' css rule
1453    var nobr = document.createElement('nobr');
1454    var span = document.createElement('span');
1455    span.setAttribute(className, 'pfc_nickmarker pfc_nick_'+nickid);
1456    span.innerHTML = nick.escapeHTML();
1457    this.buildNickItem_modify_nick_style(nickid, span);
1458    nobr.appendChild(span);
1459    a.appendChild(nobr);
1460
1461    return li;
1462  },
1463
1464  /**
1465   * clear the nickname list
1466   */
1467  clearNickList: function()
1468  {
1469    /*
1470    var nickdiv = this.el_online;
1471    var fc = nickdiv.firstChild;
1472    if (fc) nickdiv.removeChild(fc);
1473    */
1474  },
1475
1476
1477  /**
1478   * clear the message list history
1479   */
1480  clearMessages: function()
1481  {
1482    //var msgdiv = $('pfc_chat');
1483    //msgdiv.innerHTML = '';
1484  },
1485
1486  /**
1487   * parse the message
1488   */
1489  parseMessage: function(msg)
1490  {
1491    var rx = null;
1492/*
1493    // parse urls
1494    var rx_url = new RegExp('(^|[^\\"])([a-z]+\:\/\/[a-z0-9.\\~\\/\\?\\=\\&\\-\\_\\#:;%,@]*[a-z0-9\\/\\?\\=\\&\\-\\_\\#])([^\\"]|$)','ig');
1495    var ttt = msg.split(rx_url);
1496    if (ttt.length > 1 &&
1497        !navigator.appName.match("Explorer|Konqueror") &&
1498        !navigator.appVersion.match("KHTML"))
1499    {
1500      msg = '';
1501      for( var i = 0; i<ttt.length; i++)
1502      {
1503        var offset = (ttt[i].length - 7) / 2;
1504        var delta = (ttt[i].length - 7 - 60);
1505        var range1 = 7+offset-delta;
1506        var range2 = 7+offset+delta;
1507        if (ttt[i].match(rx_url))
1508        {
1509          msg = msg + '<a href="' + ttt[i] + '"';
1510          if (pfc_openlinknewwindow)
1511            msg = msg + ' onclick="window.open(this.href,\'_blank\');return false;"';
1512          msg = msg + '>' + (delta>0 ? ttt[i].substring(7,range1)+ ' ... ' + ttt[i].substring(range2,ttt[i].length) :  ttt[i]) + '</a>';
1513        }
1514        else
1515        {
1516          msg = msg + ttt[i];
1517        }
1518      }
1519    }
1520    else
1521    {
1522      // fallback for IE6/Konqueror which do not support split with regexp
1523      replace = '$1<a href="$2"';
1524      if (pfc_openlinknewwindow)
1525        replace = replace + ' onclick="window.open(this.href,\'_blank\');return false;"';
1526      replace = replace + '>$2</a>$3';
1527      msg = msg.replace(rx_url, replace);
1528    }
1529*/
1530
1531    // Remove auto-linked entries.
1532    if ( false )
1533    {
1534      rx = new RegExp('<a href="mailto:(.*?)".*?>.*?<\/a>','ig');
1535      msg = msg.replace(rx, '$1');
1536      rx = new RegExp('<a href="(.*?)".*?>.*?<\/a>','ig');
1537      msg = msg.replace(rx, '$1');
1538    }
1539
1540    // Replace double spaces outside of tags by "&nbsp; " entity.
1541    rx = new RegExp(' (?= )(?![^<]*>)','g');
1542    msg = msg.replace(rx, '&nbsp;');
1543
1544    // try to parse bbcode
1545    rx = new RegExp('\\[b\\](.+?)\\[\/b\\]','ig');
1546    msg = msg.replace(rx, '<span style="font-weight: bold">$1</span>');
1547    rx = new RegExp('\\[i\\](.+?)\\[\/i\\]','ig');
1548    msg = msg.replace(rx, '<span style="font-style: italic">$1</span>');
1549    rx = new RegExp('\\[u\\](.+?)\\[\/u\\]','ig');
1550    msg = msg.replace(rx, '<span style="text-decoration: underline">$1</span>');
1551    rx = new RegExp('\\[s\\](.+?)\\[\/s\\]','ig');
1552    msg = msg.replace(rx, '<span style="text-decoration: line-through">$1</span>');
1553    //    rx = new RegExp('\\[pre\\](.+?)\\[\/pre\\]','ig');
1554    // msg = msg.replace(rx, '<pre>$1</pre>');
1555/*
1556    rx = new RegExp('\\[email\\]([A-z0-9][\\w.-]*@[A-z0-9][\\w\\-\\.]+\\.[A-z0-9]{2,6})\\[\/email\\]','ig');
1557    msg = msg.replace(rx, '<a href="mailto: $1">$1</a>');
1558    rx = new RegExp('\\[email=([A-z0-9][\\w.-]*@[A-z0-9][\\w\\-\\.]+\\.[A-z0-9]{2,6})\\](.+?)\\[\/email\\]','ig');
1559    msg = msg.replace(rx, '<a href="mailto: $1">$2</a>');
1560*/
1561    rx = new RegExp('\\[color=([a-zA-Z]+|\\#?[0-9a-fA-F]{6}|\\#?[0-9a-fA-F]{3})\\](.+?)\\[\/color\\]','ig');
1562    msg = msg.replace(rx, '<span style="color: $1">$2</span>');
1563    // parse bbcode colors twice because the current_text_color is a bbcolor
1564    // so it's possible to have a bbcode color imbrication
1565    rx = new RegExp('\\[color=([a-zA-Z]+|\\#?[0-9a-fA-F]{6}|\\#?[0-9a-fA-F]{3})\\](.+?)\\[\/color\\]','ig');
1566    msg = msg.replace(rx, '<span style="color: $1">$2</span>');
1567
1568    // try to parse smileys
1569    var smileys = this.res.getSmileyHash();
1570    var sl = this.res.getSmileyKeys(); // Keys should be sorted by length from pfc.gui.loadSmileyBox()
1571    for(var i = 0; i < sl.length; i++)
1572    {
1573      // We don't want to replace smiley strings inside of tags.
1574      // Use negative lookahead to search for end of tag.
1575      rx = new RegExp(RegExp.escape(sl[i]) + '(?![^<]*>)','g');
1576      msg = msg.replace(rx, '<img src="'+ smileys.get(sl[i]) +'" alt="' + sl[i] + '" title="' + sl[i] + '" />');
1577    }
1578
1579    // try to parse nickname for highlighting
1580    rx = new RegExp('(^|[ :,;])'+RegExp.escape(this.nickname)+'([ :,;]|$)','gi');
1581    msg = msg.replace(rx, '$1<strong>'+ this.nickname +'</strong>$2');
1582
1583    // this piece of code is replaced by the word-wrap CSS3 rule.
1584    /*
1585    // don't allow to post words bigger than 65 caracteres
1586    // doesn't work with crappy IE and Konqueror !
1587    rx = new RegExp('([^ \\:\\<\\>\\/\\&\\;]{60})','ig');
1588    var ttt = msg.split(rx);
1589    if (ttt.length > 1 &&
1590        !navigator.appName.match("Explorer|Konqueror") &&
1591        !navigator.appVersion.match("KHTML"))
1592    {
1593      msg = '';
1594      for( var i = 0; i<ttt.length; i++)
1595      {
1596        msg = msg + ttt[i] + ' ';
1597      }
1598    }
1599    */
1600    return msg;
1601  },
1602
1603  /**
1604   * apply nicknames color to the root childs
1605   */
1606  colorizeNicks: function(root)
1607  {
1608    if (this.nickmarker)
1609    {
1610      var nicklist = this.getElementsByClassName(root, 'pfc_nickmarker', '');
1611      for(var i = 0; i < nicklist.length; i++)
1612      {
1613        var cur_nick = nicklist[i].innerHTML;
1614        var cur_color = this.getAndAssignNickColor(cur_nick);
1615        nicklist[i].style.color = cur_color;
1616      }
1617    }
1618  },
1619
1620  /**
1621   * Initialize the color array used to colirize the nicknames
1622   */
1623  reloadColorList: function()
1624  {
1625    this.colorlist = $A(pfc_nickname_color_list);
1626  },
1627
1628
1629  /**
1630   * get the corresponding nickname color
1631   */
1632  getAndAssignNickColor: function(nick)
1633  {
1634    /* check the nickname is colorized or not */
1635    var already_colorized = false;
1636    var nc = '';
1637    for(var j = 0; j < this.nickcolor.length && !already_colorized; j++)
1638    {
1639      if (this.nickcolor[j][0] == nick)
1640      {
1641        already_colorized = true;
1642        nc = this.nickcolor[j][1];
1643      }
1644    }
1645    if (!already_colorized)
1646    {
1647      /* reload the color stack if it's empty */
1648      if (this.colorlist.length == 0) this.reloadColorList();
1649      /* take the next color from the list and colorize this nickname */
1650      var cid = Math.round(Math.random()*(this.colorlist.length-1));
1651      nc = this.colorlist[cid];
1652      this.colorlist.splice(cid,1);
1653      this.nickcolor.push(new Array(nick, nc));
1654    }
1655
1656    return nc;
1657  },
1658
1659
1660  /**
1661   * Colorize with 'color' all the nicknames found as a 'root' child
1662   */
1663  applyNickColor: function(root, nick, color)
1664  {
1665
1666    var nicktochange = this.getElementsByClassName(root, 'pfc_nick_'+ _to_utf8(nick).md5(), '');
1667    for(var i = 0; nicktochange.length > i; i++)
1668      nicktochange[i].style.color = color;
1669
1670  },
1671
1672  /**
1673   * Returns a list of elements which have a clsName class
1674   */
1675  getElementsByClassName: function( root, clsName, clsIgnore )
1676  {
1677    var i, matches = new Array();
1678    var els = root.getElementsByTagName('*');
1679    var rx1 = new RegExp('.*'+clsName+'.*');
1680    var rx2 = new RegExp('.*'+clsIgnore+'.*');
1681    for(i=0; i<els.length; i++) {
1682      if(els.item(i).className.match(rx1) &&
1683         (clsIgnore == '' || !els.item(i).className.match(rx2)) )
1684      {
1685        matches.push(els.item(i));
1686      }
1687    }
1688    return matches;
1689  },
1690
1691  showClass: function(root, clsName, clsIgnore, show)
1692  {
1693    var elts = this.getElementsByClassName(root, clsName, clsIgnore);
1694    for(var i = 0; elts.length > i; i++)
1695    if (show)
1696      elts[i].style.display = 'inline';
1697    else
1698      elts[i].style.display = 'none';
1699  },
1700
1701
1702  /**
1703   * Nickname marker show/hide
1704   */
1705  nickmarker_swap: function()
1706  {
1707    if (this.nickmarker) {
1708      this.nickmarker = false;
1709    } else {
1710      this.nickmarker = true;
1711    }
1712    this.refresh_nickmarker()
1713    setCookie('pfc_nickmarker', this.nickmarker);
1714  },
1715  refresh_nickmarker: function(root)
1716  {
1717    var nickmarker_icon = $('pfc_nickmarker');
1718    if (!root) root = $('pfc_channels_content');
1719    if (this.nickmarker)
1720    {
1721      nickmarker_icon.src   = this.res.getFileUrl('images/color-on.gif');
1722      nickmarker_icon.alt   = this.res.getLabel("Hide nickname marker");
1723      nickmarker_icon.title = nickmarker_icon.alt;
1724      this.colorizeNicks(root);
1725    }
1726    else
1727    {
1728      nickmarker_icon.src   = this.res.getFileUrl('images/color-off.gif');
1729      nickmarker_icon.alt   = this.res.getLabel("Show nickname marker");
1730      nickmarker_icon.title = nickmarker_icon.alt;
1731      var elts = this.getElementsByClassName(root, 'pfc_nickmarker', '');
1732      for(var i = 0; elts.length > i; i++)
1733      {
1734        // this is not supported in konqueror =>>>  elts[i].removeAttribute('style');
1735        elts[i].style.color = '';
1736      }
1737    }
1738  },
1739
1740
1741  /**
1742   * Date/Hour show/hide
1743   */
1744  clock_swap: function()
1745  {
1746    if (this.clock) {
1747      this.clock = false;
1748    } else {
1749      this.clock = true;
1750    }
1751    this.refresh_clock();
1752    setCookie('pfc_clock', this.clock);
1753  },
1754  refresh_clock: function( root )
1755  {
1756    var clock_icon = $('pfc_clock');
1757    if (!root) root = $('pfc_channels_content');
1758    if (this.clock)
1759    {
1760      clock_icon.src   = this.res.getFileUrl('images/clock-on.gif');
1761      clock_icon.alt   = this.res.getLabel('Hide dates and hours');
1762      clock_icon.title = clock_icon.alt;
1763      this.showClass(root, 'pfc_date', 'pfc_invisible', true);
1764      this.showClass(root, 'pfc_heure', 'pfc_invisible', true);
1765    }
1766    else
1767    {
1768      clock_icon.src   = this.res.getFileUrl('images/clock-off.gif');
1769      clock_icon.alt   = this.res.getLabel('Show dates and hours');
1770      clock_icon.title = clock_icon.alt;
1771      this.showClass(root, 'pfc_date', 'pfc_invisible', false);
1772      this.showClass(root, 'pfc_heure', 'pfc_invisible', false);
1773    }
1774    // browser automaticaly scroll up misteriously when showing the dates
1775    //    $('pfc_chat').scrollTop += 30;
1776  },
1777
1778  /**
1779   * Sound button
1780   */
1781  sound_swap: function()
1782  {
1783    if (this.issoundenable) {
1784      this.issoundenable = false;
1785    } else {
1786      this.issoundenable = true;
1787    }
1788    this.refresh_sound();
1789    setCookie('pfc_issoundenable', this.issoundenable);
1790  },
1791  refresh_sound: function( root )
1792  {
1793    var snd_icon = $('pfc_sound');
1794    if (this.issoundenable)
1795    {
1796      snd_icon.src   = this.res.getFileUrl('images/sound-on.gif');
1797      snd_icon.alt   = this.res.getLabel('Disable sound notifications');
1798      snd_icon.title = snd_icon.alt;
1799    }
1800    else
1801    {
1802      snd_icon.src   = this.res.getFileUrl('images/sound-off.gif');
1803      snd_icon.alt   = this.res.getLabel('Enable sound notifications');
1804      snd_icon.title = snd_icon.alt;
1805    }
1806  },
1807
1808  /**
1809   * Connect/disconnect button
1810   */
1811  connect_disconnect: function()
1812  {
1813    if (this.isconnected)
1814      this.sendRequest('/quit');
1815    else
1816    {
1817      if (this.nickname == '')
1818        this.askNick();
1819      else
1820        this.sendRequest('/connect "'+this.nickname+'"');
1821    }
1822  },
1823  refresh_loginlogout: function()
1824  {
1825    var loginlogout_icon = $('pfc_loginlogout');
1826    if (this.isconnected)
1827    {
1828      loginlogout_icon.src   = this.res.getFileUrl('images/logout.gif');
1829      loginlogout_icon.alt   = this.res.getLabel('Disconnect');
1830      loginlogout_icon.title = loginlogout_icon.alt;
1831    }
1832    else
1833    {
1834      this.clearMessages();
1835      this.clearNickList();
1836      loginlogout_icon.src   = this.res.getFileUrl('images/login.gif');
1837      loginlogout_icon.alt   = this.res.getLabel('Connect');
1838      loginlogout_icon.title = loginlogout_icon.alt;
1839    }
1840  },
1841
1842
1843  /**
1844   * Minimize/Maximized the chat zone
1845   */
1846  swap_minimize_maximize: function()
1847  {
1848    if (this.minmax_status) {
1849      this.minmax_status = false;
1850    } else {
1851      this.minmax_status = true;
1852    }
1853    setCookie('pfc_minmax_status', this.minmax_status);
1854    this.refresh_minimize_maximize();
1855  },
1856  refresh_minimize_maximize: function()
1857  {
1858    var content = $('pfc_content_expandable');
1859    var btn     = $('pfc_minmax');
1860    if (this.minmax_status)
1861    {
1862      btn.src = this.res.getFileUrl('images/maximize.gif');
1863      btn.alt = this.res.getLabel('Magnify');
1864      btn.title = btn.alt;
1865      content.style.display = 'none';
1866    }
1867    else
1868    {
1869      btn.src = this.res.getFileUrl('images/minimize.gif');
1870      btn.alt = this.res.getLabel('Cut down');
1871      btn.title = btn.alt;
1872      content.style.display = 'block';
1873    }
1874  },
1875
1876
1877  /**
1878   * BBcode ToolBar
1879   */
1880  insert_text: function(open, close, promptifselempty)
1881  {
1882    var msgfield = $('pfc_words');
1883
1884    var pfcp = this.getPrompt();
1885    pfcp.msgfield = msgfield;
1886    pfcp.open     = open;
1887    pfcp.close    = close;
1888    pfcp.promptifselempty = promptifselempty;
1889    pfcp.callback = this.insert_text_callback;
1890
1891    // Gecko
1892    /* Always check for Gecko selection processing commands
1893       first. This is needed for Opera. */
1894    if (msgfield.selectionStart || msgfield.selectionStart == '0')
1895    {
1896      var startPos = msgfield.selectionStart;
1897      var endPos   = msgfield.selectionEnd;
1898
1899      var text = msgfield.value.substring(startPos, endPos);
1900      if (startPos == endPos && promptifselempty)
1901      {
1902        pfcp.prompt(this.res.getLabel('Enter the text to format'), '');
1903        pfcp.focus();
1904      }
1905      else
1906        this.insert_text_callback(text, pfcp);
1907    }
1908
1909    // IE
1910    else if (document.selection && document.selection.createRange)
1911    {
1912      msgfield.focus();
1913
1914      // Get selection range.
1915      pfcp.range = this.setSelection(msgfield);
1916      var text = pfcp.range.text;
1917      if (text == "" && promptifselempty)
1918      {
1919        pfcp.prompt(this.res.getLabel('Enter the text to format'), '');
1920        pfcp.focus();
1921      }
1922      else
1923        this.insert_text_callback(text, pfcp);
1924    }
1925
1926    // Fallback support for other browsers
1927    else
1928    {
1929      pfcp.prompt(this.res.getLabel('Enter the text to format'), '');
1930      pfcp.focus();
1931    }
1932    return;
1933  },
1934  insert_text_callback: function(text, pfcp)
1935  {
1936    var open             = pfcp.open;
1937    var close            = pfcp.close;
1938    var promptifselempty = pfcp.promptifselempty;
1939    var msgfield         = pfcp.msgfield;
1940    var range            = pfcp.range;
1941
1942    // Gecko
1943    /* Always check for Gecko selection processing commands
1944       first. This is needed for Opera. */
1945    if (msgfield.selectionStart || msgfield.selectionStart == '0')
1946    {
1947      var startPos = msgfield.selectionStart;
1948      var endPos   = msgfield.selectionEnd;
1949
1950      var extralength = 0;
1951      if (startPos == endPos && promptifselempty)
1952      {
1953        if (text == null) text = "";
1954        extralength = text.length;
1955      }
1956      if (text.length > 0 || !promptifselempty)
1957      {
1958        msgfield.value = msgfield.value.substring(0, startPos) + open + text + close + msgfield.value.substring(endPos, msgfield.value.length);
1959        var caretPos = endPos + open.length + extralength + close.length;
1960        msgfield.setSelectionRange(caretPos, caretPos);
1961        msgfield.focus();
1962      }
1963    }
1964    // IE
1965    else if (document.selection && document.selection.createRange)
1966    {
1967      if (text == null) text = "";
1968      if (text.length > 0 || !promptifselempty)
1969      {
1970        msgfield.focus();
1971
1972        range.text = open + text + close;
1973
1974        // Increment caret position.
1975        // Check if internally kept values for selection are initialized.
1976        msgfield.selStart = (msgfield.selStart) ? msgfield.selStart + open.length + text.length + close.length : open.length + text.length + close.length;
1977        msgfield.selEnd   = msgfield.selStart;
1978
1979        msgfield.focus();
1980      }
1981    }
1982    // Fallback support for other browsers
1983    else
1984    {
1985      if (text == null) text = "";
1986      if (text.length > 0 || !promptifselempty)
1987      {
1988        msgfield.value += open + text + close;
1989        msgfield.focus();
1990      }
1991    }
1992  },
1993
1994  /**
1995   * Minimize/Maximize none/inline
1996   */
1997  minimize_maximize: function(idname, type)
1998  {
1999    var element = $(idname);
2000    if(element.style)
2001    {
2002      if(element.style.display == type )
2003      {
2004        element.style.display = 'none';
2005      }
2006      else
2007      {
2008        element.style.display = type;
2009      }
2010    }
2011  },
2012
2013  switch_text_color: function(color)
2014  {
2015    /* clear any existing borders on the color buttons */
2016    var colorbtn = this.getElementsByClassName($('pfc_colorlist'), 'pfc_color', '');
2017    for(var i = 0; colorbtn.length > i; i++)
2018    {
2019      colorbtn[i].style.border = 'none';
2020      colorbtn[i].style.padding = '0';
2021    }
2022
2023    /* assign the new border style to the selected button */
2024    this.current_text_color = color;
2025    setCookie('pfc_current_text_color', this.current_text_color);
2026    var idname = 'pfc_color_' + color;
2027    $(idname).style.border = '1px solid #555';
2028    $(idname).style.padding = '1px';
2029
2030    // assigne the new color to the input text box
2031    this.el_words.style.color = '#'+color;
2032    this.el_words.focus();
2033  },
2034
2035  /**
2036   * Smiley show/hide
2037   */
2038  showHideSmileys: function()
2039  {
2040    if (this.showsmileys)
2041    {
2042      this.showsmileys = false;
2043    }
2044    else
2045    {
2046      this.showsmileys = true;
2047    }
2048    setCookie('pfc_showsmileys', this.showsmileys);
2049    this.refresh_Smileys();
2050  },
2051  refresh_Smileys: function()
2052  {
2053    // first of all : show/hide the smiley box
2054    var content = $('pfc_smileys');
2055    if (this.showsmileys)
2056      content.style.display = 'block';
2057    else
2058      content.style.display = 'none';
2059
2060    // then switch the button icon
2061    var btn = $('pfc_showHideSmileysbtn');
2062    if (this.showsmileys)
2063    {
2064      if (btn)
2065      {
2066        btn.src = this.res.getFileUrl('images/smiley-on.gif');
2067        btn.alt = this.res.getLabel('Hide smiley box');
2068        btn.title = btn.alt;
2069      }
2070    }
2071    else
2072    {
2073      if (btn)
2074      {
2075        btn.src = this.res.getFileUrl('images/smiley-off.gif');
2076        btn.alt = this.res.getLabel('Show smiley box');
2077        btn.title = btn.alt;
2078      }
2079    }
2080  },
2081
2082
2083  /**
2084   * Show Hide who's online
2085   */
2086  showHideWhosOnline: function()
2087  {
2088    if (this.showwhosonline)
2089    {
2090      this.showwhosonline = false;
2091    }
2092    else
2093    {
2094      this.showwhosonline = true;
2095    }
2096    setCookie('pfc_showwhosonline', this.showwhosonline);
2097    this.refresh_WhosOnline();
2098  },
2099  refresh_WhosOnline: function()
2100  {
2101    // first of all : show/hide the nickname list box
2102    var root = $('pfc_channels_content');
2103    var contentlist = this.getElementsByClassName(root, 'pfc_online', '');
2104    for(var i = 0; i < contentlist.length; i++)
2105    {
2106      var content = contentlist[i];
2107      if (this.showwhosonline)
2108        content.style.display = 'block';
2109      else
2110        content.style.display = 'none';
2111      content.style.zIndex = '100'; // for IE6, force the nickname list borders to be shown
2112    }
2113
2114    // then refresh the button icon
2115    var btn = $('pfc_showHideWhosOnlineBtn');
2116    if (!btn) return;
2117    if (this.showwhosonline)
2118    {
2119      btn.src = this.res.getFileUrl('images/online-on.gif');
2120      btn.alt = this.res.getLabel('Hide online users box');
2121      btn.title = btn.alt;
2122    }
2123    else
2124    {
2125      btn.src = this.res.getFileUrl('images/online-off.gif');
2126      btn.alt = this.res.getLabel('Show online users box');
2127      btn.title = btn.alt;
2128    }
2129    this.refresh_Chat();
2130  },
2131
2132  /**
2133   * Resize chat
2134   */
2135  refresh_Chat: function()
2136  {
2137    // resize all the tabs content
2138    var root = $('pfc_channels_content');
2139    var contentlist = this.getElementsByClassName(root, 'pfc_chat', '');
2140    for(var i = 0; i < contentlist.length; i++)
2141    {
2142      var chatdiv = contentlist[i];
2143      if (!this.showwhosonline)
2144      {
2145        chatdiv.style.width = '100%';
2146      }
2147      else
2148      {
2149        chatdiv.style.width = '';
2150      }
2151    }
2152  },
2153
2154  getPrompt: function()
2155  {
2156    if (!this.pfc)
2157    this.pfc = new pfcPrompt($('pfc_container'));
2158    return this.pfc;
2159  }
2160};
2161