81c92288f75caafe21973239df3dda74c28c479b
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 */
4 _kiwi
.view
.MemberList
= Backbone
.View
.extend({
7 "click .nick": "nickClick"
9 initialize: function (options
) {
10 this.model
.bind('all', this.render
, this);
11 $(this.el
).appendTo('#memberlists');
14 var $this = $(this.el
);
16 this.model
.forEach(function (member
) {
17 $('<li><a class="nick"><span class="prefix">' + member
.get("prefix") + '</span>' + member
.get("nick") + '</a></li>')
19 .data('member', member
);
22 nickClick: function (x
) {
23 var target
= $(x
.currentTarget
).parent('li'),
24 member
= target
.data('member'),
25 userbox
= new _kiwi
.view
.UserBox();
27 userbox
.member
= member
;
28 $('.userbox', this.$el
).remove();
29 target
.append(userbox
.$el
);
32 $('#memberlists').children().removeClass('active');
33 $(this.el
).addClass('active');
39 _kiwi
.view
.UserBox
= Backbone
.View
.extend({
41 'click .query': 'queryClick',
42 'click .info': 'infoClick',
43 'click .slap': 'slapClick'
46 initialize: function () {
47 this.$el
= $($('#tmpl_userbox').html());
50 queryClick: function (event
) {
51 var panel
= new _kiwi
.model
.Query({name
: this.member
.get('nick')});
52 _kiwi
.app
.panels
.add(panel
);
56 infoClick: function (event
) {
57 _kiwi
.app
.controlbox
.processInput('/whois ' + this.member
.get('nick'));
60 slapClick: function (event
) {
61 _kiwi
.app
.controlbox
.processInput('/slap ' + this.member
.get('nick'));
65 _kiwi
.view
.NickChangeBox
= Backbone
.View
.extend({
67 'submit': 'changeNick',
68 'click .cancel': 'close'
71 initialize: function () {
72 this.$el
= $($('#tmpl_nickchange').html());
76 // Add the UI component and give it focus
77 _kiwi
.app
.controlbox
.$el
.prepend(this.$el
);
78 this.$el
.find('input').focus();
80 this.$el
.css('bottom', _kiwi
.app
.controlbox
.$el
.outerHeight(true));
88 changeNick: function (event
) {
90 _kiwi
.gateway
.changeNick(this.$el
.find('input').val(), function (err
, val
) {
97 _kiwi
.view
.ServerSelect = function () {
98 // Are currently showing all the controlls or just a nick_change box?
101 var model
= Backbone
.View
.extend({
103 'submit form': 'submitForm',
104 'click .show_more': 'showMore'
107 initialize: function () {
108 this.$el
= $($('#tmpl_server_select').html());
110 _kiwi
.gateway
.bind('onconnect', this.networkConnected
, this);
111 _kiwi
.gateway
.bind('connecting', this.networkConnecting
, this);
113 _kiwi
.gateway
.bind('onirc_error', function (data
) {
114 $('button', this.$el
).attr('disabled', null);
116 if (data
.error
== 'nickname_in_use') {
117 this.setStatus('Nickname already taken');
118 this.show('nick_change');
123 submitForm: function (event
) {
124 if (state
=== 'nick_change') {
125 this.submitNickChange(event
);
127 this.submitLogin(event
);
130 $('button', this.$el
).attr('disabled', 1);
134 submitLogin: function (event
) {
135 // If submitting is disabled, don't do anything
136 if ($('button', this.$el
).attr('disabled')) return;
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()
148 this.trigger('server_connect', values
);
151 submitNickChange: function (event
) {
152 _kiwi
.gateway
.changeNick($('.nick', this.$el
).val());
153 this.networkConnecting();
156 showMore: function (event
) {
157 $('.more', this.$el
).slideDown('fast');
158 $('.server', this.$el
).select();
161 populateFields: function (defaults
) {
162 var nick
, server
, port
, channel
, channel_key
, ssl
, password
;
164 defaults
= defaults
|| {};
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
|| '';
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
);
187 show: function (new_state
) {
188 new_state
= new_state
|| 'all';
192 if (new_state
=== 'all') {
193 $('.show_more', this.$el
).show();
195 } else if (new_state
=== 'more') {
196 $('.more', this.$el
).slideDown('fast');
198 } else if (new_state
=== 'nick_change') {
199 $('.more', this.$el
).hide();
200 $('.show_more', this.$el
).hide();
206 setStatus: function (text
, class_name
) {
207 $('.status', this.$el
)
209 .attr('class', 'status')
210 .addClass(class_name
)
213 clearStatus: function () {
214 $('.status', this.$el
).hide();
217 networkConnected: function (event
) {
218 this.setStatus('Connected :)', 'ok');
219 $('form', this.$el
).hide();
222 networkConnecting: function (event
) {
223 this.setStatus('Connecting..', 'ok');
226 showError: function (event
) {
227 this.setStatus('Error connecting', 'error');
228 $('button', this.$el
).attr('disabled', null);
234 return new model(arguments
);
238 _kiwi
.view
.Panel
= Backbone
.View
.extend({
240 className
: "messages",
242 "click .chan": "chanClick"
245 initialize: function (options
) {
246 this.initializePanel(options
);
249 initializePanel: function (options
) {
250 this.$el
.css('display', 'none');
251 options
= options
|| {};
253 // Containing element for this panel
254 if (options
.container
) {
255 this.$container
= $(options
.container
);
257 this.$container
= $('#panels .container1');
260 this.$el
.appendTo(this.$container
);
262 this.alert_level
= 0;
264 this.model
.bind('msg', this.newMsg
, this);
267 this.model
.set({"view": this}, {"silent": true});
270 render: function () {
272 this.model
.get("backscroll").forEach(this.newMsg
);
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
,
279 // Escape any HTML that may be in here
280 msg
.msg
= $('<div />').text(msg
.msg
).html();
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>';
289 // Make links clickable
290 msg
.msg
= msg
.msg
.replace(/((https?\:\/\/|ftp\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]*))?/gi, function (url
) {
293 // Add the http is no protoocol was found
294 if (url
.match(/^www\./)) {
295 url
= 'http://' + url
;
299 if (nice
.length
> 100) {
300 nice
= nice
.substr(0, 100) + '...';
303 return '<a class="link_ext" target="_blank" rel="nofollow" href="' + url
+ '">' + nice
+ '</a>';
307 // Convert IRC formatting into HTML formatting
308 msg
.msg
= formatIRCMsg(msg
.msg
);
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
;
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);
319 return '#' + rgb
.toString(16);
322 msg
.nick_style
= 'color:' + nick_colour_hex
+ ';';
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
));
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');
335 // If this is the active panel, send an alert out
336 if (this.model
.isActive()) {
337 _kiwi
.app
.view
.alertWindow('* People are talking!');
339 this.alert('activity');
342 this.scrollToBottom();
344 // Make sure our DOM isn't getting too large (Acts as scrollback)
346 if (this.msg_count
> (parseInt(_kiwi
.global
.settings
.get('scrollback'), 10) || 250)) {
347 $('.msg:first', this.$el
).remove();
351 chanClick: function (event
) {
353 _kiwi
.gateway
.join($(event
.target
).text());
356 _kiwi
.gateway
.join($(event
.srcElement
).text());
360 var $this = this.$el
;
362 // Hide all other panels and show this one
363 this.$container
.children().css('display', 'none');
364 $this.css('display', 'block');
366 // Show this panels memberlist
367 var members
= this.model
.get("members");
369 $('#memberlists').show();
372 // Memberlist not found for this panel, hide any active ones
373 $('#memberlists').hide().children().removeClass('active');
376 _kiwi
.app
.view
.doLayout();
379 this.trigger('active', this.model
);
380 _kiwi
.app
.panels
.trigger('active', this.model
);
382 this.scrollToBottom(true);
386 alert: function (level
) {
387 // No need to highlight if this si the active panel
388 if (this.model
== _kiwi
.app
.panels
.active
) return;
391 types
= ['none', 'action', 'activity', 'highlight'];
393 // Default alert level
394 level
= level
|| 'none';
396 // If this alert level does not exist, assume clearing current level
397 type_idx
= _
.indexOf(types
, level
);
403 // Only 'upgrade' the alert. Never down (unless clearing)
404 if (type_idx
!== 0 && type_idx
<= this.alert_level
) {
408 // Clear any existing levels
409 this.model
.tab
.removeClass(function (i
, css
) {
410 return (css
.match(/\balert_\S+/g) || []).join(' ');
413 // Add the new level if there is one
414 if (level
!== 'none') {
415 this.model
.tab
.addClass('alert_' + level
);
418 this.alert_level
= type_idx
;
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;
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
;
434 _kiwi
.view
.Applet
= _kiwi
.view
.Panel
.extend({
436 initialize: function (options
) {
437 this.initializePanel(options
);
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);
447 topic: function (topic
) {
448 if (typeof topic
!== 'string' || !topic
) {
449 topic
= this.model
.get("topic");
452 this.model
.addMsg('', '== Topic for ' + this.model
.get('name') + ' is: ' + topic
, 'topic');
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"));
461 // Model for this = _kiwi.model.PanelList
462 _kiwi
.view
.Tabs
= Backbone
.View
.extend({
464 'click li': 'tabClick',
465 'click li .part': 'partClick'
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);
473 this.model
.on('active', this.panelActive
, this);
475 this.tabs_applets
= $('ul.applets', this.$el
);
476 this.tabs_msg
= $('ul.channels', this.$el
);
478 _kiwi
.gateway
.on('change:name', function (gateway
, new_val
) {
479 $('span', this.model
.server
.tab
).text(new_val
);
482 render: function () {
485 this.tabs_msg
.empty();
487 // Add the server tab first
488 this.model
.server
.tab
489 .data('panel_id', this.model
.server
.cid
)
490 .appendTo(this.tabs_msg
);
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;
498 .data('panel_id', panel
.cid
)
499 .appendTo(panel
.isApplet() ? this.tabs_applets
: this.tabs_msg
);
502 _kiwi
.app
.view
.doLayout();
505 updateTabTitle: function (panel
, new_title
) {
506 $('span', panel
.tab
).text(new_title
);
509 panelAdded: function (panel
) {
510 // Add a tab to the panel
511 panel
.tab
= $('<li><span>' + (panel
.get('title') || panel
.get('name')) + '</span></li>');
513 if (panel
.isServer()) {
514 panel
.tab
.addClass('server');
517 panel
.tab
.data('panel_id', panel
.cid
)
518 .appendTo(panel
.isApplet() ? this.tabs_applets
: this.tabs_msg
);
520 panel
.bind('change:title', this.updateTabTitle
);
521 _kiwi
.app
.view
.doLayout();
523 panelRemoved: function (panel
) {
527 _kiwi
.app
.view
.doLayout();
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');
536 panel
.tab
.addClass('active');
538 // Only show the part image on non-server tabs
539 if (!panel
.isServer()) {
540 panel
.tab
.append('<span class="part"></span>');
544 tabClick: function (e
) {
545 var tab
= $(e
.currentTarget
);
547 var panel
= this.model
.getByCid(tab
.data('panel_id'));
549 // A panel wasn't found for this tab... wadda fuck
556 partClick: function (e
) {
557 var tab
= $(e
.currentTarget
).parent();
558 var panel
= this.model
.getByCid(tab
.data('panel_id'));
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'));
570 var next
= _kiwi
.app
.panels
.active
.tab
.next();
571 if (!next
.length
) next
= $('li:first', this.tabs_msgs
);
576 var prev
= _kiwi
.app
.panels
.active
.tab
.prev();
577 if (!prev
.length
) prev
= $('li:last', this.tabs_msgs
);
585 _kiwi
.view
.TopicBar
= Backbone
.View
.extend({
587 'keydown div': 'process'
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);
598 // Not a channel topic.. clear and make uneditable
599 this.$el
.find('div').attr('contentEditable', false)
605 process: function (ev
) {
606 var inp
= $(ev
.currentTarget
),
607 inp_val
= inp
.text();
609 // Only allow topic editing if this is a channel panel
610 if (!_kiwi
.app
.panels
.active
.isChannel()) {
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
);
621 setCurrentTopic: function (new_topic
) {
622 new_topic
= new_topic
|| '';
624 // We only want a plain text version
625 $('div', this.$el
).html(formatIRCMsg(_
.escape(new_topic
)));
631 _kiwi
.view
.ControlBox
= Backbone
.View
.extend({
633 'keydown .inp': 'process',
634 'click .nick': 'showNickChange'
637 initialize: function () {
640 this.buffer
= []; // Stores previously run commands
641 this.buffer_pos
= 0; // The current position in the buffer
643 this.preprocessor
= new InputPreProcessor();
644 this.preprocessor
.recursive_depth
= 5;
646 // Hold tab autocomplete data
647 this.tabcomplete
= {active
: false, data
: [], prefix
: ''};
649 _kiwi
.gateway
.bind('change:nick', function () {
650 $('.nick', that
.$el
).text(this.get('nick'));
654 showNickChange: function (ev
) {
655 (new _kiwi
.view
.NickChangeBox()).render();
658 process: function (ev
) {
660 inp
= $(ev
.currentTarget
),
664 if (navigator
.appVersion
.indexOf("Mac") !== -1) {
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
= '';
678 case (ev
.keyCode
=== 13): // return
679 inp_val
= inp_val
.trim();
682 $.each(inp_val
.split('\n'), function (idx
, line
) {
683 that
.processInput(line
);
686 this.buffer
.push(inp_val
);
687 this.buffer_pos
= this.buffer
.length
;
695 case (ev
.keyCode
=== 38): // up
696 if (this.buffer_pos
> 0) {
698 inp
.val(this.buffer
[this.buffer_pos
]);
702 case (ev
.keyCode
=== 40): // down
703 if (this.buffer_pos
< this.buffer
.length
) {
705 inp
.val(this.buffer
[this.buffer_pos
]);
709 case (ev
.keyCode
=== 37 && meta
): // left
710 _kiwi
.app
.panels
.view
.prev();
713 case (ev
.keyCode
=== 39 && meta
): // right
714 _kiwi
.app
.panels
.view
.next();
717 case (ev
.keyCode
=== 9): // tab
718 this.tabcomplete
.active
= true;
719 if (_
.isEqual(this.tabcomplete
.data
, [])) {
720 // Get possible autocompletions
722 $.each(_kiwi
.app
.panels
.active
.get('members').models
, function (i
, member
) {
724 ac_data
.push(member
.get('nick'));
726 ac_data
= _
.sortBy(ac_data
, function (nick
) {
729 this.tabcomplete
.data
= ac_data
;
732 if (inp_val
[inp
[0].selectionStart
- 1] === ' ') {
737 var tokens
= inp_val
.substring(0, inp
[0].selectionStart
).split(' '),
742 nick
= tokens
[tokens
.length
- 1];
743 if (this.tabcomplete
.prefix
=== '') {
744 this.tabcomplete
.prefix
= nick
;
747 this.tabcomplete
.data
= _
.select(this.tabcomplete
.data
, function (n
) {
748 return (n
.toLowerCase().indexOf(that
.tabcomplete
.prefix
.toLowerCase()) === 0);
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
);
757 val
+= inp_val
.substr(inp
[0].selectionStart
);
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
);
776 processInput: function (command_raw
) {
780 // The default command
781 if (command_raw
[0] !== '/') {
782 command_raw
= '/msg ' + _kiwi
.app
.panels
.active
.get('name') + ' ' + command_raw
;
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
);
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);
799 params
.unshift(_kiwi
.app
.panels
.active
.get('name'));
802 // Trigger the command events
803 this.trigger('command', {command
: command
, params
: params
});
804 this.trigger('command_' + command
, {command
: command
, params
: params
});
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
});
817 _kiwi
.view
.StatusMessage
= Backbone
.View
.extend({
818 initialize: function () {
821 // Timer for hiding the message after X seconds
825 text: function (text
, opt
) {
828 opt
.type
= opt
.type
|| '';
829 opt
.timeout
= opt
.timeout
|| 5000;
831 this.$el
.text(text
).attr('class', opt
.type
);
832 this.$el
.slideDown(_kiwi
.app
.view
.doLayout
);
834 if (opt
.timeout
) this.doTimeout(opt
.timeout
);
837 html: function (html
, opt
) {
840 opt
.type
= opt
.type
|| '';
841 opt
.timeout
= opt
.timeout
|| 5000;
843 this.$el
.html(text
).attr('class', opt
.type
);
844 this.$el
.slideDown(_kiwi
.app
.view
.doLayout
);
846 if (opt
.timeout
) this.doTimeout(opt
.timeout
);
850 this.$el
.slideUp(_kiwi
.app
.view
.doLayout
);
853 doTimeout: function (length
) {
854 if (this.tmr
) clearTimeout(this.tmr
);
856 this.tmr
= setTimeout(function () { that
.hide(); }, length
);
863 _kiwi
.view
.ResizeHandler
= Backbone
.View
.extend({
865 'mousedown': 'startDrag',
866 'mouseup': 'stopDrag'
869 initialize: function () {
870 this.dragging
= false;
871 this.starting_width
= {};
873 $(window
).on('mousemove', $.proxy(this.onDrag
, this));
876 startDrag: function (event
) {
877 this.dragging
= true;
880 stopDrag: function (event
) {
881 this.dragging
= false;
884 onDrag: function (event
) {
885 if (!this.dragging
) return;
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();
895 _kiwi
.view
.AppToolbar
= Backbone
.View
.extend({
897 'click .settings': 'clickSettings'
900 initialize: function () {
903 clickSettings: function (event
) {
904 _kiwi
.app
.controlbox
.processInput('/settings');
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
);
916 // Change the theme when the config is changed
917 _kiwi
.global
.settings
.on('change:theme', this.updateTheme
, this);
922 $(document
).keydown(this.setKeyFocus
);
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?';
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];
940 // If we have no theme specified, get it from the settings
941 if (!theme_name
) theme_name
= _kiwi
.global
.settings
.get('theme');
943 // Clear any current theme
944 this.$el
.removeClass(function (i
, css
) {
945 return (css
.match (/\btheme_\S+/g) || []).join(' ');
948 // Apply the new theme
949 this.$el
.addClass('theme_' + (theme_name
|| 'default'));
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
) {
960 // If we're typing into an input box somewhere, ignore
961 if ((ev
.target
.tagName
.toLowerCase() === 'input') || $(ev
.target
).attr('contenteditable')) {
965 $('#controlbox .inp').focus();
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');
977 top
: el_toolbar
.outerHeight(true),
978 bottom
: el_controlbox
.outerHeight(true)
982 // If any elements are not visible, full size the panals instead
983 if (!el_toolbar
.is(':visible')) {
987 if (!el_controlbox
.is(':visible')) {
988 css_heights
.bottom
= 0;
991 // Apply the CSS sizes
992 el_panels
.css(css_heights
);
993 el_memberlists
.css(css_heights
);
994 el_resize_handle
.css(css_heights
);
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));
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));
1009 alertWindow: function (title
) {
1010 if (!this.alertWindowTimer
) {
1011 this.alertWindowTimer
= new (function () {
1014 var has_focus
= true;
1016 var default_title
= 'Kiwi IRC';
1017 var title
= 'Kiwi IRC';
1019 this.setTitle = function (new_title
) {
1020 new_title
= new_title
|| default_title
;
1021 window
.document
.title
= new_title
;
1025 this.start = function (new_title
) {
1026 // Don't alert if we already have focus
1027 if (has_focus
) return;
1031 tmr
= setInterval(this.update
, 1000);
1034 this.stop = function () {
1035 // Stop the timer and clear the title
1036 if (tmr
) clearInterval(tmr
);
1040 // Some browsers don't always update the last title correctly
1041 // Wait a few seconds and then reset
1042 setTimeout(this.reset
, 2000);
1045 this.reset = function () {
1051 this.update = function () {
1053 that
.setTitle(title
);
1061 $(window
).focus(function (event
) {
1065 // Some browsers don't always update the last title correctly
1066 // Wait a few seconds and then reset
1067 setTimeout(that
.reset
, 2000);
1070 $(window
).blur(function (event
) {
1076 this.alertWindowTimer
.start(title
);
1080 barsHide: function (instant
) {
1084 $('#toolbar').slideUp({queue
: false, duration
: 400, step
: this.doLayout
});
1085 $('#controlbox').slideUp({queue
: false, duration
: 400, step
: this.doLayout
});
1087 $('#toolbar').slideUp(0);
1088 $('#controlbox').slideUp(0);
1093 barsShow: function (instant
) {
1097 $('#toolbar').slideDown({queue
: false, duration
: 400, step
: this.doLayout
});
1098 $('#controlbox').slideDown({queue
: false, duration
: 400, step
: this.doLayout
});
1100 $('#toolbar').slideDown(0);
1101 $('#controlbox').slideDown(0);