3 _kiwi
.model
.Application
= Backbone
.Model
.extend({
4 /** _kiwi.view.Application */
7 /** _kiwi.view.StatusMessage */
10 /* Address for the kiwi server */
13 initialize: function (options
) {
14 if (options
.container
) {
15 this.set('container', options
.container
);
18 // The base url to the kiwi server
19 this.set('base_path', options
.base_path
? options
.base_path
: '/kiwi');
21 // Path for the settings.json file
22 this.set('settings_path', options
.settings_path
?
23 options
.settings_path
:
24 this.get('base_path') + '/assets/settings.json'
27 // Any options sent down from the server
28 this.server_settings
= options
.server_settings
|| {};
29 this.translations
= options
.translations
|| {};
30 this.themes
= options
.themes
|| [];
31 this.text_theme
= options
.text_theme
|| {};
33 // Best guess at where the kiwi server is if not already specified
34 this.kiwi_server
= options
.kiwi_server
|| this.detectKiwiServer();
36 // The applet to initially load
37 this.startup_applet_name
= options
.startup
|| 'kiwi_startup';
39 // Set any default settings before anything else is applied
40 if (this.server_settings
&& this.server_settings
.client
&& this.server_settings
.client
.settings
) {
41 this.applyDefaultClientSettings(this.server_settings
.client
.settings
);
46 initializeInterfaces: function () {
48 _kiwi
.gateway
= new _kiwi
.model
.Gateway();
49 this.bindGatewayCommands(_kiwi
.gateway
);
51 this.initializeClient();
52 this.initializeGlobals();
54 this.view
.barsHide(true);
58 detectKiwiServer: function () {
59 // If running from file, default to localhost:7777 by default
60 if (window
.location
.protocol
=== 'file:') {
61 return 'http://localhost:7778';
63 // Assume the kiwi server is on the same server
64 return window
.location
.protocol
+ '//' + window
.location
.host
;
69 showStartup: function() {
70 this.startup_applet
= _kiwi
.model
.Applet
.load(this.startup_applet_name
, {no_tab
: true});
71 this.startup_applet
.tab
= this.view
.$('.console');
72 this.startup_applet
.view
.show();
76 initializeClient: function () {
77 this.view
= new _kiwi
.view
.Application({model
: this, el
: this.get('container')});
79 // Takes instances of model_network
80 this.connections
= new _kiwi
.model
.NetworkPanelList();
83 this.applet_panels
= new _kiwi
.model
.PanelList();
84 this.applet_panels
.view
.$el
.addClass('panellist applets');
85 this.view
.$el
.find('.tabs').append(this.applet_panels
.view
.$el
);
88 * Set the UI components up
90 this.controlbox
= new _kiwi
.view
.ControlBox({el
: $('#kiwi .controlbox')[0]});
91 this.bindControllboxCommands(this.controlbox
);
92 this.rightbar
= new _kiwi
.view
.RightBar({el
: this.view
.$('.right_bar')[0]});
94 this.topicbar
= new _kiwi
.view
.TopicBar({el
: this.view
.$el
.find('.topic')[0]});
96 new _kiwi
.view
.AppToolbar({el
: _kiwi
.app
.view
.$el
.find('.toolbar .app_tools')[0]});
97 new _kiwi
.view
.ChannelTools({el
: _kiwi
.app
.view
.$el
.find('.channel_tools')[0]});
99 this.message
= new _kiwi
.view
.StatusMessage({el
: this.view
.$el
.find('.status_message')[0]});
101 this.resize_handle
= new _kiwi
.view
.ResizeHandler({el
: this.view
.$el
.find('.memberlists_resize_handle')[0]});
103 // Rejigg the UI sizes
104 this.view
.doLayout();
108 initializeGlobals: function () {
109 _kiwi
.global
.connections
= this.connections
;
111 _kiwi
.global
.panels
= this.panels
;
112 _kiwi
.global
.panels
.applets
= this.applet_panels
;
114 _kiwi
.global
.components
.Applet
= _kiwi
.model
.Applet
;
115 _kiwi
.global
.components
.Panel
=_kiwi
.model
.Panel
;
116 _kiwi
.global
.components
.MenuBox
= _kiwi
.view
.MenuBox
;
120 applyDefaultClientSettings: function (settings
) {
121 _
.each(settings
, function (value
, setting
) {
122 if (typeof _kiwi
.global
.settings
.get(setting
) === 'undefined') {
123 _kiwi
.global
.settings
.set(setting
, value
);
129 panels
: (function() {
132 var fn = function(panel_type
) {
135 // Default panel type
136 panel_type
= panel_type
|| 'connections';
138 switch (panel_type
) {
140 panels
= this.connections
.panels();
143 panels
= this.applet_panels
.models
;
147 // Active panels / server
148 panels
.active
= active_panel
;
149 panels
.server
= this.connections
.active_connection
?
150 this.connections
.active_connection
.panels
.server
:
156 _
.extend(fn
, Backbone
.Events
);
158 // Keep track of the active panel. Channel/query/server or applet
159 fn
.bind('active', function (new_active_panel
) {
160 active_panel
= new_active_panel
;
167 bindGatewayCommands: function (gw
) {
170 gw
.on('connect', function (event
) {
171 that
.view
.barsShow();
176 * Handle the reconnections to the kiwi server
179 // 0 = non-reconnecting state. 1 = reconnecting state.
182 // If the current or upcoming disconnect was planned
183 var unplanned_disconnect
= false;
185 gw
.on('disconnect', function (event
) {
186 unplanned_disconnect
= !gw
.disconnect_requested
;
188 if (unplanned_disconnect
) {
189 var msg
= _kiwi
.global
.i18n
.translate('client_models_application_reconnecting').fetch() + '...';
190 that
.message
.text(msg
, {timeout
: 10000});
193 that
.view
.$el
.removeClass('connected');
195 // Mention the disconnection on every channel
196 _kiwi
.app
.connections
.forEach(function(connection
) {
197 connection
.panels
.server
.addMsg('', styleText('quit', {text
: msg
}), 'action quit');
199 connection
.panels
.forEach(function(panel
) {
200 if (!panel
.isChannel())
203 panel
.addMsg('', styleText('quit', {text
: msg
}), 'action quit');
211 gw
.on('reconnecting', function (event
) {
212 var msg
= _kiwi
.global
.i18n
.translate('client_models_application_reconnect_in_x_seconds').fetch(event
.delay
/1000) + '...';
214 // Only need to mention the repeating re-connection messages on server panels
215 _kiwi
.app
.connections
.forEach(function(connection
) {
216 connection
.panels
.server
.addMsg('', styleText('quit', {text
: msg
}), 'action quit');
221 gw
.on('connect', function (event
) {
222 that
.view
.$el
.addClass('connected');
223 if (gw_stat
!== 1) return;
225 if (unplanned_disconnect
) {
226 var msg
= _kiwi
.global
.i18n
.translate('client_models_application_reconnect_successfully').fetch() + ':)';
227 that
.message
.text(msg
, {timeout
: 5000});
230 // Mention the re-connection on every channel
231 _kiwi
.app
.connections
.forEach(function(connection
) {
232 connection
.panels
.server
.addMsg('', styleText('rejoin', {text
: msg
}), 'action join');
234 connection
.panels
.forEach(function(panel
) {
235 if (!panel
.isChannel())
238 panel
.addMsg('', styleText('rejoin', {text
: msg
}), 'action join');
247 gw
.on('kiwi:reconfig', function () {
248 $.getJSON(that
.get('settings_path'), function (data
) {
249 that
.server_settings
= data
.server_settings
|| {};
250 that
.translations
= data
.translations
|| {};
255 gw
.on('kiwi:jumpserver', function (data
) {
257 // No server set? Then nowhere to jump to.
258 if (typeof data
.kiwi_server
=== 'undefined')
261 serv
= data
.kiwi_server
;
263 // Strip any trailing slash from the end
264 if (serv
[serv
.length
-1] === '/')
265 serv
= serv
.substring(0, serv
.length
-1);
267 // Force the jumpserver now?
269 // Get an interval between 5 and 6 minutes so everyone doesn't reconnect it all at once
270 var jump_server_interval
= Math
.random() * (360 - 300) + 300;
272 // Tell the user we are going to disconnect, wait 5 minutes then do the actual reconnect
273 var msg
= _kiwi
.global
.i18n
.translate('client_models_application_jumpserver_prepare').fetch();
274 that
.message
.text(msg
, {timeout
: 10000});
276 setTimeout(function forcedReconnect() {
277 var msg
= _kiwi
.global
.i18n
.translate('client_models_application_jumpserver_reconnect').fetch();
278 that
.message
.text(msg
, {timeout
: 8000});
280 setTimeout(function forcedReconnectPartTwo() {
281 _kiwi
.app
.kiwi_server
= serv
;
283 _kiwi
.gateway
.reconnect(function() {
284 // Reconnect all the IRC connections
285 that
.connections
.forEach(function(con
){ con
.reconnect(); });
289 }, jump_server_interval
* 1000);
297 * Bind to certain commands that may be typed into the control box
299 bindControllboxCommands: function (controlbox
) {
303 $.extend(controlbox
.preprocessor
.aliases
, {
306 '/me': '/action $1+',
310 '/raw': '/quote $1+',
312 // Op related aliases
313 '/op': '/quote mode $channel +o $1+',
314 '/deop': '/quote mode $channel -o $1+',
315 '/hop': '/quote mode $channel +h $1+',
316 '/dehop': '/quote mode $channel -h $1+',
317 '/voice': '/quote mode $channel +v $1+',
318 '/devoice': '/quote mode $channel -v $1+',
319 '/k': '/kick $channel $1+',
320 '/ban': '/quote mode $channel +b $1+',
321 '/unban': '/quote mode $channel -b $1+',
324 '/slap': '/me slaps $1 around a bit with a large trout'
327 // Functions to bind to controlbox events
329 'unknown_command': unknownCommand
,
330 'command': allCommands
,
331 'command:msg': msgCommand
,
332 'command:action': actionCommand
,
333 'command:join': joinCommand
,
334 'command:part': partCommand
,
335 'command:nick': nickCommand
,
336 'command:query': queryCommand
,
337 'command:invite': inviteCommand
,
338 'command:topic': topicCommand
,
339 'command:notice': noticeCommand
,
340 'command:quote': quoteCommand
,
341 'command:kick': kickCommand
,
342 'command:clear': clearCommand
,
343 'command:ctcp': ctcpCommand
,
344 'command:server': serverCommand
,
345 'command:whois': whoisCommand
,
346 'command:whowas': whowasCommand
,
347 'command:encoding': encodingCommand
,
348 'command:channel': channelCommand
,
349 'command:applet': appletCommand
,
350 'command:settings': settingsCommand
,
351 'command:script': scriptCommand
354 fn_to_bind
['command:css'] = function (ev
) {
355 var queryString
= '?reload=' + new Date().getTime();
356 $('link[rel="stylesheet"]').each(function () {
357 this.href
= this.href
.replace(/\?.*|$/, queryString
);
361 fn_to_bind
['command:js'] = function (ev
) {
362 if (!ev
.params
[0]) return;
363 $script(ev
.params
[0] + '?' + (new Date().getTime()));
367 fn_to_bind
['command:set'] = function (ev
) {
368 if (!ev
.params
[0]) return;
370 var setting
= ev
.params
[0],
373 // Do we have a second param to set a value?
377 value
= ev
.params
.join(' ');
379 // If we're setting a true boolean value..
380 if (value
=== 'true')
383 // If we're setting a false boolean value..
384 if (value
=== 'false')
387 // If we're setting a number..
388 if (parseInt(value
, 10).toString() === value
)
389 value
= parseInt(value
, 10);
391 _kiwi
.global
.settings
.set(setting
, value
);
394 // Read the value to the user
395 _kiwi
.app
.panels().active
.addMsg('', styleText('set_setting', {text
: setting
+ ' = ' + _kiwi
.global
.settings
.get(setting
).toString()}));
399 fn_to_bind
['command:save'] = function (ev
) {
400 _kiwi
.global
.settings
.save();
401 _kiwi
.app
.panels().active
.addMsg('', styleText('settings_saved', {text
: translateText('client_models_application_settings_saved')}));
405 fn_to_bind
['command:alias'] = function (ev
) {
408 // No parameters passed so list them
410 $.each(controlbox
.preprocessor
.aliases
, function (name
, rule
) {
411 _kiwi
.app
.panels().server
.addMsg(' ', styleText('list_aliases', {text
: name
+ ' => ' + rule
}));
416 // Deleting an alias?
417 if (ev
.params
[0] === 'del' || ev
.params
[0] === 'delete') {
419 if (name
[0] !== '/') name
= '/' + name
;
420 delete controlbox
.preprocessor
.aliases
[name
];
427 rule
= ev
.params
.join(' ');
429 // Make sure the name starts with a slash
430 if (name
[0] !== '/') name
= '/' + name
;
432 // Now actually add the alias
433 controlbox
.preprocessor
.aliases
[name
] = rule
;
437 fn_to_bind
['command:ignore'] = function (ev
) {
438 var list
= this.connections
.active_connection
.get('ignore_list');
440 // No parameters passed so list them
442 if (list
.length
> 0) {
443 _kiwi
.app
.panels().active
.addMsg(' ', styleText('ignore_title', {text
: translateText('client_models_application_ignore_title')}));
444 $.each(list
, function (idx
, ignored_pattern
) {
445 _kiwi
.app
.panels().active
.addMsg(' ', styleText('ignored_pattern', {text
: ignored_pattern
}));
448 _kiwi
.app
.panels().active
.addMsg(' ', styleText('ignore_none', {text
: translateText('client_models_application_ignore_none')}));
453 // We have a parameter, so add it
454 list
.push(ev
.params
[0]);
455 this.connections
.active_connection
.set('ignore_list', list
);
456 _kiwi
.app
.panels().active
.addMsg(' ', styleText('ignore_nick', {text
: translateText('client_models_application_ignore_nick', [ev
.params
[0]])}));
460 fn_to_bind
['command:unignore'] = function (ev
) {
461 var list
= this.connections
.active_connection
.get('ignore_list');
464 _kiwi
.app
.panels().active
.addMsg(' ', styleText('ignore_stop_notice', {text
: translateText('client_models_application_ignore_stop_notice')}));
468 list
= _
.reject(list
, function(pattern
) {
469 return pattern
=== ev
.params
[0];
472 this.connections
.active_connection
.set('ignore_list', list
);
474 _kiwi
.app
.panels().active
.addMsg(' ', styleText('ignore_stopped', {text
: translateText('client_models_application_ignore_stopped', [ev
.params
[0]])}));
478 _
.each(fn_to_bind
, function(fn
, event_name
) {
479 controlbox
.on(event_name
, _
.bind(fn
, that
));
487 // A fallback action. Send a raw command to the server
488 function unknownCommand (ev
) {
489 var raw_cmd
= ev
.command
+ ' ' + ev
.params
.join(' ');
490 console
.log('RAW: ' + raw_cmd
);
491 this.connections
.active_connection
.gateway
.raw(raw_cmd
);
494 function allCommands (ev
) {}
496 function joinCommand (ev
) {
497 var panels
, channel_names
;
499 channel_names
= ev
.params
.join(' ').split(',');
500 panels
= this.connections
.active_connection
.createAndJoinChannels(channel_names
);
502 // Show the last channel if we have one
504 panels
[panels
.length
- 1].view
.show();
507 function queryCommand (ev
) {
508 var destination
, message
, panel
;
510 destination
= ev
.params
[0];
513 message
= ev
.params
.join(' ');
515 // Check if we have the panel already. If not, create it
516 panel
= this.connections
.active_connection
.panels
.getByName(destination
);
518 panel
= new _kiwi
.model
.Query({name
: destination
});
519 this.connections
.active_connection
.panels
.add(panel
);
522 if (panel
) panel
.view
.show();
525 this.connections
.active_connection
.gateway
.msg(panel
.get('name'), message
);
526 panel
.addMsg(_kiwi
.app
.connections
.active_connection
.get('nick'), styleText('privmsg', {text
: message
}), 'privmsg');
531 function msgCommand (ev
) {
533 destination
= ev
.params
[0],
534 panel
= this.connections
.active_connection
.panels
.getByName(destination
) || this.panels().server
;
537 message
= formatToIrcMsg(ev
.params
.join(' '));
539 panel
.addMsg(_kiwi
.app
.connections
.active_connection
.get('nick'), styleText('privmsg', {text
: message
}), 'privmsg');
540 this.connections
.active_connection
.gateway
.msg(destination
, message
);
543 function actionCommand (ev
) {
544 if (_kiwi
.app
.panels().active
.isServer()) {
548 var panel
= _kiwi
.app
.panels().active
;
549 panel
.addMsg('', styleText('action', {nick
: _kiwi
.app
.connections
.active_connection
.get('nick'), text
: ev
.params
.join(' ')}), 'action');
550 this.connections
.active_connection
.gateway
.action(panel
.get('name'), ev
.params
.join(' '));
553 function partCommand (ev
) {
556 if (ev
.params
.length
=== 0) {
557 this.connections
.active_connection
.gateway
.part(_kiwi
.app
.panels().active
.get('name'));
559 _
.each(ev
.params
, function (channel
) {
560 that
.connections
.active_connection
.gateway
.part(channel
);
565 function nickCommand (ev
) {
566 this.connections
.active_connection
.gateway
.changeNick(ev
.params
[0]);
569 function topicCommand (ev
) {
572 if (ev
.params
.length
=== 0) return;
574 if (this.connections
.active_connection
.isChannelName(ev
.params
[0])) {
575 channel_name
= ev
.params
[0];
578 channel_name
= _kiwi
.app
.panels().active
.get('name');
581 this.connections
.active_connection
.gateway
.topic(channel_name
, ev
.params
.join(' '));
584 function noticeCommand (ev
) {
587 // Make sure we have a destination and some sort of message
588 if (ev
.params
.length
<= 1) return;
590 destination
= ev
.params
[0];
593 this.connections
.active_connection
.gateway
.notice(destination
, ev
.params
.join(' '));
596 function quoteCommand (ev
) {
597 var raw
= ev
.params
.join(' ');
598 this.connections
.active_connection
.gateway
.raw(raw
);
601 function kickCommand (ev
) {
602 var nick
, panel
= _kiwi
.app
.panels().active
;
604 if (!panel
.isChannel()) return;
606 // Make sure we have a nick
607 if (ev
.params
.length
=== 0) return;
612 this.connections
.active_connection
.gateway
.kick(panel
.get('name'), nick
, ev
.params
.join(' '));
615 function clearCommand (ev
) {
616 // Can't clear a server or applet panel
617 if (_kiwi
.app
.panels().active
.isServer() || _kiwi
.app
.panels().active
.isApplet()) {
621 if (_kiwi
.app
.panels().active
.clearMessages
) {
622 _kiwi
.app
.panels().active
.clearMessages();
626 function ctcpCommand(ev
) {
629 // Make sure we have a target and a ctcp type (eg. version, time)
630 if (ev
.params
.length
< 2) return;
632 target
= ev
.params
[0];
638 this.connections
.active_connection
.gateway
.ctcp(true, type
, target
, ev
.params
.join(' '));
641 function settingsCommand (ev
) {
642 var settings
= _kiwi
.model
.Applet
.loadOnce('kiwi_settings');
643 settings
.view
.show();
646 function scriptCommand (ev
) {
647 var editor
= _kiwi
.model
.Applet
.loadOnce('kiwi_script_editor');
651 function appletCommand (ev
) {
652 if (!ev
.params
[0]) return;
654 var panel
= new _kiwi
.model
.Applet();
657 // Url and name given
658 panel
.load(ev
.params
[0], ev
.params
[1]);
660 // Load a pre-loaded applet
661 if (_kiwi
.applets
[ev
.params
[0]]) {
662 panel
.load(new _kiwi
.applets
[ev
.params
[0]]());
664 _kiwi
.app
.panels().server
.addMsg('', styleText('applet_notfound', {text
: translateText('client_models_application_applet_notfound', [ev
.params
[0]])}));
669 _kiwi
.app
.connections
.active_connection
.panels
.add(panel
);
675 function inviteCommand (ev
) {
678 // A nick must be specified
682 // Can only invite into channels
683 if (!_kiwi
.app
.panels().active
.isChannel())
687 channel
= _kiwi
.app
.panels().active
.get('name');
689 _kiwi
.app
.connections
.active_connection
.gateway
.raw('INVITE ' + nick
+ ' ' + channel
);
691 _kiwi
.app
.panels().active
.addMsg('', styleText('channel_has_been_invited', {nick
: nick
, text
: translateText('client_models_application_has_been_invited', [channel
])}), 'action');
695 function whoisCommand (ev
) {
700 } else if (_kiwi
.app
.panels().active
.isQuery()) {
701 nick
= _kiwi
.app
.panels().active
.get('name');
705 _kiwi
.app
.connections
.active_connection
.gateway
.raw('WHOIS ' + nick
+ ' ' + nick
);
709 function whowasCommand (ev
) {
714 } else if (_kiwi
.app
.panels().active
.isQuery()) {
715 nick
= _kiwi
.app
.panels().active
.get('name');
719 _kiwi
.app
.connections
.active_connection
.gateway
.raw('WHOWAS ' + nick
);
722 function encodingCommand (ev
) {
724 _kiwi
.gateway
.setEncoding(null, ev
.params
[0], function (success
) {
726 _kiwi
.app
.panels().active
.addMsg('', styleText('encoding_changed', {text
: translateText('client_models_application_encoding_changed', [ev
.params
[0]])}));
728 _kiwi
.app
.panels().active
.addMsg('', styleText('encoding_invalid', {text
: translateText('client_models_application_encoding_invalid', [ev
.params
[0]])}));
732 _kiwi
.app
.panels().active
.addMsg('', styleText('client_models_application_encoding_notspecified', {text
: translateText('client_models_application_encoding_notspecified')}));
733 _kiwi
.app
.panels().active
.addMsg('', styleText('client_models_application_encoding_usage', {text
: translateText('client_models_application_encoding_usage')}));
737 function channelCommand (ev
) {
738 var active_panel
= _kiwi
.app
.panels().active
;
740 if (!active_panel
.isChannel())
743 new _kiwi
.model
.ChannelInfo({channel
: _kiwi
.app
.panels().active
});
746 function serverCommand (ev
) {
747 var server
, port
, ssl
, password
, nick
,
750 // If no server address given, show the new connection dialog
752 tmp
= new _kiwi
.view
.MenuBox(_kiwi
.global
.i18n
.translate('client_models_application_connection_create').fetch());
753 tmp
.addItem('new_connection', new _kiwi
.model
.NewConnection().view
.$el
);
756 // Center screen the dialog
758 top
: (this.view
.$el
.height() / 2) - (tmp
.$el
.height() / 2),
759 left
: (this.view
.$el
.width() / 2) - (tmp
.$el
.width() / 2)
765 // Port given in 'host:port' format and no specific port given after a space
766 if (ev
.params
[0].indexOf(':') > 0) {
767 tmp
= ev
.params
[0].split(':');
771 password
= ev
.params
[1] || undefined;
774 // Server + port given as 'host port'
775 server
= ev
.params
[0];
776 port
= ev
.params
[1] || 6667;
778 password
= ev
.params
[2] || undefined;
781 // + in the port means SSL
782 if (port
.toString()[0] === '+') {
784 port
= parseInt(port
.substring(1), 10);
789 // Default port if one wasn't found
792 // Use the same nick as we currently have
793 nick
= _kiwi
.app
.connections
.active_connection
.get('nick');
795 _kiwi
.app
.panels().active
.addMsg('', styleText('client_models_application_connection_connecting', {text
: translateText('client_models_application_connection_connecting', [server
, port
.toString()])}));
797 _kiwi
.gateway
.newConnection({
803 }, function(err
, new_connection
) {
805 _kiwi
.app
.panels().active
.addMsg('', styleText('client_models_application_connection_error', {text
: translateText('client_models_application_connection_error', [server
, port
.toString(), err
.toString()])}));