81c92288f75caafe21973239df3dda74c28c479b
[KiwiIRC.git] / client / assets / dev / view.js
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 */
3
4 _kiwi.view.MemberList = Backbone.View.extend({
5 tagName: "ul",
6 events: {
7 "click .nick": "nickClick"
8 },
9 initialize: function (options) {
10 this.model.bind('all', this.render, this);
11 $(this.el).appendTo('#memberlists');
12 },
13 render: function () {
14 var $this = $(this.el);
15 $this.empty();
16 this.model.forEach(function (member) {
17 $('<li><a class="nick"><span class="prefix">' + member.get("prefix") + '</span>' + member.get("nick") + '</a></li>')
18 .appendTo($this)
19 .data('member', member);
20 });
21 },
22 nickClick: function (x) {
23 var target = $(x.currentTarget).parent('li'),
24 member = target.data('member'),
25 userbox = new _kiwi.view.UserBox();
26
27 userbox.member = member;
28 $('.userbox', this.$el).remove();
29 target.append(userbox.$el);
30 },
31 show: function () {
32 $('#memberlists').children().removeClass('active');
33 $(this.el).addClass('active');
34 }
35 });
36
37
38
39 _kiwi.view.UserBox = Backbone.View.extend({
40 events: {
41 'click .query': 'queryClick',
42 'click .info': 'infoClick',
43 'click .slap': 'slapClick'
44 },
45
46 initialize: function () {
47 this.$el = $($('#tmpl_userbox').html());
48 },
49
50 queryClick: function (event) {
51 var panel = new _kiwi.model.Query({name: this.member.get('nick')});
52 _kiwi.app.panels.add(panel);
53 panel.view.show();
54 },
55
56 infoClick: function (event) {
57 _kiwi.app.controlbox.processInput('/whois ' + this.member.get('nick'));
58 },
59
60 slapClick: function (event) {
61 _kiwi.app.controlbox.processInput('/slap ' + this.member.get('nick'));
62 }
63 });
64
65 _kiwi.view.NickChangeBox = Backbone.View.extend({
66 events: {
67 'submit': 'changeNick',
68 'click .cancel': 'close'
69 },
70
71 initialize: function () {
72 this.$el = $($('#tmpl_nickchange').html());
73 },
74
75 render: function () {
76 // Add the UI component and give it focus
77 _kiwi.app.controlbox.$el.prepend(this.$el);
78 this.$el.find('input').focus();
79
80 this.$el.css('bottom', _kiwi.app.controlbox.$el.outerHeight(true));
81 },
82
83 close: function () {
84 this.$el.remove();
85
86 },
87
88 changeNick: function (event) {
89 var that = this;
90 _kiwi.gateway.changeNick(this.$el.find('input').val(), function (err, val) {
91 that.close();
92 });
93 return false;
94 }
95 });
96
97 _kiwi.view.ServerSelect = function () {
98 // Are currently showing all the controlls or just a nick_change box?
99 var state = 'all';
100
101 var model = Backbone.View.extend({
102 events: {
103 'submit form': 'submitForm',
104 'click .show_more': 'showMore'
105 },
106
107 initialize: function () {
108 this.$el = $($('#tmpl_server_select').html());
109
110 _kiwi.gateway.bind('onconnect', this.networkConnected, this);
111 _kiwi.gateway.bind('connecting', this.networkConnecting, this);
112
113 _kiwi.gateway.bind('onirc_error', function (data) {
114 $('button', this.$el).attr('disabled', null);
115
116 if (data.error == 'nickname_in_use') {
117 this.setStatus('Nickname already taken');
118 this.show('nick_change');
119 }
120 }, this);
121 },
122
123 submitForm: function (event) {
124 if (state === 'nick_change') {
125 this.submitNickChange(event);
126 } else {
127 this.submitLogin(event);
128 }
129
130 $('button', this.$el).attr('disabled', 1);
131 return false;
132 },
133
134 submitLogin: function (event) {
135 // If submitting is disabled, don't do anything
136 if ($('button', this.$el).attr('disabled')) return;
137
138 var values = {
139 nick: $('.nick', this.$el).val(),
140 server: $('.server', this.$el).val(),
141 port: $('.port', this.$el).val(),
142 ssl: $('.ssl', this.$el).prop('checked'),
143 password: $('.password', this.$el).val(),
144 channel: $('.channel', this.$el).val(),
145 channel_key: $('.channel_key', this.$el).val()
146 };
147
148 this.trigger('server_connect', values);
149 },
150
151 submitNickChange: function (event) {
152 _kiwi.gateway.changeNick($('.nick', this.$el).val());
153 this.networkConnecting();
154 },
155
156 showMore: function (event) {
157 $('.more', this.$el).slideDown('fast');
158 $('.server', this.$el).select();
159 },
160
161 populateFields: function (defaults) {
162 var nick, server, port, channel, channel_key, ssl, password;
163
164 defaults = defaults || {};
165
166 nick = defaults.nick || '';
167 server = defaults.server || '';
168 port = defaults.port || 6667;
169 ssl = defaults.ssl || 0;
170 password = defaults.password || '';
171 channel = defaults.channel || '';
172 channel_key = defaults.channel_key || '';
173
174 $('.nick', this.$el).val(nick);
175 $('.server', this.$el).val(server);
176 $('.port', this.$el).val(port);
177 $('.ssl', this.$el).prop('checked', ssl);
178 $('.password', this.$el).val(password);
179 $('.channel', this.$el).val(channel);
180 $('.channel_key', this.$el).val(channel_key);
181 },
182
183 hide: function () {
184 this.$el.slideUp();
185 },
186
187 show: function (new_state) {
188 new_state = new_state || 'all';
189
190 this.$el.show();
191
192 if (new_state === 'all') {
193 $('.show_more', this.$el).show();
194
195 } else if (new_state === 'more') {
196 $('.more', this.$el).slideDown('fast');
197
198 } else if (new_state === 'nick_change') {
199 $('.more', this.$el).hide();
200 $('.show_more', this.$el).hide();
201 }
202
203 state = new_state;
204 },
205
206 setStatus: function (text, class_name) {
207 $('.status', this.$el)
208 .text(text)
209 .attr('class', 'status')
210 .addClass(class_name)
211 .show();
212 },
213 clearStatus: function () {
214 $('.status', this.$el).hide();
215 },
216
217 networkConnected: function (event) {
218 this.setStatus('Connected :)', 'ok');
219 $('form', this.$el).hide();
220 },
221
222 networkConnecting: function (event) {
223 this.setStatus('Connecting..', 'ok');
224 },
225
226 showError: function (event) {
227 this.setStatus('Error connecting', 'error');
228 $('button', this.$el).attr('disabled', null);
229 this.show();
230 }
231 });
232
233
234 return new model(arguments);
235 };
236
237
238 _kiwi.view.Panel = Backbone.View.extend({
239 tagName: "div",
240 className: "messages",
241 events: {
242 "click .chan": "chanClick"
243 },
244
245 initialize: function (options) {
246 this.initializePanel(options);
247 },
248
249 initializePanel: function (options) {
250 this.$el.css('display', 'none');
251 options = options || {};
252
253 // Containing element for this panel
254 if (options.container) {
255 this.$container = $(options.container);
256 } else {
257 this.$container = $('#panels .container1');
258 }
259
260 this.$el.appendTo(this.$container);
261
262 this.alert_level = 0;
263
264 this.model.bind('msg', this.newMsg, this);
265 this.msg_count = 0;
266
267 this.model.set({"view": this}, {"silent": true});
268 },
269
270 render: function () {
271 this.$el.empty();
272 this.model.get("backscroll").forEach(this.newMsg);
273 },
274 newMsg: function (msg) {
275 // TODO: make sure that the message pane is scrolled to the bottom (Or do we? ~Darren)
276 var re, line_msg, $this = this.$el,
277 nick_colour_hex;
278
279 // Escape any HTML that may be in here
280 msg.msg = $('<div />').text(msg.msg).html();
281
282 // Make the channels clickable
283 re = new RegExp('\\B([' + _kiwi.gateway.get('channel_prefix') + '][^ ,.\\007]+)', 'g');
284 msg.msg = msg.msg.replace(re, function (match) {
285 return '<a class="chan">' + match + '</a>';
286 });
287
288
289 // Make links clickable
290 msg.msg = msg.msg.replace(/((https?\:\/\/|ftp\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]*))?/gi, function (url) {
291 var nice;
292
293 // Add the http is no protoocol was found
294 if (url.match(/^www\./)) {
295 url = 'http://' + url;
296 }
297
298 nice = url;
299 if (nice.length > 100) {
300 nice = nice.substr(0, 100) + '...';
301 }
302
303 return '<a class="link_ext" target="_blank" rel="nofollow" href="' + url + '">' + nice + '</a>';
304 });
305
306
307 // Convert IRC formatting into HTML formatting
308 msg.msg = formatIRCMsg(msg.msg);
309
310
311 // Add some colours to the nick (Method based on IRSSIs nickcolor.pl)
312 nick_colour_hex = (function (nick) {
313 var nick_int = 0, rgb;
314
315 _.map(nick.split(''), function (i) { nick_int += i.charCodeAt(0); });
316 rgb = hsl2rgb(nick_int % 255, 70, 35);
317 rgb = rgb[2] | (rgb[1] << 8) | (rgb[0] << 16);
318
319 return '#' + rgb.toString(16);
320 })(msg.nick);
321
322 msg.nick_style = 'color:' + nick_colour_hex + ';';
323
324 // Build up and add the line
325 line_msg = '<div class="msg <%= type %>"><div class="time"><%- time %></div><div class="nick" style="<%= nick_style %>"><%- nick %></div><div class="text" style="<%= style %>"><%= msg %> </div></div>';
326 $this.append(_.template(line_msg, msg));
327
328 // Activity/alerts based on the type of new message
329 if (msg.type.match(/^action /)) {
330 this.alert('action');
331 } else if (msg.msg.indexOf(_kiwi.gateway.get('nick')) > -1) {
332 _kiwi.app.view.alertWindow('* People are talking!');
333 this.alert('highlight');
334 } else {
335 // If this is the active panel, send an alert out
336 if (this.model.isActive()) {
337 _kiwi.app.view.alertWindow('* People are talking!');
338 }
339 this.alert('activity');
340 }
341
342 this.scrollToBottom();
343
344 // Make sure our DOM isn't getting too large (Acts as scrollback)
345 this.msg_count++;
346 if (this.msg_count > (parseInt(_kiwi.global.settings.get('scrollback'), 10) || 250)) {
347 $('.msg:first', this.$el).remove();
348 this.msg_count--;
349 }
350 },
351 chanClick: function (event) {
352 if (event.target) {
353 _kiwi.gateway.join($(event.target).text());
354 } else {
355 // IE...
356 _kiwi.gateway.join($(event.srcElement).text());
357 }
358 },
359 show: function () {
360 var $this = this.$el;
361
362 // Hide all other panels and show this one
363 this.$container.children().css('display', 'none');
364 $this.css('display', 'block');
365
366 // Show this panels memberlist
367 var members = this.model.get("members");
368 if (members) {
369 $('#memberlists').show();
370 members.view.show();
371 } else {
372 // Memberlist not found for this panel, hide any active ones
373 $('#memberlists').hide().children().removeClass('active');
374 }
375
376 _kiwi.app.view.doLayout();
377 this.alert('none');
378
379 this.trigger('active', this.model);
380 _kiwi.app.panels.trigger('active', this.model);
381
382 this.scrollToBottom(true);
383 },
384
385
386 alert: function (level) {
387 // No need to highlight if this si the active panel
388 if (this.model == _kiwi.app.panels.active) return;
389
390 var types, type_idx;
391 types = ['none', 'action', 'activity', 'highlight'];
392
393 // Default alert level
394 level = level || 'none';
395
396 // If this alert level does not exist, assume clearing current level
397 type_idx = _.indexOf(types, level);
398 if (!type_idx) {
399 level = 'none';
400 type_idx = 0;
401 }
402
403 // Only 'upgrade' the alert. Never down (unless clearing)
404 if (type_idx !== 0 && type_idx <= this.alert_level) {
405 return;
406 }
407
408 // Clear any existing levels
409 this.model.tab.removeClass(function (i, css) {
410 return (css.match(/\balert_\S+/g) || []).join(' ');
411 });
412
413 // Add the new level if there is one
414 if (level !== 'none') {
415 this.model.tab.addClass('alert_' + level);
416 }
417
418 this.alert_level = type_idx;
419 },
420
421
422 // Scroll to the bottom of the panel
423 scrollToBottom: function (force_down) {
424 // If this isn't the active panel, don't scroll
425 if (this.model !== _kiwi.app.panels.active) return;
426
427 // Don't scroll down if we're scrolled up the panel a little
428 if (force_down || this.$container.scrollTop() + this.$container.height() > this.$el.outerHeight() - 150) {
429 this.$container[0].scrollTop = this.$container[0].scrollHeight;
430 }
431 }
432 });
433
434 _kiwi.view.Applet = _kiwi.view.Panel.extend({
435 className: 'applet',
436 initialize: function (options) {
437 this.initializePanel(options);
438 }
439 });
440
441 _kiwi.view.Channel = _kiwi.view.Panel.extend({
442 initialize: function (options) {
443 this.initializePanel(options);
444 this.model.bind('change:topic', this.topic, this);
445 },
446
447 topic: function (topic) {
448 if (typeof topic !== 'string' || !topic) {
449 topic = this.model.get("topic");
450 }
451
452 this.model.addMsg('', '== Topic for ' + this.model.get('name') + ' is: ' + topic, 'topic');
453
454 // If this is the active channel then update the topic bar
455 if (_kiwi.app.panels.active === this) {
456 _kiwi.app.topicbar.setCurrentTopic(this.model.get("topic"));
457 }
458 }
459 });
460
461 // Model for this = _kiwi.model.PanelList
462 _kiwi.view.Tabs = Backbone.View.extend({
463 events: {
464 'click li': 'tabClick',
465 'click li .part': 'partClick'
466 },
467
468 initialize: function () {
469 this.model.on("add", this.panelAdded, this);
470 this.model.on("remove", this.panelRemoved, this);
471 this.model.on("reset", this.render, this);
472
473 this.model.on('active', this.panelActive, this);
474
475 this.tabs_applets = $('ul.applets', this.$el);
476 this.tabs_msg = $('ul.channels', this.$el);
477
478 _kiwi.gateway.on('change:name', function (gateway, new_val) {
479 $('span', this.model.server.tab).text(new_val);
480 }, this);
481 },
482 render: function () {
483 var that = this;
484
485 this.tabs_msg.empty();
486
487 // Add the server tab first
488 this.model.server.tab
489 .data('panel_id', this.model.server.cid)
490 .appendTo(this.tabs_msg);
491
492 // Go through each panel adding its tab
493 this.model.forEach(function (panel) {
494 // If this is the server panel, ignore as it's already added
495 if (panel == that.model.server) return;
496
497 panel.tab
498 .data('panel_id', panel.cid)
499 .appendTo(panel.isApplet() ? this.tabs_applets : this.tabs_msg);
500 });
501
502 _kiwi.app.view.doLayout();
503 },
504
505 updateTabTitle: function (panel, new_title) {
506 $('span', panel.tab).text(new_title);
507 },
508
509 panelAdded: function (panel) {
510 // Add a tab to the panel
511 panel.tab = $('<li><span>' + (panel.get('title') || panel.get('name')) + '</span></li>');
512
513 if (panel.isServer()) {
514 panel.tab.addClass('server');
515 }
516
517 panel.tab.data('panel_id', panel.cid)
518 .appendTo(panel.isApplet() ? this.tabs_applets : this.tabs_msg);
519
520 panel.bind('change:title', this.updateTabTitle);
521 _kiwi.app.view.doLayout();
522 },
523 panelRemoved: function (panel) {
524 panel.tab.remove();
525 delete panel.tab;
526
527 _kiwi.app.view.doLayout();
528 },
529
530 panelActive: function (panel) {
531 // Remove any existing tabs or part images
532 $('.part', this.$el).remove();
533 this.tabs_applets.children().removeClass('active');
534 this.tabs_msg.children().removeClass('active');
535
536 panel.tab.addClass('active');
537
538 // Only show the part image on non-server tabs
539 if (!panel.isServer()) {
540 panel.tab.append('<span class="part"></span>');
541 }
542 },
543
544 tabClick: function (e) {
545 var tab = $(e.currentTarget);
546
547 var panel = this.model.getByCid(tab.data('panel_id'));
548 if (!panel) {
549 // A panel wasn't found for this tab... wadda fuck
550 return;
551 }
552
553 panel.view.show();
554 },
555
556 partClick: function (e) {
557 var tab = $(e.currentTarget).parent();
558 var panel = this.model.getByCid(tab.data('panel_id'));
559
560 // Only need to part if it's a channel
561 // If the nicklist is empty, we haven't joined the channel as yet
562 if (panel.isChannel() && panel.get('members').models.length > 0) {
563 _kiwi.gateway.part(panel.get('name'));
564 } else {
565 panel.close();
566 }
567 },
568
569 next: function () {
570 var next = _kiwi.app.panels.active.tab.next();
571 if (!next.length) next = $('li:first', this.tabs_msgs);
572
573 next.click();
574 },
575 prev: function () {
576 var prev = _kiwi.app.panels.active.tab.prev();
577 if (!prev.length) prev = $('li:last', this.tabs_msgs);
578
579 prev.click();
580 }
581 });
582
583
584
585 _kiwi.view.TopicBar = Backbone.View.extend({
586 events: {
587 'keydown div': 'process'
588 },
589
590 initialize: function () {
591 _kiwi.app.panels.bind('active', function (active_panel) {
592 // If it's a channel topic, update and make editable
593 if (active_panel.isChannel()) {
594 this.setCurrentTopic(active_panel.get('topic') || '');
595 this.$el.find('div').attr('contentEditable', true);
596
597 } else {
598 // Not a channel topic.. clear and make uneditable
599 this.$el.find('div').attr('contentEditable', false)
600 .text('');
601 }
602 }, this);
603 },
604
605 process: function (ev) {
606 var inp = $(ev.currentTarget),
607 inp_val = inp.text();
608
609 // Only allow topic editing if this is a channel panel
610 if (!_kiwi.app.panels.active.isChannel()) {
611 return false;
612 }
613
614 // If hit return key, update the current topic
615 if (ev.keyCode === 13) {
616 _kiwi.gateway.topic(_kiwi.app.panels.active.get('name'), inp_val);
617 return false;
618 }
619 },
620
621 setCurrentTopic: function (new_topic) {
622 new_topic = new_topic || '';
623
624 // We only want a plain text version
625 $('div', this.$el).html(formatIRCMsg(_.escape(new_topic)));
626 }
627 });
628
629
630
631 _kiwi.view.ControlBox = Backbone.View.extend({
632 events: {
633 'keydown .inp': 'process',
634 'click .nick': 'showNickChange'
635 },
636
637 initialize: function () {
638 var that = this;
639
640 this.buffer = []; // Stores previously run commands
641 this.buffer_pos = 0; // The current position in the buffer
642
643 this.preprocessor = new InputPreProcessor();
644 this.preprocessor.recursive_depth = 5;
645
646 // Hold tab autocomplete data
647 this.tabcomplete = {active: false, data: [], prefix: ''};
648
649 _kiwi.gateway.bind('change:nick', function () {
650 $('.nick', that.$el).text(this.get('nick'));
651 });
652 },
653
654 showNickChange: function (ev) {
655 (new _kiwi.view.NickChangeBox()).render();
656 },
657
658 process: function (ev) {
659 var that = this,
660 inp = $(ev.currentTarget),
661 inp_val = inp.val(),
662 meta;
663
664 if (navigator.appVersion.indexOf("Mac") !== -1) {
665 meta = ev.ctrlKey;
666 } else {
667 meta = ev.altKey;
668 }
669
670 // If not a tab key, reset the tabcomplete data
671 if (this.tabcomplete.active && ev.keyCode !== 9) {
672 this.tabcomplete.active = false;
673 this.tabcomplete.data = [];
674 this.tabcomplete.prefix = '';
675 }
676
677 switch (true) {
678 case (ev.keyCode === 13): // return
679 inp_val = inp_val.trim();
680
681 if (inp_val) {
682 $.each(inp_val.split('\n'), function (idx, line) {
683 that.processInput(line);
684 });
685
686 this.buffer.push(inp_val);
687 this.buffer_pos = this.buffer.length;
688 }
689
690 inp.val('');
691 return false;
692
693 break;
694
695 case (ev.keyCode === 38): // up
696 if (this.buffer_pos > 0) {
697 this.buffer_pos--;
698 inp.val(this.buffer[this.buffer_pos]);
699 }
700 break;
701
702 case (ev.keyCode === 40): // down
703 if (this.buffer_pos < this.buffer.length) {
704 this.buffer_pos++;
705 inp.val(this.buffer[this.buffer_pos]);
706 }
707 break;
708
709 case (ev.keyCode === 37 && meta): // left
710 _kiwi.app.panels.view.prev();
711 return false;
712
713 case (ev.keyCode === 39 && meta): // right
714 _kiwi.app.panels.view.next();
715 return false;
716
717 case (ev.keyCode === 9): // tab
718 this.tabcomplete.active = true;
719 if (_.isEqual(this.tabcomplete.data, [])) {
720 // Get possible autocompletions
721 var ac_data = [];
722 $.each(_kiwi.app.panels.active.get('members').models, function (i, member) {
723 if (!member) return;
724 ac_data.push(member.get('nick'));
725 });
726 ac_data = _.sortBy(ac_data, function (nick) {
727 return nick;
728 });
729 this.tabcomplete.data = ac_data;
730 }
731
732 if (inp_val[inp[0].selectionStart - 1] === ' ') {
733 return false;
734 }
735
736 (function () {
737 var tokens = inp_val.substring(0, inp[0].selectionStart).split(' '),
738 val,
739 p1,
740 newnick,
741 range,
742 nick = tokens[tokens.length - 1];
743 if (this.tabcomplete.prefix === '') {
744 this.tabcomplete.prefix = nick;
745 }
746
747 this.tabcomplete.data = _.select(this.tabcomplete.data, function (n) {
748 return (n.toLowerCase().indexOf(that.tabcomplete.prefix.toLowerCase()) === 0);
749 });
750
751 if (this.tabcomplete.data.length > 0) {
752 p1 = inp[0].selectionStart - (nick.length);
753 val = inp_val.substr(0, p1);
754 newnick = this.tabcomplete.data.shift();
755 this.tabcomplete.data.push(newnick);
756 val += newnick;
757 val += inp_val.substr(inp[0].selectionStart);
758 inp.val(val);
759
760 if (inp[0].setSelectionRange) {
761 inp[0].setSelectionRange(p1 + newnick.length, p1 + newnick.length);
762 } else if (inp[0].createTextRange) { // not sure if this bit is actually needed....
763 range = inp[0].createTextRange();
764 range.collapse(true);
765 range.moveEnd('character', p1 + newnick.length);
766 range.moveStart('character', p1 + newnick.length);
767 range.select();
768 }
769 }
770 }).apply(this);
771 return false;
772 }
773 },
774
775
776 processInput: function (command_raw) {
777 var command, params,
778 pre_processed;
779
780 // The default command
781 if (command_raw[0] !== '/') {
782 command_raw = '/msg ' + _kiwi.app.panels.active.get('name') + ' ' + command_raw;
783 }
784
785 // Process the raw command for any aliases
786 this.preprocessor.vars.server = _kiwi.gateway.get('name');
787 this.preprocessor.vars.channel = _kiwi.app.panels.active.get('name');
788 this.preprocessor.vars.destination = this.preprocessor.vars.channel;
789 command_raw = this.preprocessor.process(command_raw);
790
791 // Extract the command and parameters
792 params = command_raw.split(' ');
793 if (params[0][0] === '/') {
794 command = params[0].substr(1).toLowerCase();
795 params = params.splice(1, params.length - 1);
796 } else {
797 // Default command
798 command = 'msg';
799 params.unshift(_kiwi.app.panels.active.get('name'));
800 }
801
802 // Trigger the command events
803 this.trigger('command', {command: command, params: params});
804 this.trigger('command_' + command, {command: command, params: params});
805
806 // If we didn't have any listeners for this event, fire a special case
807 // TODO: This feels dirty. Should this really be done..?
808 if (!this._callbacks['command_' + command]) {
809 this.trigger('unknown_command', {command: command, params: params});
810 }
811 }
812 });
813
814
815
816
817 _kiwi.view.StatusMessage = Backbone.View.extend({
818 initialize: function () {
819 this.$el.hide();
820
821 // Timer for hiding the message after X seconds
822 this.tmr = null;
823 },
824
825 text: function (text, opt) {
826 // Defaults
827 opt = opt || {};
828 opt.type = opt.type || '';
829 opt.timeout = opt.timeout || 5000;
830
831 this.$el.text(text).attr('class', opt.type);
832 this.$el.slideDown(_kiwi.app.view.doLayout);
833
834 if (opt.timeout) this.doTimeout(opt.timeout);
835 },
836
837 html: function (html, opt) {
838 // Defaults
839 opt = opt || {};
840 opt.type = opt.type || '';
841 opt.timeout = opt.timeout || 5000;
842
843 this.$el.html(text).attr('class', opt.type);
844 this.$el.slideDown(_kiwi.app.view.doLayout);
845
846 if (opt.timeout) this.doTimeout(opt.timeout);
847 },
848
849 hide: function () {
850 this.$el.slideUp(_kiwi.app.view.doLayout);
851 },
852
853 doTimeout: function (length) {
854 if (this.tmr) clearTimeout(this.tmr);
855 var that = this;
856 this.tmr = setTimeout(function () { that.hide(); }, length);
857 }
858 });
859
860
861
862
863 _kiwi.view.ResizeHandler = Backbone.View.extend({
864 events: {
865 'mousedown': 'startDrag',
866 'mouseup': 'stopDrag'
867 },
868
869 initialize: function () {
870 this.dragging = false;
871 this.starting_width = {};
872
873 $(window).on('mousemove', $.proxy(this.onDrag, this));
874 },
875
876 startDrag: function (event) {
877 this.dragging = true;
878 },
879
880 stopDrag: function (event) {
881 this.dragging = false;
882 },
883
884 onDrag: function (event) {
885 if (!this.dragging) return;
886
887 this.$el.css('left', event.clientX - (this.$el.outerWidth(true) / 2));
888 $('#memberlists').css('width', this.$el.parent().width() - (this.$el.position().left + this.$el.outerWidth()));
889 _kiwi.app.view.doLayout();
890 }
891 });
892
893
894
895 _kiwi.view.AppToolbar = Backbone.View.extend({
896 events: {
897 'click .settings': 'clickSettings'
898 },
899
900 initialize: function () {
901 },
902
903 clickSettings: function (event) {
904 _kiwi.app.controlbox.processInput('/settings');
905 }
906 });
907
908
909
910 _kiwi.view.Application = Backbone.View.extend({
911 initialize: function () {
912 $(window).resize(this.doLayout);
913 $('#toolbar').resize(this.doLayout);
914 $('#controlbox').resize(this.doLayout);
915
916 // Change the theme when the config is changed
917 _kiwi.global.settings.on('change:theme', this.updateTheme, this);
918 this.updateTheme();
919
920 this.doLayout();
921
922 $(document).keydown(this.setKeyFocus);
923
924 // Confirmation require to leave the page
925 window.onbeforeunload = function () {
926 if (_kiwi.gateway.isConnected()) {
927 return 'This will close all KiwiIRC conversations. Are you sure you want to close this window?';
928 }
929 };
930 },
931
932
933
934 updateTheme: function (theme_name) {
935 // If called by the settings callback, get the correct new_value
936 if (theme_name === _kiwi.global.settings) {
937 theme_name = arguments[1];
938 }
939
940 // If we have no theme specified, get it from the settings
941 if (!theme_name) theme_name = _kiwi.global.settings.get('theme');
942
943 // Clear any current theme
944 this.$el.removeClass(function (i, css) {
945 return (css.match (/\btheme_\S+/g) || []).join(' ');
946 });
947
948 // Apply the new theme
949 this.$el.addClass('theme_' + (theme_name || 'default'));
950 },
951
952
953 // Globally shift focus to the command input box on a keypress
954 setKeyFocus: function (ev) {
955 // If we're copying text, don't shift focus
956 if (ev.ctrlKey || ev.altKey || ev.metaKey) {
957 return;
958 }
959
960 // If we're typing into an input box somewhere, ignore
961 if ((ev.target.tagName.toLowerCase() === 'input') || $(ev.target).attr('contenteditable')) {
962 return;
963 }
964
965 $('#controlbox .inp').focus();
966 },
967
968
969 doLayout: function () {
970 var el_panels = $('#panels');
971 var el_memberlists = $('#memberlists');
972 var el_toolbar = $('#toolbar');
973 var el_controlbox = $('#controlbox');
974 var el_resize_handle = $('#memberlists_resize_handle');
975
976 var css_heights = {
977 top: el_toolbar.outerHeight(true),
978 bottom: el_controlbox.outerHeight(true)
979 };
980
981
982 // If any elements are not visible, full size the panals instead
983 if (!el_toolbar.is(':visible')) {
984 css_heights.top = 0;
985 }
986
987 if (!el_controlbox.is(':visible')) {
988 css_heights.bottom = 0;
989 }
990
991 // Apply the CSS sizes
992 el_panels.css(css_heights);
993 el_memberlists.css(css_heights);
994 el_resize_handle.css(css_heights);
995
996 // Set the panels width depending on the memberlist visibility
997 if (el_memberlists.css('display') != 'none') {
998 // Handle + panels to the side of the memberlist
999 el_panels.css('right', el_memberlists.outerWidth(true) + el_resize_handle.outerWidth(true));
1000 el_resize_handle.css('left', el_memberlists.position().left - el_resize_handle.outerWidth(true));
1001 } else {
1002 // Memberlist is hidden so handle + panels to the right edge
1003 el_panels.css('right', el_resize_handle.outerWidth(true));
1004 el_resize_handle.css('left', el_panels.outerWidth(true));
1005 }
1006 },
1007
1008
1009 alertWindow: function (title) {
1010 if (!this.alertWindowTimer) {
1011 this.alertWindowTimer = new (function () {
1012 var that = this;
1013 var tmr;
1014 var has_focus = true;
1015 var state = 0;
1016 var default_title = 'Kiwi IRC';
1017 var title = 'Kiwi IRC';
1018
1019 this.setTitle = function (new_title) {
1020 new_title = new_title || default_title;
1021 window.document.title = new_title;
1022 return new_title;
1023 };
1024
1025 this.start = function (new_title) {
1026 // Don't alert if we already have focus
1027 if (has_focus) return;
1028
1029 title = new_title;
1030 if (tmr) return;
1031 tmr = setInterval(this.update, 1000);
1032 };
1033
1034 this.stop = function () {
1035 // Stop the timer and clear the title
1036 if (tmr) clearInterval(tmr);
1037 tmr = null;
1038 this.setTitle();
1039
1040 // Some browsers don't always update the last title correctly
1041 // Wait a few seconds and then reset
1042 setTimeout(this.reset, 2000);
1043 };
1044
1045 this.reset = function () {
1046 if (tmr) return;
1047 that.setTitle();
1048 };
1049
1050
1051 this.update = function () {
1052 if (state === 0) {
1053 that.setTitle(title);
1054 state = 1;
1055 } else {
1056 that.setTitle();
1057 state = 0;
1058 }
1059 };
1060
1061 $(window).focus(function (event) {
1062 has_focus = true;
1063 that.stop();
1064
1065 // Some browsers don't always update the last title correctly
1066 // Wait a few seconds and then reset
1067 setTimeout(that.reset, 2000);
1068 });
1069
1070 $(window).blur(function (event) {
1071 has_focus = false;
1072 });
1073 })();
1074 }
1075
1076 this.alertWindowTimer.start(title);
1077 },
1078
1079
1080 barsHide: function (instant) {
1081 var that = this;
1082
1083 if (!instant) {
1084 $('#toolbar').slideUp({queue: false, duration: 400, step: this.doLayout});
1085 $('#controlbox').slideUp({queue: false, duration: 400, step: this.doLayout});
1086 } else {
1087 $('#toolbar').slideUp(0);
1088 $('#controlbox').slideUp(0);
1089 this.doLayout();
1090 }
1091 },
1092
1093 barsShow: function (instant) {
1094 var that = this;
1095
1096 if (!instant) {
1097 $('#toolbar').slideDown({queue: false, duration: 400, step: this.doLayout});
1098 $('#controlbox').slideDown({queue: false, duration: 400, step: this.doLayout});
1099 } else {
1100 $('#toolbar').slideDown(0);
1101 $('#controlbox').slideDown(0);
1102 this.doLayout();
1103 }
1104 }
1105 });