Starting to separate model from view.
[KiwiIRC.git] / client / js / front.js
CommitLineData
8343584e
D
1/*jslint white:true, regexp: true, nomen: true, devel: true, undef: true, browser: true, continue: true, sloppy: true, forin: true, newcap: true, plusplus: true, maxerr: 50, indent: 4 */
2/*global kiwi, _, io, $, iScroll, agent, touchscreen, init_data, plugs, plugins, registerTouches, randomString */
5a29866a
JA
3/**
4* @namespace
5*/
673a7c8f 6kiwi.front = {
5a29866a
JA
7 /**
8 * The current channel
9 * @type Object
10 */
11 cur_channel: null,
12 /**
13 * A list of windows
14 * @type Object
15 */
a3364605 16 windows: {},
5a29866a
JA
17 /**
18 * A list of Tabviews
19 * @type Object
20 */
a3364605 21 tabviews: {},
5a29866a
JA
22 /**
23 * A list of Utilityviews
24 * @type Object
25 */
604c5174 26 utilityviews: {},
5a29866a
JA
27 /**
28 * A list of Boxes
29 * @type Object
30 */
a3364605 31 boxes: {},
8343584e 32
5a29866a
JA
33 /**
34 * The command history
35 * @type Array
36 */
37 buffer: [],
38 /**
39 * The current command history position
40 * @type Number
41 */
42 buffer_pos: 0,
43
44 /**
45 * Container for misc data (eg. userlist generation)
46 * @type Object
47 */
bfdeb872 48 cache: {original_topic: '', userlist: {}},
8343584e 49
5a29866a
JA
50 /**
51 * Initialisation function
52 */
a3364605 53 init: function () {
1fce4b40
JA
54 /*global Box, touch_scroll:true, Tabview */
55 var about_info, supportsOrientationChange, orientationEvent, scroll_opts, server_tabview;
673a7c8f
D
56 kiwi.gateway.nick = 'kiwi_' + Math.ceil(100 * Math.random()) + Math.ceil(100 * Math.random());
57 kiwi.gateway.session_id = null;
8343584e 58
b41a381f
D
59 // Bind to the gateway events
60 kiwi.front.events.bindAll();
8343584e 61
a3364605 62 // Build the about box
673a7c8f 63 kiwi.front.boxes.about = new Box("about");
a3364605
JA
64 about_info = 'UI adapted for ' + agent;
65 if (touchscreen) {
66 about_info += ' touchscreen ';
67 }
68 about_info += 'usage';
69 $('#tmpl_about_box').tmpl({
47468e80 70 about: about_info
673a7c8f 71 }).appendTo(kiwi.front.boxes.about.content);
a3364605 72
673a7c8f 73 //$(window).bind("beforeunload", function(){ kiwi.gateway.quit(); });
8343584e 74
a3364605
JA
75 if (touchscreen) {
76 $('#kiwi').addClass('touchscreen');
77
78 // Single touch scrolling through scrollback for touchscreens
79 scroll_opts = {};
80 touch_scroll = new iScroll('windows', scroll_opts);
81 }
82
b41a381f 83 kiwi.front.ui.registerKeys();
8343584e 84
b41a381f
D
85 $('#kiwi .toolbars').resize(kiwi.front.ui.doLayoutSize);
86 $(window).resize(kiwi.front.ui.doLayoutSize);
b10afa2d
D
87
88 // Add the resizer for the userlist
b6ee5d82 89 $('<div id="nicklist_resize"></div>').appendTo('#kiwi');
80c584e7
JA
90 $('#nicklist_resize').draggable({axis: "x", drag: function () {
91 var t = $(this),
7779d29a 92 ul = $('#kiwi .userlist'),
fde6ea95 93 new_width = $(document).width() - parseInt(t.css('left'), 10);
8343584e 94
7779d29a
D
95 new_width = new_width - parseInt(ul.css('margin-left'), 10);
96 new_width = new_width - parseInt(ul.css('margin-right'), 10);
b10afa2d
D
97
98 // Make sure we don't remove the userlist alltogether
b10afa2d
D
99 if (new_width < 20) {
100 $(this).data('draggable').offset.click.left = 10;
101 console.log('whoaa');
102 }
103
1fce4b40 104 Tabview.getCurrentTab().userlist.setWidth(new_width);
7779d29a 105 $('#windows').css('right', ul.outerWidth(true));
b10afa2d
D
106 }});
107
a3364605
JA
108
109 $('#kiwi .formconnectwindow').submit(function () {
110 var netsel = $('#kiwi .formconnectwindow .network'),
46f98bec
JA
111 netport = $('#kiwi .formconnectwindow .port'),
112 netssl = $('#kiwi .formconnectwindow .ssl'),
e8a707e7 113 netpass = $('#kiwi .formconnectwindow .password'),
a3364605 114 nick = $('#kiwi .formconnectwindow .nick'),
6849bc6c
JA
115 tmp,
116 forwardKeys;
8343584e 117
a3364605
JA
118 if (nick.val() === '') {
119 nick.val('Nick please!');
120 nick.focus();
121 return false;
122 }
8343584e 123
a3364605 124 tmp = nick.val().split(' ');
673a7c8f 125 kiwi.gateway.nick = tmp[0];
c89b9fdf
D
126
127 init_data.channel = $('#channel').val();
128
b41a381f 129 kiwi.front.ui.doLayout();
a3364605 130 try {
de578961
D
131 tmp = '/connect ' + netsel.val() + ' ' + netport.val() + ' ';
132 tmp += (netssl.is(':checked') ? 'true' : 'false') + ' ' + netpass.val();
133 kiwi.front.run(tmp);
4c408915
D
134 } catch (e) {
135 console.log(e);
136 }
8343584e 137
b41a381f 138 $('#kiwi .connectwindow').slideUp('', kiwi.front.ui.barsShow);
6849bc6c 139
5a29866a
JA
140 /**
141 * Listen for keyboard activity on any window, and forward it to the
142 * input box so users can type even if the input box is not in focus
143 * @inner
144 * @param {eventObject} event The event to forward
145 */
6849bc6c
JA
146 forwardKeys = function (event) {
147 $('#kiwi_msginput').focus();
148 $('#kiwi_msginput').trigger(event);
149 };
150 $('#kiwi_msginput').attr('tabindex', 0);
151 $('#kiwi_msginput').focus();
152 $('#windows').attr('tabindex',100);
153 $('#windows').keydown(forwardKeys).keypress(forwardKeys).keyup(forwardKeys);
a3364605
JA
154
155 return false;
156 });
157
158 supportsOrientationChange = (typeof window.onorientationchange !== undefined);
159 orientationEvent = supportsOrientationChange ? "orientationchange" : "resize";
d30da6a0 160 if (window.addEventListener) {
b41a381f 161 window.addEventListener(orientationEvent, kiwi.front.ui.doLayoutSize, false);
d30da6a0
D
162 } else {
163 // < IE9
b41a381f 164 window.attachEvent(orientationEvent, kiwi.front.ui.doLayoutSize, false);
d30da6a0 165 }
b41a381f 166 //$('#kiwi').bind("resize", kiwi.front.ui.doLayoutSize, false);
a3364605 167
b41a381f
D
168 kiwi.front.ui.doLayout();
169 kiwi.front.ui.barsHide();
8343584e 170
f7a9a13c
JA
171 kiwi.bbchans = new kiwi.model.ChannelList();
172 kiwi.bbtabs = new kiwi.view.Tabs({"el": $('#kiwi .windowlist ul')[0], "model": kiwi.bbchans});
173
174
1fce4b40 175 server_tabview = new Tabview('server');
0f439a3b 176 server_tabview.userlist.setWidth(0); // Disable the userlist
3586a76e 177 server_tabview.setIcon('/img/app_menu.png');
3586a76e
D
178 $('.icon', server_tabview.tab).tipTip({
179 delay: 0,
180 keepAlive: true,
181 content: $('#tmpl_network_menu').tmpl({}).html(),
182 activation: 'click'
183 });
8343584e 184
a3364605 185 // Any pre-defined nick?
c89b9fdf 186 if (typeof window.init_data.nick === "string") {
bad1ea63
JA
187 $('#kiwi .formconnectwindow .nick').val(init_data.nick);
188 }
c89b9fdf
D
189
190 // Any pre-defined channels?
191 if (typeof window.init_data.channel === 'string') {
192 $('#channel').val(init_data.channel);
193 }
8343584e 194
8ee99eba
JA
195 // Fix for Opera inserting a spurious <br/>
196 $('#kiwi .cur_topic br').remove();
8343584e 197
a3364605 198 $('#kiwi .cur_topic').keydown(function (e) {
aa191535
D
199 if (e.which === 13) {
200 // enter
201 e.preventDefault();
202 $(this).change();
203 $('#kiwi_msginput').focus();
204 } else if (e.which === 27) {
205 // escape
206 e.preventDefault();
bfdeb872 207 $(this).text(kiwi.front.cache.original_topic);
aa191535
D
208 $('#kiwi_msginput').focus();
209 }
a3364605 210 });
aa191535 211 /*$('.cur_topic').live('keypress', function(e) {
110ce6d4 212 if (e.keyCode === 13) {
1666d8d9 213 // enter
110ce6d4
JA
214 e.preventDefault();
215 $(this).change();
216 $('#kiwi_msginput').focus();
1666d8d9
JA
217 } else if (e.keyCode === 27) {
218 // escape
219 e.preventDefault();
bfdeb872 220 $(this).text(kiwi.front.cache.original_topic);
110ce6d4 221 }
aa191535 222 });*/
a3364605 223 $('.cur_topic').live('change', function () {
110ce6d4
JA
224 var chan, text;
225 text = $(this).text();
bfdeb872 226 if (text !== kiwi.front.cache.original_topic) {
1fce4b40 227 chan = Tabview.getCurrentTab().name;
312d2d7c 228 kiwi.gateway.topic(chan, text);
110ce6d4
JA
229 }
230 });
ff19dc74
D
231
232
c6ef62a3 233 $('#windows a.chan').live('click', function () {
673a7c8f 234 kiwi.front.joinChannel($(this).text());
ff19dc74
D
235 return false;
236 });
8343584e 237
fde6ea95 238 kiwi.data.set('chanList', []);
abb46d2d 239
604eaa7d 240 // Load any client plugins
2fc64ec2
JA
241 (function () {
242 var i;
243 for (i in plugins) {
673a7c8f 244 kiwi.plugs.loadPlugin(plugins[i]);
2fc64ec2
JA
245 }
246 }());
a3364605 247 },
8343584e 248
a3364605 249
8343584e 250
5a29866a
JA
251 /**
252 * Joins a channel
253 * @param {String} chan_name The name of the channel to join
254 */
a3364605
JA
255 joinChannel: function (chan_name) {
256 var chans = chan_name.split(','),
c6ef62a3 257 i,
1fce4b40
JA
258 chan,
259 tab;
a3364605
JA
260 for (i in chans) {
261 chan = chans[i];
1fce4b40
JA
262 tab = Tabview.getTab(chan);
263 if ((!tab) || (tab.safe_to_close === true)) {
673a7c8f 264 kiwi.gateway.join(chan);
1fce4b40 265 tab = new Tabview(chan);
a3364605 266 } else {
1fce4b40 267 tab.show();
a3364605
JA
268 }
269 }
270 },
8343584e 271
5a29866a
JA
272 /**
273 * Parses and executes text and commands entered into the input msg box
274 * @param {String} msg The message string to parse
275 */
a3364605 276 run: function (msg) {
312d2d7c 277 var parts, dest, t, pos, textRange, plugin_event, msg_sliced, tab, nick;
8343584e 278
875d0c71
D
279 // Run through any plugins
280 plugin_event = {command: msg};
673a7c8f 281 plugin_event = kiwi.plugs.run('command_run', plugin_event);
c6ef62a3
JA
282 if (!plugin_event || typeof plugin_event.command === 'undefined') {
283 return;
284 }
875d0c71
D
285
286 // Update msg if it's been changed by any plugins
287 msg = plugin_event.command.toString();
288
8343584e 289
a3364605 290 if (msg.substring(0, 1) === '/') {
cd81360a 291 console.log("running " + msg);
a3364605
JA
292 parts = msg.split(' ');
293 switch (parts[0].toLowerCase()) {
294 case '/j':
295 case '/join':
673a7c8f 296 kiwi.front.joinChannel(parts[1]);
a3364605 297 break;
8343584e 298
a3364605
JA
299 case '/connect':
300 case '/server':
4c408915 301 if (typeof parts[1] === 'undefined') {
e8a707e7 302 alert('Usage: /connect servername [port] [ssl] [password]');
a3364605
JA
303 break;
304 }
8343584e 305
4c408915 306 if (typeof parts[2] === 'undefined') {
bad1ea63
JA
307 parts[2] = 6667;
308 }
8343584e 309
4c408915 310 if ((typeof parts[3] === 'undefined') || !parts[3] || (parts[3] === 'false') || (parts[3] === 'no')) {
46f98bec
JA
311 parts[3] = false;
312 } else {
313 parts[3] = true;
314 }
8343584e 315
1fce4b40 316 Tabview.getCurrentTab().addMsg(null, ' ', '=== Connecting to ' + parts[1] + ' on port ' + parts[2] + (parts[3] ? ' using SSL' : '') + '...', 'status');
e8a707e7 317 kiwi.gateway.connect(parts[1], parts[2], parts[3], parts[4]);
a3364605 318 break;
8343584e 319
a3364605
JA
320 case '/nick':
321 console.log("/nick");
322 if (parts[1] === undefined) {
323 console.log("calling show nick");
b41a381f 324 kiwi.front.ui.showChangeNick();
a3364605 325 } else {
312d2d7c 326 console.log("sending nick");
5d57d62d 327 kiwi.gateway.changeNick(parts[1]);
a3364605
JA
328 }
329 break;
330
331 case '/part':
332 if (typeof parts[1] === "undefined") {
1fce4b40
JA
333 if (Tabview.getCurrentTab().safe_to_close) {
334 Tabview.getCurrentTab().close();
a3364605 335 } else {
312d2d7c 336 kiwi.gateway.part(Tabview.getCurrentTab().name);
a3364605
JA
337 }
338 } else {
312d2d7c 339 kiwi.gateway.part(msg.substring(6));
a3364605
JA
340 }
341 break;
8343584e 342
a3364605
JA
343 case '/names':
344 if (typeof parts[1] !== "undefined") {
673a7c8f 345 kiwi.gateway.raw(msg.substring(1));
a3364605
JA
346 }
347 break;
8343584e 348
a3364605 349 case '/debug':
673a7c8f 350 kiwi.gateway.debug();
a3364605 351 break;
8343584e 352
a3364605
JA
353 case '/q':
354 case '/query':
355 if (typeof parts[1] !== "undefined") {
1fce4b40 356 tab = new Tabview(parts[1]);
a3364605
JA
357 }
358 break;
323e90f6 359
8343584e 360
323e90f6
D
361 case '/m':
362 case '/msg':
363 if (typeof parts[1] !== "undefined") {
c6ef62a3 364 msg_sliced = msg.split(' ').slice(2).join(' ');
312d2d7c 365 kiwi.gateway.privmsg(parts[1], msg_sliced);
a5ffcf00 366
1fce4b40
JA
367 tab = Tabview.getTab(parts[1]);
368 if (!tab) {
369 tab = new Tabview(parts[1]);
a5ffcf00 370 }
1fce4b40 371 tab.addMsg(null, kiwi.gateway.nick, msg_sliced);
323e90f6
D
372 }
373 break;
8343584e 374
cdd803f6
D
375 case '/k':
376 case '/kick':
80c584e7 377 if (typeof parts[1] === 'undefined') {
fde6ea95
JA
378 return;
379 }
312d2d7c
JA
380 t = msg.split(' ', 3);
381 nick = t[1];
382 kiwi.gateway.kick(Tabview.getCurrentTab().name, nick, t[2]);
cdd803f6
D
383 break;
384
a3364605 385 case '/quote':
673a7c8f 386 kiwi.gateway.raw(msg.replace(/^\/quote /i, ''));
a3364605 387 break;
8343584e 388
a3364605 389 case '/me':
1fce4b40 390 tab = Tabview.getCurrentTab();
312d2d7c 391 kiwi.gateway.ctcp(true, 'ACTION', tab.name, msg.substring(4));
1fce4b40 392 tab.addMsg(null, ' ', '* ' + kiwi.gateway.nick + ' ' + msg.substring(4), 'action', 'color:#555;');
a3364605
JA
393 break;
394
395 case '/notice':
396 dest = parts[1];
397 msg = parts.slice(2).join(' ');
398
673a7c8f 399 kiwi.gateway.notice(dest, msg);
b41a381f 400 kiwi.front.events.onNotice({}, {nick: kiwi.gateway.nick, channel: dest, msg: msg});
a3364605
JA
401 break;
402
403 case '/win':
404 if (parts[1] !== undefined) {
b41a381f 405 kiwi.front.ui.windowsShow(parseInt(parts[1], 10));
a3364605
JA
406 }
407 break;
408
409 case '/quit':
b575fa08 410 kiwi.gateway.quit(parts.slice(1).join(' '));
f4f7781b
JA
411 break;
412
45c03116 413 case '/topic':
a3364605
JA
414 if (parts[1] === undefined) {
415 t = $('.cur_topic');
416 if (t.createTextRange) {
417 pos = t.text().length();
418 textRange = t.createTextRange();
419 textRange.collapse(true);
420 textRange.moveEnd(pos);
421 textRange.moveStart(pos);
422 textRange.select();
423 } else if (t.setSelectionRange) {
424 t.setSelectionRange(pos, pos);
425 }
426 } else {
312d2d7c 427 kiwi.gateway.topic(Tabview.getCurrentTab().name, msg.split(' ', 2)[1]);
aa191535 428 }
45c03116 429 break;
abb46d2d
D
430
431 case '/kiwi':
312d2d7c 432 kiwi.gateway.ctcp(true, 'KIWI', Tabview.getCurrentTab().name, msg.substring(6));
abb46d2d
D
433 break;
434
a3c7edd4
JA
435 case '/ctcp':
436 parts = parts.slice(1);
437 dest = parts.shift();
312d2d7c 438 t = parts.shift();
a3c7edd4 439 msg = parts.join(' ');
312d2d7c
JA
440 console.log(parts);
441
442 kiwi.gateway.ctcp(true, t, dest, msg);
443 Tabview.getServerTab().addMsg(null, 'CTCP Request', '[to ' + dest + '] ' + t + ' ' + msg, 'ctcp');
a3c7edd4 444 break;
a3364605 445 default:
1fce4b40 446 //Tabview.getCurrentTab().addMsg(null, ' ', '--> Invalid command: '+parts[0].substring(1));
8343584e 447 kiwi.gateway.raw(msg.substring(1));
bfdeb872 448 break;
a3364605
JA
449 }
450
451 } else {
452 //alert('Sending message: '+msg);
453 if (msg.trim() === '') {
bad1ea63
JA
454 return;
455 }
1fce4b40 456 if (Tabview.getCurrentTab().name !== 'server') {
312d2d7c 457 kiwi.gateway.privmsg(Tabview.getCurrentTab().name, msg);
1fce4b40 458 Tabview.getCurrentTab().addMsg(null, kiwi.gateway.nick, msg);
a3364605
JA
459 }
460 }
461 },
8343584e
D
462
463
8343584e 464
8343584e 465
7af36ece 466
0b399cb0
D
467 /**
468 * Sort the window list
469 */
470 sortWindowList: function () {
f93aafe5
JA
471 var win_list = $('#kiwi .windowlist ul'),
472 listitems = win_list.children('li').get();
c32b30d6 473
0b399cb0 474 listitems.sort(function (a, b) {
f93aafe5 475 if (a === Tabview.getServerTab().tab[0]) {
0b399cb0
D
476 return -1;
477 }
f93aafe5 478 if (b === Tabview.getServerTab().tab[0]) {
b16d21f2
JA
479 return 1;
480 }
f93aafe5
JA
481 var compA = $(a).text().toUpperCase(),
482 compB = $(b).text().toUpperCase();
0b399cb0 483 return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
f93aafe5 484 });
0b399cb0
D
485
486 $.each(listitems, function(idx, itm) {
487 win_list.append(itm);
488 });
489 },
490
491
492
a3364605 493
5a29866a
JA
494 /**
495 * Syncs with the Kiwi server
496 * Not implemented
497 */
a3364605 498 sync: function () {
673a7c8f 499 kiwi.gateway.sync();
a3364605 500 },
8343584e 501
5a29866a
JA
502 /**
503 * Checks if a given name is the name of a channel
504 * @param {String} name The name to check
505 * @returns {Boolean} True if name is the name of a channel, false if it is not
506 */
a3364605 507 isChannel: function (name) {
c6ef62a3 508 var prefix, is_chan;
a3364605 509 prefix = name.charAt(0);
673a7c8f 510 if (kiwi.gateway.channel_prefix.indexOf(prefix) > -1) {
a3364605
JA
511 is_chan = true;
512 } else {
513 is_chan = false;
514 }
8343584e 515
a3364605
JA
516 return is_chan;
517 },
8343584e 518
5a29866a
JA
519 /**
520 * Formats a message. Adds bold, underline and colouring
521 * @param {String} msg The message to format
522 * @returns {String} The HTML formatted message
523 */
0a202119 524 formatIRCMsg: function (msg) {
8343584e
D
525 var re, next;
526
9d34a1ca 527 if ((!msg) || (typeof msg !== 'string')) {
b91949aa 528 return '';
9d34a1ca 529 }
8343584e 530
24552b4b
JA
531 // bold
532 if (msg.indexOf(String.fromCharCode(2)) !== -1) {
533 next = '<b>';
534 while (msg.indexOf(String.fromCharCode(2)) !== -1) {
535 msg = msg.replace(String.fromCharCode(2), next);
536 next = (next === '<b>') ? '</b>' : '<b>';
537 }
538 if (next === '</b>') {
539 msg = msg + '</b>';
540 }
541 }
8343584e 542
24552b4b
JA
543 // underline
544 if (msg.indexOf(String.fromCharCode(31)) !== -1) {
545 next = '<u>';
546 while (msg.indexOf(String.fromCharCode(31)) !== -1) {
547 msg = msg.replace(String.fromCharCode(31), next);
548 next = (next === '<u>') ? '</u>' : '<u>';
549 }
550 if (next === '</u>') {
551 msg = msg + '</u>';
552 }
553 }
8343584e 554
24552b4b 555 // colour
5a29866a
JA
556 /**
557 * @inner
558 */
52a45f8c
JA
559 msg = (function (msg) {
560 var replace, colourMatch, col, i, match, to, endCol, fg, bg, str;
561 replace = '';
5a29866a
JA
562 /**
563 * @inner
564 */
52a45f8c
JA
565 colourMatch = function (str) {
566 var re = /^\x03([0-9][0-5]?)(,([0-9][0-5]?))?/;
567 return re.exec(str);
568 };
5a29866a
JA
569 /**
570 * @inner
571 */
52a45f8c
JA
572 col = function (num) {
573 switch (parseInt(num, 10)) {
574 case 0:
575 return '#FFFFFF';
576 case 1:
577 return '#000000';
578 case 2:
579 return '#000080';
580 case 3:
581 return '#008000';
582 case 4:
583 return '#FF0000';
584 case 5:
585 return '#800040';
586 case 6:
587 return '#800080';
588 case 7:
589 return '#FF8040';
590 case 8:
591 return '#FFFF00';
592 case 9:
593 return '#80FF00';
594 case 10:
595 return '#008080';
596 case 11:
597 return '#00FFFF';
598 case 12:
599 return '#0000FF';
600 case 13:
601 return '#FF55FF';
602 case 14:
603 return '#808080';
604 case 15:
605 return '#C0C0C0';
606 default:
607 return null;
608 }
609 };
610 if (msg.indexOf('\x03') !== -1) {
611 i = msg.indexOf('\x03');
612 replace = msg.substr(0, i);
613 while (i < msg.length) {
5a29866a
JA
614 /**
615 * @inner
616 */
52a45f8c
JA
617 match = colourMatch(msg.substr(i, 6));
618 if (match) {
619 //console.log(match);
620 // Next colour code
621 to = msg.indexOf('\x03', i + 1);
622 endCol = msg.indexOf(String.fromCharCode(15), i + 1);
623 if (endCol !== -1) {
624 if (to === -1) {
625 to = endCol;
626 } else {
627 to = ((to < endCol) ? to : endCol);
628 }
629 }
630 if (to === -1) {
631 to = msg.length;
632 }
633 //console.log(i, to);
634 fg = col(match[1]);
635 bg = col(match[3]);
636 str = msg.substring(i + 1 + match[1].length + ((bg !== null) ? match[2].length + 1 : 0), to);
637 //console.log(str);
638 replace += '<span style="' + ((fg !== null) ? 'color: ' + fg + '; ' : '') + ((bg !== null) ? 'background-color: ' + bg + ';' : '') + '">' + str + '</span>';
639 i = to;
640 } else {
641 if ((msg[i] !== '\x03') && (msg[i] !== String.fromCharCode(15))) {
642 replace += msg[i];
643 }
644 i++;
24552b4b 645 }
52a45f8c
JA
646 }
647 return replace;
648 }
649 return msg;
650 }(msg));
651
24552b4b 652 return msg;
cbcccdbd 653 },
5a29866a
JA
654
655 /**
656 * Registers Kiwi IRC as a handler for the irc:// protocol in the browser
657 */
cbcccdbd
D
658 registerProtocolHandler: function () {
659 var state, uri;
660 url = kiwi_server.replace(/\/kiwi$/, '/?ircuri=%s');
661 try {
662 //state = window.navigator.isProtocolHandlerRegistered('irc', url);
663 //if (state !== 'registered') {
664 window.navigator.registerProtocolHandler('irc', url, 'Kiwi IRC');
665 //}
666 } catch (e) {
667 console.log('Unable to register Kiwi IRC as a handler for irc:// links');
668 console.error(e);
669 }
a3364605 670 }
8343584e 671
a3364605 672};
54f4a22e
D
673
674
675
676
677
5a29866a
JA
678/**
679* @constructor
680*/
dde5dcd9
JA
681var ChannelList = function () {
682 /*globals Utilityview */
683 var chanList, view, table, obj, renderTable, waiting;
684 chanList = [];
685
686 view = new Utilityview('Channel List');
687 view.div.css('overflow-y', 'scroll');
688
689 table = $('<table style="margin:1em 2em;"><thead style="font-weight: bold;"><tr><td>Channel Name</td><td>Members</td><td style="padding-left: 2em;">Topic</td></tr></thead><tbody style="vertical-align: top;"></tbody>');
690 table = table.appendTo(view.div);
691
692 waiting = false;
5a29866a
JA
693 /**
694 * @inner
695 */
dde5dcd9
JA
696 renderTable = function () {
697 var tbody;
698 tbody = table.children('tbody:first').detach();
699 /*tbody.children().each(function (child) {
700 var i, chan;
701 child = $(child);
702 chan = child.children('td:first').text();
703 for (i = 0; i < chanList.length; i++) {
704 if (chanList[i].channel === chan) {
705 chanList[i].html = child.detach();
706 break;
707 }
708 }
709 });*/
710 _.each(chanList, function (chan) {
711 chan.html = $(chan.html).appendTo(tbody);
712 });
713 table = table.append(tbody);
714 waiting = false;
715 };
5a29866a
JA
716 /**
717 * @lends ChannelList
718 */
719 return {
720 /**
721 * Adds a channel or channels to the list
722 * @param {Object} channels The channel or Array of channels to add
723 */
dde5dcd9
JA
724 addChannel: function (channels) {
725 if (!_.isArray(channels)) {
726 channels = [channels];
727 }
728 _.each(channels, function (chan) {
729 var html, channel;
730 html = $('<tr><td><a class="chan">' + chan.channel + '</a></td><td class="num_users" style="text-align: center;">' + chan.num_users + '</td><td style="padding-left: 2em;">' + kiwi.front.formatIRCMsg(chan.topic) + '</td></tr>');
731 chan.html = html;
732 chanList.push(chan);
733 });
734 chanList.sort(function (a, b) {
735 return b.num_users - a.num_users;
736 });
737 if (!waiting) {
738 waiting = true;
739 _.defer(renderTable);
740 }
741 },
5a29866a
JA
742 /**
743 * Show the {@link UtilityView} that will display this channel list
744 */
dde5dcd9
JA
745 show: function () {
746 view.show();
747 },
5a29866a
JA
748 /**
749 * @private
750 */
dde5dcd9
JA
751 prototype: {constructor: this}
752 };
dde5dcd9 753};
54f4a22e 754
5a29866a
JA
755/**
756* @constructor
757* @param {String} name The name of the UserList
758*/
8343584e 759var UserList = function (name) {
7af36ece
JA
760 /*globals User */
761 var userlist, list_html, sortUsers;
8343584e
D
762
763 userlist = [];
764
765 $('#kiwi .userlist').append($('<ul id="kiwi_userlist_' + name + '"></ul>'));
766 list_html = $('#kiwi_userlist_' + name);
767 $('a.nick', list_html[0]).live('click', this.clickHandler);
768
5a29866a
JA
769 /**
770 * @inner
771 */
8343584e
D
772 sortUsers = function () {
773 var parent;
774 parent = list_html.parent();
775 list_html = list_html.detach();
776
777 // Not sure this is needed.
778 // It's O(n^2) as well, so need to test to see if it works without.
779 // Alternative to test: list_html.children('li').detach();
780 list_html.children().each(function (child) {
781 var i, nick;
782 child = $(child);
783 nick = child.data('nick');
784 for (i = 0; i < userlist.length; i++) {
785 if (userlist[i].nick === nick) {
786 userlist[i].html = child.detach();
787 break;
788 }
789 }
790 });
791
7af36ece 792 userlist.sort(User.compare);
8343584e 793
8343584e
D
794 _.each(userlist, function (user) {
795 user.html = user.html.appendTo(list_html);
796 });
54f4a22e 797
8343584e
D
798 list_html = list_html.appendTo(parent);
799 };
800
5a29866a
JA
801 /**
802 * Adds a user or users to the UserList.
803 * Chainable method.
804 * @param {Object} users The user or Array of users to add
805 * @returns {UserList} This UserList
806 */
8343584e
D
807 this.addUser = function (users) {
808 if (!_.isArray(users)) {
809 users = [users];
810 }
811 _.each(users, function (user) {
7af36ece
JA
812 user = new User(user.nick, user.modes);
813 user.html = $('<li><a class="nick">' + user.prefix + user.nick + '</a></li>');
814 user.html.data('user', user);
815 userlist.push(user);
8343584e
D
816 });
817 sortUsers();
818
819 return this;
820 };
821
5a29866a
JA
822 /**
823 * Removes a user or users from the UserList.
824 * Chainable method.
825 * @param {String} nicks The nick or Array of nicks to remove
826 * @returns {UserList} This UserList
827 */
8343584e
D
828 this.removeUser = function (nicks) {
829 var toRemove;
830 if (!_.isArray(nicks)) {
831 nicks = [nicks];
832 }
833 toRemove = _.select(userlist, function (user) {
834 return _.any(nicks, function (n) {
835 return n === user.nick;
836 });
837 });
838
839 _.each(toRemove, function (user) {
840 user.html.remove();
841 });
842
843 userlist = _.difference(userlist, toRemove);
844
845 return this;
846 };
847
5a29866a
JA
848 /**
849 * Renames a user in the UserList.
850 * Chainable method.
851 * @param {String} oldNick The old nick
852 * @param {String} newNick The new nick
853 * @returns {UserList} This UserList
854 */
8343584e
D
855 this.renameUser = function (oldNick, newNick) {
856 var user = _.detect(userlist, function (u) {
857 return u.nick === oldNick;
858 });
859 if (user) {
860 user.nick = newNick;
7af36ece 861 user.html.text(User.getPrefix(user.modes) + newNick);
8343584e
D
862 }
863
864 sortUsers();
865
866 return this;
867 };
868
5a29866a
JA
869 /**
870 * Lists the users in this UserList.
871 * @param {Boolean} modesort True to enable sorting by mode, false for lexicographical sort
872 * @param {Array} mode If specified, only return those users who have the specified modes
873 * @returns {Array} The users in the UserList that match the criteria
874 */
8343584e
D
875 this.listUsers = function (modesort, modes) {
876 var users = userlist;
877 if (modes) {
878 users = _.select(users, function (user) {
879 return _.any(modes, function (m) {
880 return _.any(user.modes, function (um) {
881 return m === um;
882 });
883 });
884 });
885 }
886 if ((modesort === true) || (typeof modesort === undefined)) {
887 return users;
888 } else {
889 return _.sortBy(users, function (user) {
890 return user.nick;
891 });
892 }
893 };
894
5a29866a
JA
895 /**
896 * Remove this UserList from the DOM.
897 */
8343584e
D
898 this.remove = function () {
899 list_html.remove();
900 list_html = null;
901 userlist = null;
902 };
903
5a29866a
JA
904 /**
905 * Empty the UserList.
906 * Chainable method.
907 * @returns {UserList} This UserList
908 */
8343584e
D
909 this.empty = function () {
910 list_html.children().remove();
911 userlist = [];
912
913 return this;
914 };
915
5a29866a
JA
916 /**
917 * Checks whether a given nick is in the UserList.
918 * @param {String} nick The nick to search for
919 * @returns {Boolean} True if the nick is in the userlist, false otherwise
920 */
8343584e
D
921 this.hasUser = function (nick) {
922 return _.any(userlist, function (user) {
923 return user.nick === nick;
924 });
925 };
926
5a29866a
JA
927 /**
928 * Returns the object representing the user with the given nick, if it is in the UserList.
929 * @param {String} nick The nick to retrieve
930 * @returns {Object} An object representing the user, if it exists, null otherwise
931 */
7af36ece
JA
932 this.getUser = function (nick) {
933 if (this.hasUser(nick)) {
934 return _.detect(userlist, function (user) {
935 return user.nick === nick;
936 });
937 } else {
938 return null;
939 }
940 };
941
5a29866a
JA
942 /**
943 * Sets the UserList's activity.
944 * Chainable method.
945 * @param {Boolean} active If true, sets the UserList to active. If False, sets it to inactive
946 * @returns {UserList} This UserList
947 */
8343584e
D
948 this.active = function (active) {
949 if ((arguments.length === 0) || (active)) {
950 list_html.addClass('active');
951 list_html.show();
952 } else {
953 list_html.removeClass('active');
954 list_html.hide();
955 }
956
957 return this;
958 };
959
5a29866a
JA
960 /**
961 * Updates a user's mode.
962 * Chainable method.
963 * @param {String} nick The nick of the user to modify
964 * @param {String} mode The mode to add or remove
965 * @param {Boolean} add Adds the mode if true, removes it otherwise
966 * @returns {UserList} This UserList
967 */
8343584e 968 this.changeUserMode = function (nick, mode, add) {
7af36ece 969 var user, prefix;
8343584e 970 if (this.hasUser(nick)) {
7af36ece 971 user = _.detect(userlist, function (u) {
8343584e
D
972 return u.nick === nick;
973 });
974
7af36ece 975 prefix = user.prefix;
8343584e 976 if ((arguments.length < 3) || (add)) {
7af36ece 977 user.addMode(mode);
8343584e 978 } else {
7af36ece
JA
979 user.removeMode(mode);
980 }
981 if (prefix !== user.prefix) {
982 user.html.children('a:first').text(user.prefix + user.nick);
8343584e 983 }
8343584e
D
984 sortUsers();
985 }
986
987 return this;
988 };
989};
5a29866a
JA
990/**
991* @memberOf UserList
992*/
0f439a3b 993UserList.prototype.width = 100; // 0 to disable
5a29866a
JA
994/**
995* Sets the width of the UserList.
996* Chainable method.
997* @param {Number} newWidth The new width of the UserList
998* @returns {UserList} This UserList
999*/
8343584e
D
1000UserList.prototype.setWidth = function (newWidth) {
1001 var w, u;
7779d29a 1002 if (typeof newWidth === 'number') {
8343584e 1003 this.width = newWidth;
7779d29a 1004 }
8343584e 1005
7779d29a
D
1006 w = $('#windows');
1007 u = $('#kiwi .userlist');
8343584e 1008
7779d29a 1009 u.width(this.width);
8343584e
D
1010
1011 return this;
1012};
5a29866a
JA
1013/**
1014* The click handler for this UserList
1015*/
8343584e 1016UserList.prototype.clickHandler = function () {
7af36ece 1017 var li = $(this).parent(),
0dc1dc79
D
1018 user = li.data('user'),
1019 userbox;
1020
8343584e
D
1021 // Remove any existing userboxes
1022 $('#kiwi .userbox').remove();
1023
0dc1dc79
D
1024 if (li.data('userbox') === true) {
1025 // This li already has the userbox, show remove it instead
1026 li.removeData('userbox');
1027
8343584e 1028 } else {
0dc1dc79
D
1029 // We don't have a userbox so create one
1030 userbox = $('#tmpl_user_box').tmpl({nick: user.nick}).appendTo(li);
8343584e 1031
0dc1dc79 1032 $('.userbox_query', userbox).click(function (ev) {
8343584e
D
1033 var nick = $('#kiwi .userbox_nick').val();
1034 kiwi.front.run('/query ' + nick);
1035 });
1036
0dc1dc79 1037 $('.userbox_whois', userbox).click(function (ev) {
8343584e
D
1038 var nick = $('#kiwi .userbox_nick').val();
1039 kiwi.front.run('/whois ' + nick);
1040 });
0dc1dc79 1041 li.data('userbox', true);
8343584e
D
1042 }
1043};
54f4a22e
D
1044
1045
1046
5a29866a
JA
1047/**
1048* @constructor
943caaef
JA
1049* The User class. Represents a user on a channel.
1050* @param {String} nick The user's nickname
1051* @param {Array} modes An array of channel user modes
5a29866a 1052*/
7af36ece
JA
1053var User = function (nick, modes) {
1054 var sortModes;
5a29866a
JA
1055 /**
1056 * @inner
1057 */
7af36ece
JA
1058 sortModes = function (modes) {
1059 return modes.sort(function (a, b) {
1060 var a_idx, b_idx, i;
1061 for (i = 0; i < kiwi.gateway.user_prefixes.length; i++) {
1062 if (kiwi.gateway.user_prefixes[i].mode === a) {
1063 a_idx = i;
1064 }
1065 }
1066 for (i = 0; i < kiwi.gateway.user_prefixes.length; i++) {
1067 if (kiwi.gateway.user_prefixes[i].mode === b) {
1068 b_idx = i;
1069 }
1070 }
1071 if (a_idx < b_idx) {
1072 return -1;
1073 } else if (a_idx > b_idx) {
1074 return 1;
1075 } else {
1076 return 0;
1077 }
1078 });
1079 };
1080
1081 this.nick = User.stripPrefix(nick);
1082 this.modes = modes || [];
1083 this.modes = sortModes(this.modes);
1084 this.prefix = User.getPrefix(this.modes);
1085
943caaef
JA
1086 /**
1087 * @inner
1088 */
7af36ece
JA
1089 this.addMode = function (mode) {
1090 this.modes.push(mode);
1091 this.modes = sortModes(this.modes);
1092 this.prefix = User.getPrefix(this.modes);
1093 return this;
1094 };
1095};
1096
943caaef
JA
1097/**
1098* Removes a channel mode from the user
1099* @param {String} mode The mode(s) to remove
1100* @returns {User} Returns the User object to allow chaining
1101*/
7af36ece
JA
1102User.prototype.removeMode = function (mode) {
1103 this.modes = _.reject(this.modes, function (m) {
1104 return m === mode;
1105 });
1106 this.prefix = User.getPrefix(this.modes);
1107 return this;
1108};
1109
943caaef
JA
1110/**
1111* Checks to see if the user is an op on the channel
1112* @returns {Boolean} True if the user is an op, false otherwise
1113*/
7af36ece
JA
1114User.prototype.isOp = function () {
1115 // return true if this.mode[0] > o
1116 return false;
1117};
1118
943caaef
JA
1119/**
1120* Returns the highest user prefix (e.g.~, @, or +) that matches the modes given
1121* @param {Array} modes An array of mode letters
1122* @returns {String} The user's prefix
1123*/
7af36ece
JA
1124User.getPrefix = function (modes) {
1125 var prefix = '';
1126 if (typeof modes[0] !== 'undefined') {
1127 prefix = _.detect(kiwi.gateway.user_prefixes, function (prefix) {
1128 return prefix.mode === modes[0];
1129 });
1130 prefix = (prefix) ? prefix.symbol : '';
1131 }
1132 return prefix;
1133};
1134
943caaef
JA
1135/**
1136* Returns the user's nick without the mode prefix
1137* @param {String} nick The nick to strip the prefix from
1138* @returns {String} The nick without the prefix
1139*/
7af36ece
JA
1140User.stripPrefix = function (nick) {
1141 var tmp = nick, i, j, k;
1142 i = 0;
1143 for (j = 0; j < nick.length; j++) {
1144 for (k = 0; k < kiwi.gateway.user_prefixes.length; k++) {
1145 if (nick.charAt(j) === kiwi.gateway.user_prefixes[k].symbol) {
1146 i++;
1147 break;
1148 }
1149 }
1150 }
1151
1152 return tmp.substr(i);
1153};
1154
943caaef
JA
1155/**
1156* Comparison function to order nicks based on their modes and/or nicks
1157* @param {User} a The first User to evaluate
1158* @param {User} b The second User to evaluate
1159* @returns {Number} -1 if a should be sorted before b, 1 if b should be sorted before a, and 0 if the two Users are the same.
1160*/
7af36ece
JA
1161User.compare = function (a, b) {
1162 var i, a_idx, b_idx, a_nick, b_nick;
1163 // Try to sort by modes first
1164 if (a.modes.length > 0) {
1165 // a has modes, but b doesn't so a should appear first
1166 if (b.modes.length === 0) {
1167 return -1;
1168 }
1169 a_idx = b_idx = -1;
1170 // Compare the first (highest) mode
1171 for (i = 0; i < kiwi.gateway.user_prefixes.length; i++) {
1172 if (kiwi.gateway.user_prefixes[i].mode === a.modes[0]) {
1173 a_idx = i;
1174 }
1175 }
1176 for (i = 0; i < kiwi.gateway.user_prefixes.length; i++) {
1177 if (kiwi.gateway.user_prefixes[i].mode === b.modes[0]) {
1178 b_idx = i;
1179 }
1180 }
1181 if (a_idx < b_idx) {
1182 return -1;
1183 } else if (a_idx > b_idx) {
1184 return 1;
1185 }
1186 // If we get to here both a and b have the same highest mode so have to resort to lexicographical sorting
1187
1188 } else if (b.modes.length > 0) {
1189 // b has modes but a doesn't so b should appear first
1190 return 1;
1191 }
1192 a_nick = a.nick.toLocaleUpperCase();
1193 b_nick = b.nick.toLocaleUpperCase();
1194 // Lexicographical sorting
1195 if (a_nick < b_nick) {
1196 return -1;
1197 } else if (a_nick > b_nick) {
1198 return 1;
1199 } else {
1200 // This should never happen; both users have the same nick.
1201 console.log('Something\'s gone wrong somewhere - two users have the same nick!');
1202 return 0;
1203 }
1204};
1205
1206
54f4a22e 1207
1bb74900
D
1208/*
1209 * MISC VIEW
1210 */
5a29866a
JA
1211/**
1212* @constructor
943caaef
JA
1213* A tab to show non-channel and non-query windows to the user
1214* @param {String} name The name of the view
5a29866a 1215*/
604c5174 1216var Utilityview = function (name) {
c6ef62a3
JA
1217 var rand_name = randomString(15),
1218 tmp_divname = 'kiwi_window_' + rand_name,
c6ef62a3 1219 tmp_tabname = 'kiwi_tab_' + rand_name;
8343584e 1220
604c5174
D
1221 this.name = rand_name;
1222 this.title = name;
1223 this.topic = ' ';
4e4ca8e6 1224 this.panel = $('#panel1');
1bb74900 1225
4e4ca8e6
D
1226 if (typeof $('.scroller', this.panel)[0] === 'undefined') {
1227 this.panel.append('<div id="' + tmp_divname + '" class="messages"></div>');
1228 } else {
1229 $('.scroller', this.panel).append('<div id="' + tmp_divname + '" class="messages"></div>');
1230 }
604c5174
D
1231
1232 this.tab = $('<li id="' + tmp_tabname + '">' + this.title + '</li>');
c6ef62a3 1233 this.tab.click(function () {
673a7c8f 1234 kiwi.front.utilityviews[rand_name.toLowerCase()].show();
604c5174
D
1235 });
1236 $('#kiwi .utilityviewlist ul').append(this.tab);
b41a381f 1237 kiwi.front.ui.doLayoutSize();
8343584e 1238
1bb74900
D
1239 this.div = $('#' + tmp_divname);
1240 this.div.css('overflow', 'hidden');
1241
673a7c8f 1242 kiwi.front.utilityviews[rand_name.toLowerCase()] = this;
1bb74900
D
1243};
1244
1245Utilityview.prototype.name = null;
604c5174 1246Utilityview.prototype.title = null;
1bb74900
D
1247Utilityview.prototype.div = null;
1248Utilityview.prototype.tab = null;
ca7e63ea 1249Utilityview.prototype.topic = ' ';
4e4ca8e6 1250Utilityview.prototype.panel = null;
943caaef
JA
1251/**
1252* Brings this view to the foreground
1253*/
1bb74900 1254Utilityview.prototype.show = function () {
4e4ca8e6 1255 $('.messages', this.panel).removeClass("active");
1bb74900 1256 $('#kiwi .userlist ul').removeClass("active");
2b9dcc03 1257 $('#kiwi .toolbars ul li').removeClass("active");
1bb74900 1258
4e4ca8e6 1259 this.panel.css('overflow-y', 'hidden');
2b9dcc03 1260 $('#windows').css('right', 0);
1bb74900
D
1261 // Activate this tab!
1262 this.div.addClass('active');
1263 this.tab.addClass('active');
1264
1265 this.addPartImage();
1266
b41a381f 1267 kiwi.front.ui.setTopicText(this.topic);
673a7c8f 1268 kiwi.front.cur_channel = this;
1bb74900
D
1269
1270 // If we're using fancy scrolling, refresh it
1271 if (touch_scroll) {
1272 touch_scroll.refresh();
1273 }
c6ef62a3 1274};
943caaef
JA
1275/**
1276* Sets a new panel to be this view's parent
1277* @param {JQuery} new_panel The new parent
1278*/
4e4ca8e6
D
1279Utilityview.prototype.setPanel = function (new_panel) {
1280 this.div.detach();
1281 this.panel = new_panel;
1282 this.panel.append(this.div);
1283 this.show();
1284};
943caaef
JA
1285/**
1286* Removes the panel from the UI and destroys its contents
1287*/
1bb74900
D
1288Utilityview.prototype.close = function () {
1289 this.div.remove();
1290 this.tab.remove();
8343584e 1291
1fce4b40 1292 if (Tabview.getCurrentTab() === this) {
673a7c8f 1293 kiwi.front.tabviews.server.show();
1bb74900 1294 }
673a7c8f 1295 delete kiwi.front.utilityviews[this.name.toLowerCase()];
1bb74900 1296};
943caaef
JA
1297/**
1298* Adds the close image to the tab
1299*/
1bb74900
D
1300Utilityview.prototype.addPartImage = function () {
1301 this.clearPartImage();
8343584e 1302
1bb74900
D
1303 // We can't close this tab, so don't have the close image
1304 if (this.name === 'server') {
1305 return;
1306 }
1307
8397d902 1308 var del_html = '<img src="/img/redcross.png" class="tab_part" />';
1bb74900 1309 this.tab.append(del_html);
8343584e 1310
1bb74900 1311 $('.tab_part', this.tab).click(function () {
1fce4b40
JA
1312 if (Tabview.getCurrentTab().name !== 'server') {
1313 Tabview.getCurrentTab().close();
1bb74900
D
1314 }
1315 });
1316};
943caaef
JA
1317/**
1318* Removes the close image from the tab
1319*/
1bb74900 1320Utilityview.prototype.clearPartImage = function () {
2b9dcc03 1321 $('#kiwi .toolbars .tab_part').remove();
1bb74900
D
1322};
1323
1324
1325
1326
54f4a22e
D
1327
1328/*
1329 *
1330 * TABVIEWS
1331 *
1332 */
1333
5a29866a
JA
1334/**
1335* @constructor
943caaef
JA
1336* A tab to show a channel or query window
1337* @param {String} v_name The window's target's name (i.e. channel name or nickname)
5a29866a 1338*/
0f439a3b
D
1339var Tabview = function (v_name) {
1340 /*global Tabview, UserList */
3586a76e 1341 var re, htmlsafe_name, tmp_divname, tmp_userlistname, tmp_tabname, tmp_tab, userlist_enabled = true;
0a202119
D
1342
1343 if (v_name.charAt(0) === kiwi.gateway.channel_prefix) {
0b399cb0 1344 htmlsafe_name = 'chan_' + randomString(15);
0f439a3b 1345 } else {
0b399cb0 1346 htmlsafe_name = 'query_' + randomString(15);
0f439a3b
D
1347 userlist_enabled = false;
1348 }
1349
1350 tmp_divname = 'kiwi_window_' + htmlsafe_name;
1351 tmp_userlistname = 'kiwi_userlist_' + htmlsafe_name;
1352 tmp_tabname = 'kiwi_tab_' + htmlsafe_name;
1353
1fce4b40 1354 if (!Tabview.tabExists(v_name)) {
0b399cb0 1355 // Create the window
0f439a3b 1356 $('#kiwi .windows .scroller').append('<div id="' + tmp_divname + '" class="messages"></div>');
0b399cb0 1357
c32b30d6 1358 // Create the window tab
f7a9a13c 1359
0b399cb0
D
1360 tmp_tab = $('<li id="' + tmp_tabname + '"><span></span></li>');
1361 $('span', tmp_tab).text(v_name);
3586a76e
D
1362 $('#kiwi .windowlist ul').append(tmp_tab);
1363 tmp_tab.click(function (e) {
1fce4b40
JA
1364 var tab = Tabview.getTab(v_name);
1365 if (tab) {
1366 tab.show();
1367 }
1368 });
c32b30d6
D
1369
1370 kiwi.front.sortWindowList();
0f439a3b 1371 }
0f439a3b 1372
0a202119 1373 kiwi.front.tabviews[v_name.toLowerCase()] = this;
0f439a3b
D
1374 this.name = v_name;
1375 this.div = $('#' + tmp_divname);
1376 this.userlist = new UserList(htmlsafe_name);
1377 this.tab = $('#' + tmp_tabname);
4e4ca8e6 1378 this.panel = $('#panel1');
0f439a3b
D
1379
1380 if (!userlist_enabled) {
1381 this.userlist.setWidth(0);
1382 }
1383 this.show();
1384
1385 if (typeof registerTouches === "function") {
1386 //alert("Registering touch interface");
1387 //registerTouches($('#'+tmp_divname));
1388 registerTouches(document.getElementById(tmp_divname));
1389 }
1390
b41a381f 1391 kiwi.front.ui.doLayoutSize();
4e4ca8e6 1392};
a3364605
JA
1393Tabview.prototype.name = null;
1394Tabview.prototype.div = null;
1395Tabview.prototype.userlist = null;
1396Tabview.prototype.tab = null;
1397Tabview.prototype.topic = "";
1398Tabview.prototype.safe_to_close = false; // If we have been kicked/banned/etc from this channel, don't wait for a part message
4e4ca8e6 1399Tabview.prototype.panel = null;
182e307e 1400Tabview.prototype.msg_count = 0;
943caaef
JA
1401/**
1402* Brings this view to the foreground
1403*/
a3364605 1404Tabview.prototype.show = function () {
875d0c71
D
1405 var w, u;
1406
4e4ca8e6 1407 $('.messages', this.panel).removeClass("active");
a3364605 1408 $('#kiwi .userlist ul').removeClass("active");
2b9dcc03 1409 $('#kiwi .toolbars ul li').removeClass("active");
8343584e 1410
875d0c71
D
1411 w = $('#windows');
1412 u = $('#kiwi .userlist');
1413
8536a327 1414 this.panel.css('overflow-y', 'scroll');
875d0c71
D
1415
1416 // Set the window size accordingly
7779d29a
D
1417 if (this.userlist.width > 0) {
1418 this.userlist.setWidth();
1419 w.css('right', u.outerWidth(true));
8343584e 1420 this.userlist.active(true);
b10afa2d
D
1421 // Enable the userlist resizer
1422 $('#nicklist_resize').css('display', 'block');
1423 } else {
7779d29a 1424 w.css('right', 0);
b10afa2d
D
1425 // Disable the userlist resizer
1426 $('#nicklist_resize').css('display', 'none');
c6ef62a3 1427 }
7779d29a
D
1428
1429 this.div.addClass('active');
a3364605 1430 this.tab.addClass('active');
8343584e 1431
a3364605
JA
1432 // Add the part image to the tab
1433 this.addPartImage();
8343584e 1434
a3364605 1435 this.clearHighlight();
b41a381f 1436 kiwi.front.ui.setTopicText(this.topic);
673a7c8f 1437 kiwi.front.cur_channel = this;
8343584e 1438
a3364605
JA
1439 // If we're using fancy scrolling, refresh it
1440 if (touch_scroll) {
1441 touch_scroll.refresh();
1442 }
1443
1444 this.scrollBottom();
1445 if (!touchscreen) {
1446 $('#kiwi_msginput').focus();
1447 }
1448};
943caaef
JA
1449/**
1450* Removes the panel from the UI and destroys its contents
1451*/
a3364605
JA
1452Tabview.prototype.close = function () {
1453 this.div.remove();
1454 this.userlist.remove();
8343584e 1455 this.userlist = null;
a3364605 1456 this.tab.remove();
8343584e 1457
673a7c8f
D
1458 if (kiwi.front.cur_channel === this) {
1459 kiwi.front.tabviews.server.show();
a3364605 1460 }
673a7c8f 1461 delete kiwi.front.tabviews[this.name.toLowerCase()];
a3364605 1462};
943caaef
JA
1463/**
1464* Adds the close image to the tab
1465*/
a3364605
JA
1466Tabview.prototype.addPartImage = function () {
1467 this.clearPartImage();
8343584e 1468
a3364605
JA
1469 // We can't close this tab, so don't have the close image
1470 if (this.name === 'server') {
1471 return;
1472 }
1473
8397d902 1474 var del_html = '<img src="/img/redcross.png" class="tab_part" />';
a3364605 1475 this.tab.append(del_html);
8343584e 1476
a3364605 1477 $('.tab_part', this.tab).click(function () {
673a7c8f
D
1478 if (kiwi.front.isChannel($(this).parent().text())) {
1479 kiwi.front.run("/part");
a3364605
JA
1480 } else {
1481 // Make sure we don't close the server tab
673a7c8f
D
1482 if (kiwi.front.cur_channel.name !== 'server') {
1483 kiwi.front.cur_channel.close();
a3364605
JA
1484 }
1485 }
1486 });
1487};
943caaef
JA
1488/**
1489* Removes the close image from the tab
1490*/
a3364605 1491Tabview.prototype.clearPartImage = function () {
2b9dcc03 1492 $('#kiwi .toolbars .tab_part').remove();
a3364605 1493};
943caaef
JA
1494/**
1495* Sets the tab's icon
1496* @param {String} url The URL of the icon to display
1497*/
3586a76e
D
1498Tabview.prototype.setIcon = function (url) {
1499 this.tab.prepend('<img src="' + url + '" class="icon" />');
1500 this.tab.css('padding-left', '33px');
7af36ece 1501};
943caaef
JA
1502/**
1503* Sets the tab's label
1504*/
3586a76e
D
1505Tabview.prototype.setTabText = function (text) {
1506 $('span', this.tab).text(text);
7af36ece 1507};
943caaef
JA
1508/**
1509* Adds a message to the window.
1510* This method will automatically format the message (bold, underline, colours etc.)
1511* @param {Date} time The timestamp of the message. May be null.
1512* @param {String} nick The origin of the message
1513* @param {String} msg The message to display
1514* @param {String} type The CSS class to assign to the whole message line
1515* @param {String} style Extra CSS commands to apply just to the msg
1516*/
a3364605 1517Tabview.prototype.addMsg = function (time, nick, msg, type, style) {
8343584e
D
1518 var self, tmp, d, re, line_msg;
1519
a3364605 1520 self = this;
8343584e 1521
ca7e63ea 1522 tmp = {msg: msg, time: time, nick: nick, tabview: this.name};
673a7c8f 1523 tmp = kiwi.plugs.run('addmsg', tmp);
c6ef62a3
JA
1524 if (!tmp) {
1525 return;
1526 }
8343584e 1527
ca7e63ea
D
1528
1529 msg = tmp.msg;
1530 time = tmp.time;
1531 nick = tmp.nick;
1532
a3364605
JA
1533 if (time === null) {
1534 d = new Date();
1535 time = d.getHours().toString().lpad(2, "0") + ":" + d.getMinutes().toString().lpad(2, "0") + ":" + d.getSeconds().toString().lpad(2, "0");
1536 }
8343584e 1537
a3364605
JA
1538 // The CSS class (action, topic, notice, etc)
1539 if (typeof type !== "string") {
1540 type = '';
1541 }
8343584e 1542
a3364605
JA
1543 // Make sure we don't have NaN or something
1544 if (typeof msg !== "string") {
1545 msg = '';
1546 }
8343584e 1547
ca7e63ea 1548 // Make the channels clickable
673a7c8f 1549 re = new RegExp('\\B(' + kiwi.gateway.channel_prefix + '[^ ,.\\007]+)', 'g');
a3364605 1550 msg = msg.replace(re, function (match) {
ca7e63ea 1551 return '<a class="chan">' + match + '</a>';
9545f343
JA
1552 });
1553
0a202119 1554 msg = kiwi.front.formatIRCMsg(msg);
8343584e 1555
ca7e63ea
D
1556 // Build up and add the line
1557 line_msg = $('<div class="msg ' + type + '"><div class="time">' + time + '</div><div class="nick">' + nick + '</div><div class="text" style="' + style + '">' + msg + ' </div></div>');
a3364605
JA
1558 this.div.append(line_msg);
1559
182e307e
D
1560 this.msg_count++;
1561 if (this.msg_count > 250) {
1562 $('.msg:first', this.div).remove();
1563 this.msg_count--;
1564 }
1565
a3364605
JA
1566 if (!touchscreen) {
1567 this.scrollBottom();
1568 } else {
1569 touch_scroll.refresh();
1570 //console.log(this.div.attr("scrollHeight") +" - "+ $('#windows').height());
1571 this.scrollBottom();
1572 //if(this.div.attr("scrollHeight") > $('#windows').height()){
1573 // touch_scroll.scrollTo(0, this.div.height());
1574 //}
1575 }
1576};
943caaef
JA
1577/**
1578* Scroll to the bottom of the window
1579*/
a3364605 1580Tabview.prototype.scrollBottom = function () {
4e4ca8e6 1581 var panel = this.panel;
4e4ca8e6 1582 panel[0].scrollTop = panel[0].scrollHeight;
a3364605 1583};
943caaef
JA
1584/**
1585* Change a user's nick on the channel
1586* @param {String} newNick The new nick
1587* @param {String} oldNick The old nick
1588*/
a3364605 1589Tabview.prototype.changeNick = function (newNick, oldNick) {
8343584e
D
1590 var inChan = this.userlist.hasUser(oldNick);
1591 if (inChan) {
1592 this.userlist.renameUser(oldNick, newNick);
673a7c8f 1593 this.addMsg(null, ' ', '=== ' + oldNick + ' is now known as ' + newNick, 'action changenick');
a3364605
JA
1594 }
1595};
943caaef
JA
1596/**
1597* Highlight the tab
1598*/
a3364605
JA
1599Tabview.prototype.highlight = function () {
1600 this.tab.addClass('highlight');
1601};
943caaef
JA
1602/**
1603* Indicate activity on the tab
1604*/
a3364605
JA
1605Tabview.prototype.activity = function () {
1606 this.tab.addClass('activity');
1607};
943caaef
JA
1608/**
1609* Clear the tab's highlight
1610*/
a3364605
JA
1611Tabview.prototype.clearHighlight = function () {
1612 this.tab.removeClass('highlight');
1613 this.tab.removeClass('activity');
1614};
943caaef
JA
1615/**
1616* Change the channel's topic
1617* @param {String} new_topic The new channel topic
1618*/
a3364605
JA
1619Tabview.prototype.changeTopic = function (new_topic) {
1620 this.topic = new_topic;
1621 this.addMsg(null, ' ', '=== Topic for ' + this.name + ' is: ' + new_topic, 'topic');
673a7c8f 1622 if (kiwi.front.cur_channel.name === this.name) {
b41a381f 1623 kiwi.front.ui.setTopicText(new_topic);
a3364605
JA
1624 }
1625};
1fce4b40 1626// Static functions
943caaef
JA
1627/**
1628* Checks to see if a tab by the given name exists
1629* @param {String} name The name to check
1630* @returns {Boolean} True if the tab exists, false otherwise
1631*/
1fce4b40 1632Tabview.tabExists = function (name) {
0b399cb0 1633 return (Tabview.getTab(name) !== null);
1fce4b40 1634};
943caaef
JA
1635/**
1636* Returns the tab which has the given name
1637* @param {String} name The name of the tab to return
1638* @returns {Tabview} The Tabview with the given name, or null if it does not exist
1639*/
1fce4b40 1640Tabview.getTab = function (name) {
0b399cb0
D
1641 var tab;
1642
1643 // Make sure we actually have a name
f93aafe5
JA
1644 if (typeof name !== 'string') {
1645 return null;
1646 }
0b399cb0
D
1647
1648 // Go through each tabview and pick out the matching one
1649 $.each(kiwi.front.tabviews, function (i, item) {
f93aafe5 1650 if (item.name.toLowerCase() === name.toLowerCase()) {
0b399cb0
D
1651 tab = item;
1652 return false;
1653 }
1654 });
1655
1656 // If we we didn't find one, return null instead
1657 tab = tab || null;
1658
1659 return tab;
1fce4b40 1660};
943caaef
JA
1661/**
1662* Returns the tab that corresponds to the server
1663* @retruns {Tabview} The server Tabview
1664*/
1fce4b40 1665Tabview.getServerTab = function () {
0b399cb0 1666 return Tabview.getTab('server');
1fce4b40 1667};
943caaef
JA
1668/**
1669* Returns all tabs
1670* @returns {Array} All of the tabs
1671*/
1fce4b40
JA
1672Tabview.getAllTabs = function () {
1673 return kiwi.front.tabviews;
1674};
943caaef
JA
1675/**
1676* Returns the tab that's currently showing
1677* @returns {Tabview} The tab that's currently showing
1678*/
1fce4b40
JA
1679Tabview.getCurrentTab = function () {
1680 return kiwi.front.cur_channel;
1681};
a3364605 1682
b41a381f
D
1683
1684
1685
1686
1687
5a29866a
JA
1688/**
1689* @constructor
943caaef
JA
1690* Floating message box
1691* @param {String} classname The CSS classname to apply to the box
5a29866a 1692*/
a3364605
JA
1693var Box = function (classname) {
1694 this.id = randomString(10);
1695 var tmp = $('<div id="' + this.id + '" class="box ' + classname + '"><div class="boxarea"></div></div>');
1696 $('#kiwi').append(tmp);
1697 this.box = $('#' + this.id);
1698 this.content = $('#' + this.id + ' .boxarea');
8343584e 1699
a3364605
JA
1700 this.box.draggable({ stack: ".box" });
1701 this.content.click(function () {});
1702 //this.box.click(function(){ $(this)..css });
1703};
1704Box.prototype.create = function (name, classname) {
8343584e 1705
a3364605
JA
1706};
1707Box.prototype.id = null;
1708Box.prototype.box = null;
1709Box.prototype.content = null;
1710Box.prototype.destroy = function () {
1711 var name;
1712 this.box.remove();
673a7c8f
D
1713 for (name in kiwi.front.boxes) {
1714 if (kiwi.front.boxes[name].id === this.id) {
1715 delete kiwi.front.boxes[name];
a3364605
JA
1716 }
1717 }
1718};
8343584e 1719Box.prototype.height = function () {
a3364605
JA
1720 return this.box.height();
1721};