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 */
23 * A list of Utilityviews
39 * The current command history position
45 * Container for misc data (eg. userlist generation)
48 cache
: {original_topic
: '', userlist
: {}},
51 * Initialisation function
54 /*global Box, touch_scroll:true, Tabview */
55 var about_info
, supportsOrientationChange
, orientationEvent
, scroll_opts
, server_tabview
;
56 kiwi
.gateway
.nick
= 'kiwi_' + Math
.ceil(100 * Math
.random()) + Math
.ceil(100 * Math
.random());
57 kiwi
.gateway
.session_id
= null;
59 // Bind to the gateway events
60 kiwi
.front
.events
.bindAll();
62 // Build the about box
63 kiwi
.front
.boxes
.about
= new Box("about");
64 about_info
= 'UI adapted for ' + agent
;
66 about_info
+= ' touchscreen ';
68 about_info
+= 'usage';
69 $('#tmpl_about_box').tmpl({
71 }).appendTo(kiwi
.front
.boxes
.about
.content
);
73 //$(window).bind("beforeunload", function(){ kiwi.gateway.quit(); });
76 $('#kiwi').addClass('touchscreen');
78 // Single touch scrolling through scrollback for touchscreens
80 touch_scroll
= new iScroll('windows', scroll_opts
);
83 kiwi
.front
.ui
.registerKeys();
85 $('#kiwi .toolbars').resize(kiwi
.front
.ui
.doLayoutSize
);
86 $(window
).resize(kiwi
.front
.ui
.doLayoutSize
);
88 // Add the resizer for the userlist
89 $('<div id="nicklist_resize"></div>').appendTo('#kiwi');
90 $('#nicklist_resize').draggable({axis
: "x", drag: function () {
92 ul
= $('#kiwi .userlist'),
93 new_width
= $(document
).width() - parseInt(t
.css('left'), 10);
95 new_width
= new_width
- parseInt(ul
.css('margin-left'), 10);
96 new_width
= new_width
- parseInt(ul
.css('margin-right'), 10);
98 // Make sure we don't remove the userlist alltogether
100 $(this).data('draggable').offset
.click
.left
= 10;
101 console
.log('whoaa');
104 var members
= kiwi
.currentPanel
.get("members");
106 $(members
.view
.el
).width(new_width
);
108 $('#windows').css('right', ul
.outerWidth(true));
112 $('#kiwi .formconnectwindow').submit(function () {
113 var netsel
= $('#kiwi .formconnectwindow .network'),
114 netport
= $('#kiwi .formconnectwindow .port'),
115 netssl
= $('#kiwi .formconnectwindow .ssl'),
116 netpass
= $('#kiwi .formconnectwindow .password'),
117 nick
= $('#kiwi .formconnectwindow .nick'),
121 if (nick
.val() === '') {
122 nick
.val('Nick please!');
127 tmp
= nick
.val().split(' ');
128 kiwi
.gateway
.nick
= tmp
[0];
130 init_data
.channel
= $('#channel').val();
132 kiwi
.front
.ui
.doLayout();
134 kiwi
.gateway
.connect(netsel
.val(), netport
.val(), netssl
.is(':checked'), netpass
.val(), function () {
135 setTimeout(function () {
136 kiwi
.channels
.server
.set({"name": netsel
.val()});
137 kiwi
.channels
.view
.render();
144 $('#kiwi .connectwindow').slideUp('', kiwi
.front
.ui
.barsShow
);
147 * Listen for keyboard activity on any window, and forward it to the
148 * input box so users can type even if the input box is not in focus
150 * @param {eventObject} event The event to forward
152 forwardKeys = function (event
) {
153 $('#kiwi_msginput').focus();
154 $('#kiwi_msginput').trigger(event
);
156 $('#kiwi_msginput').attr('tabindex', 0);
157 $('#kiwi_msginput').focus();
158 $('#windows').attr('tabindex',100);
159 $('#windows').keydown(forwardKeys
).keypress(forwardKeys
).keyup(forwardKeys
);
164 supportsOrientationChange
= (typeof window
.onorientationchange
!== undefined);
165 orientationEvent
= supportsOrientationChange
? "orientationchange" : "resize";
166 if (window
.addEventListener
) {
167 window
.addEventListener(orientationEvent
, kiwi
.front
.ui
.doLayoutSize
, false);
170 window
.attachEvent(orientationEvent
, kiwi
.front
.ui
.doLayoutSize
, false);
172 //$('#kiwi').bind("resize", kiwi.front.ui.doLayoutSize, false);
174 kiwi
.front
.ui
.doLayout();
175 kiwi
.front
.ui
.barsHide();
177 kiwi
.channels
= new kiwi
.model
.PanelList();
180 //server_tabview = new Tabview('server');
181 //server_tabview.userlist.setWidth(0); // Disable the userlist
182 //server_tabview.setIcon('/img/app_menu.png');
183 //$('.icon', server_tabview.tab).tipTip({
186 // content: $('#tmpl_network_menu').tmpl({}).html(),
187 // activation: 'click'
190 // Any pre-defined nick?
191 if (typeof window
.init_data
.nick
=== "string") {
192 $('#kiwi .formconnectwindow .nick').val(init_data
.nick
);
195 // Any pre-defined channels?
196 if (typeof window
.init_data
.channel
=== 'string') {
197 $('#channel').val(init_data
.channel
);
200 // Fix for Opera inserting a spurious <br/>
201 $('#kiwi .cur_topic br').remove();
203 $('#kiwi .cur_topic').keydown(function (e
) {
204 if (e
.which
=== 13) {
208 $('#kiwi_msginput').focus();
209 } else if (e
.which
=== 27) {
212 $(this).text(kiwi
.front
.cache
.original_topic
);
213 $('#kiwi_msginput').focus();
216 /*$('.cur_topic').live('keypress', function(e) {
217 if (e.keyCode === 13) {
221 $('#kiwi_msginput').focus();
222 } else if (e.keyCode === 27) {
225 $(this).text(kiwi.front.cache.original_topic);
228 $('.cur_topic').live('change', function () {
230 text
= $(this).text();
231 if (text
!== kiwi
.front
.cache
.original_topic
) {
232 if (kiwi
.currentPanel
.isChannel
) {
233 kiwi
.gateway
.topic(kiwi
.currentPannel
.get("name"), text
);
239 $('#windows a.chan').live('click', function () {
240 kiwi
.front
.joinChannel($(this).text());
244 kiwi
.data
.set('chanList', []);
246 // Load any client plugins
250 kiwi
.plugs
.loadPlugin(plugins
[i
]);
259 * @param {String} chan_name The name of the channel to join
261 joinChannel: function (chan_name
) {
262 var chans
= chan_name
.split(','),
268 panel
= kiwi
.channels
.getByName(chan
);
270 kiwi
.gateway
.join(chan
);
276 * Parses and executes text and commands entered into the input msg box
277 * @param {String} msg The message string to parse
279 run: function (msg
) {
280 var parts
, dest
, t
, pos
, textRange
, plugin_event
, msg_sliced
, tab
, nick
, panel
;
282 // Run through any plugins
283 plugin_event
= {command
: msg
};
284 plugin_event
= kiwi
.plugs
.run('command_run', plugin_event
);
285 if (!plugin_event
|| typeof plugin_event
.command
=== 'undefined') {
289 // Update msg if it's been changed by any plugins
290 msg
= plugin_event
.command
.toString();
293 if (msg
.substring(0, 1) === '/') {
294 console
.log("running " + msg
);
295 parts
= msg
.split(' ');
296 switch (parts
[0].toLowerCase()) {
299 kiwi
.front
.joinChannel(parts
[1]);
304 if (typeof parts
[1] === 'undefined') {
305 alert('Usage: /connect servername [port] [ssl] [password]');
309 if (typeof parts
[2] === 'undefined') {
313 if ((typeof parts
[3] === 'undefined') || !parts
[3] || (parts
[3] === 'false') || (parts
[3] === 'no')) {
319 kiwi
.channels
.server
.addMsg(null, ' ', '=== Connecting to ' + parts
[1] + ' on port ' + parts
[2] + (parts
[3] ? ' using SSL' : '') + '...', 'status');
320 console
.log('Connecting to ' + parts
[1] + ' on port ' + parts
[2] + (parts
[3] ? ' using SSL' : '') + '...');
321 kiwi
.gateway
.connect(parts
[1], parts
[2], parts
[3], parts
[4]);
325 console
.log("/nick");
326 if (parts
[1] === undefined) {
327 console
.log("calling show nick");
328 kiwi
.front
.ui
.showChangeNick();
330 console
.log("sending nick");
331 kiwi
.gateway
.changeNick(parts
[1]);
336 if (typeof parts
[1] === "undefined") {
337 if (kiwi
.currentPanel
.isChannel
) {
338 kiwi
.gateway
.part(kiwi
.currentPanel
.get("name"));
341 kiwi
.gateway
.part(msg
.substring(6));
346 if (typeof parts
[1] !== "undefined") {
347 kiwi
.gateway
.raw(msg
.substring(1));
349 if (kiwi
.currentPanel
.isChannel
) {
350 kiwi
.gateway
.raw("NAMES " + kiwi
.currentPanel
.get("name"));
356 kiwi
.gateway
.debug();
361 if (typeof parts
[1] !== "undefined") {
362 //tab = new Tabview(parts[1]);
369 if (typeof parts
[1] !== "undefined") {
370 msg_sliced
= msg
.split(' ').slice(2).join(' ');
371 kiwi
.gateway
.privmsg(parts
[1], msg_sliced
);
374 panel
= kiwi
.channels
.getByName(parts
[1]);
376 panel
.addMsg(null, kiwi
.gateway
.nick
, msg_sliced
);
383 if (typeof parts
[1] === 'undefined') {
386 t
= msg
.split(' ', 3);
388 //kiwi.gateway.kick(Tabview.getCurrentTab().name, nick, t[2]);
392 kiwi
.gateway
.raw(msg
.replace(/^\/quote /i, ''));
396 if (kiwi
.currentPanel
.isChannel
) {
397 kiwi
.gateway
.ctcp(true, 'ACTION', tab
.name
, msg
.substring(4), function () {
398 kiwi
.currentPanel
.addMsg(null, ' ', '* ' + kiwi
.gateway
.nick
+ ' ' + msg
.substring(4), 'action', 'color:#555;');
406 msg
= parts
.slice(2).join(' ');
408 kiwi
.gateway
.notice(dest
, msg
, function () {
409 kiwi
.front
.events
.onNotice({}, {nick
: kiwi
.gateway
.nick
, channel
: dest
, msg
: msg
});
414 if (parts[1] !== undefined) {
415 kiwi.front.ui.windowsShow(parseInt(parts[1], 10));
420 kiwi
.gateway
.quit(parts
.slice(1).join(' '));
424 if (parts
[1] === undefined) {
426 if (t
.createTextRange
) {
427 pos
= t
.text().length();
428 textRange
= t
.createTextRange();
429 textRange
.collapse(true);
430 textRange
.moveEnd(pos
);
431 textRange
.moveStart(pos
);
433 } else if (t
.setSelectionRange
) {
434 t
.setSelectionRange(pos
, pos
);
437 if (kiwi
.currentPanel
.isChannel
) {
438 kiwi
.gateway
.topic(kiwi
.currentPanel
.get("name"), msg
.split(' ', 2)[1]);
444 kiwi
.gateway
.ctcp(true, 'KIWI', kiwi
.currentPanel
.get("name"), msg
.substring(6));
448 parts
= parts
.slice(1);
449 dest
= parts
.shift();
451 msg
= parts
.join(' ');
454 kiwi
.gateway
.ctcp(true, t
, dest
, msg
, function () {
455 kiwi
.channels
.server
.addMsg(null, 'CTCP Request', '[to ' + dest
+ '] ' + t
+ ' ' + msg
, 'ctcp');
459 kiwi
.currentPanel
.addMsg(null, ' ', '--> Invalid command: '+parts
[0].substring(1));
460 kiwi
.gateway
.raw(msg
.substring(1));
465 //alert('Sending message: '+msg);
466 if (msg
.trim() === '') {
469 if (kiwi
.currentPanel
.isChannel
) {
470 kiwi
.gateway
.privmsg(kiwi
.currentPanel
.get("name"), msg
, function () {
471 kiwi
.currentPanel
.addMsg(null, kiwi
.gateway
.nick
, msg
);
482 * Sort the window list
484 sortWindowList: function () {
485 var win_list
= $('#kiwi .windowlist ul'),
486 listitems
= win_list
.children('li').get();
488 /*listitems.sort(function (a, b) {
489 if (a === Tabview.getServerTab().tab[0]) {
492 if (b === Tabview.getServerTab().tab[0]) {
495 var compA = $(a).text().toUpperCase(),
496 compB = $(b).text().toUpperCase();
497 return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
500 $.each(listitems, function(idx, itm) {
501 win_list.append(itm);
509 * Syncs with the Kiwi server
517 * Checks if a given name is the name of a channel
518 * @param {String} name The name to check
519 * @returns {Boolean} True if name is the name of a channel, false if it is not
521 isChannel: function (name
) {
523 prefix
= name
.charAt(0);
524 if (kiwi
.gateway
.channel_prefix
.indexOf(prefix
) > -1) {
534 * Formats a message. Adds bold, underline and colouring
535 * @param {String} msg The message to format
536 * @returns {String} The HTML formatted message
538 formatIRCMsg: function (msg
) {
541 if ((!msg
) || (typeof msg
!== 'string')) {
546 if (msg
.indexOf(String
.fromCharCode(2)) !== -1) {
548 while (msg
.indexOf(String
.fromCharCode(2)) !== -1) {
549 msg
= msg
.replace(String
.fromCharCode(2), next
);
550 next
= (next
=== '<b>') ? '</b>' : '<b>';
552 if (next
=== '</b>') {
558 if (msg
.indexOf(String
.fromCharCode(31)) !== -1) {
560 while (msg
.indexOf(String
.fromCharCode(31)) !== -1) {
561 msg
= msg
.replace(String
.fromCharCode(31), next
);
562 next
= (next
=== '<u>') ? '</u>' : '<u>';
564 if (next
=== '</u>') {
573 msg
= (function (msg
) {
574 var replace
, colourMatch
, col
, i
, match
, to
, endCol
, fg
, bg
, str
;
579 colourMatch = function (str
) {
580 var re
= /^\x03([0-9][0-5]?)(,([0-9][0-5]?))?/;
586 col = function (num
) {
587 switch (parseInt(num
, 10)) {
624 if (msg
.indexOf('\x03') !== -1) {
625 i
= msg
.indexOf('\x03');
626 replace
= msg
.substr(0, i
);
627 while (i
< msg
.length
) {
631 match
= colourMatch(msg
.substr(i
, 6));
633 //console.log(match);
635 to
= msg
.indexOf('\x03', i
+ 1);
636 endCol
= msg
.indexOf(String
.fromCharCode(15), i
+ 1);
641 to
= ((to
< endCol
) ? to
: endCol
);
647 //console.log(i, to);
650 str
= msg
.substring(i
+ 1 + match
[1].length
+ ((bg
!== null) ? match
[2].length
+ 1 : 0), to
);
652 replace
+= '<span style="' + ((fg
!== null) ? 'color: ' + fg
+ '; ' : '') + ((bg
!== null) ? 'background-color: ' + bg
+ ';' : '') + '">' + str
+ '</span>';
655 if ((msg
[i
] !== '\x03') && (msg
[i
] !== String
.fromCharCode(15))) {
670 * Registers Kiwi IRC as a handler for the irc:// protocol in the browser
672 registerProtocolHandler: function () {
674 url
= kiwi_server
.replace(/\/kiwi$/, '/?ircuri=%s');
676 //state = window.navigator.isProtocolHandlerRegistered('irc', url);
677 //if (state !== 'registered') {
678 window
.navigator
.registerProtocolHandler('irc', url
, 'Kiwi IRC');
681 console
.log('Unable to register Kiwi IRC as a handler for irc:// links');
695 var ChannelList = function () {
696 /*globals Utilityview */
697 var chanList
, view
, table
, obj
, renderTable
, waiting
;
700 view
= new Utilityview('Channel List');
701 view
.div
.css('overflow-y', 'scroll');
703 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>');
704 table
= table
.appendTo(view
.div
);
710 renderTable = function () {
712 tbody
= table
.children('tbody:first').detach();
713 /*tbody.children().each(function (child) {
716 chan = child.children('td:first').text();
717 for (i = 0; i < chanList.length; i++) {
718 if (chanList[i].channel === chan) {
719 chanList[i].html = child.detach();
724 _
.each(chanList
, function (chan
) {
725 chan
.html
= $(chan
.html
).appendTo(tbody
);
727 table
= table
.append(tbody
);
735 * Adds a channel or channels to the list
736 * @param {Object} channels The channel or Array of channels to add
738 addChannel: function (channels
) {
739 if (!_
.isArray(channels
)) {
740 channels
= [channels
];
742 _
.each(channels
, function (chan
) {
744 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>');
748 chanList
.sort(function (a
, b
) {
749 return b
.num_users
- a
.num_users
;
753 _
.defer(renderTable
);
757 * Show the {@link UtilityView} that will display this channel list
765 prototype: {constructor: this}
774 * A tab to show non-channel and non-query windows to the user
775 * @param {String} name The name of the view
777 var Utilityview = function (name
) {
778 var rand_name
= randomString(15),
779 tmp_divname
= 'kiwi_window_' + rand_name
,
780 tmp_tabname
= 'kiwi_tab_' + rand_name
;
782 this.name
= rand_name
;
785 this.panel
= $('#panel1');
787 if (typeof $('.scroller', this.panel
)[0] === 'undefined') {
788 this.panel
.append('<div id="' + tmp_divname
+ '" class="messages"></div>');
790 $('.scroller', this.panel
).append('<div id="' + tmp_divname
+ '" class="messages"></div>');
793 this.tab
= $('<li id="' + tmp_tabname
+ '">' + this.title
+ '</li>');
794 this.tab
.click(function () {
795 kiwi
.front
.utilityviews
[rand_name
.toLowerCase()].show();
797 $('#kiwi .utilityviewlist ul').append(this.tab
);
798 kiwi
.front
.ui
.doLayoutSize();
800 this.div
= $('#' + tmp_divname
);
801 this.div
.css('overflow', 'hidden');
803 kiwi
.front
.utilityviews
[rand_name
.toLowerCase()] = this;
806 Utilityview
.prototype.name
= null;
807 Utilityview
.prototype.title
= null;
808 Utilityview
.prototype.div
= null;
809 Utilityview
.prototype.tab
= null;
810 Utilityview
.prototype.topic
= ' ';
811 Utilityview
.prototype.panel
= null;
813 * Brings this view to the foreground
815 Utilityview
.prototype.show = function () {
816 $('.messages', this.panel
).removeClass("active");
817 $('#kiwi .userlist ul').removeClass("active");
818 $('#kiwi .toolbars ul li').removeClass("active");
820 this.panel
.css('overflow-y', 'hidden');
821 $('#windows').css('right', 0);
822 // Activate this tab!
823 this.div
.addClass('active');
824 this.tab
.addClass('active');
828 kiwi
.front
.ui
.setTopicText(this.topic
);
829 kiwi
.front
.cur_channel
= this;
831 // If we're using fancy scrolling, refresh it
833 touch_scroll
.refresh();
837 * Sets a new panel to be this view's parent
838 * @param {JQuery} new_panel The new parent
840 Utilityview
.prototype.setPanel = function (new_panel
) {
842 this.panel
= new_panel
;
843 this.panel
.append(this.div
);
847 * Removes the panel from the UI and destroys its contents
849 Utilityview
.prototype.close = function () {
853 if (Tabview
.getCurrentTab() === this) {
854 kiwi
.front
.tabviews
.server
.show();
856 delete kiwi
.front
.utilityviews
[this.name
.toLowerCase()];
859 * Adds the close image to the tab
861 Utilityview
.prototype.addPartImage = function () {
862 this.clearPartImage();
864 // We can't close this tab, so don't have the close image
865 if (this.name
=== 'server') {
869 var del_html
= '<img src="/img/redcross.png" class="tab_part" />';
870 this.tab
.append(del_html
);
872 $('.tab_part', this.tab
).click(function () {
873 if (Tabview
.getCurrentTab().name
!== 'server') {
874 Tabview
.getCurrentTab().close();
879 * Removes the close image from the tab
881 Utilityview
.prototype.clearPartImage = function () {
882 $('#kiwi .toolbars .tab_part').remove();
887 * Floating message box
888 * @param {String} classname The CSS classname to apply to the box
890 var Box = function (classname
) {
891 this.id
= randomString(10);
892 var tmp
= $('<div id="' + this.id
+ '" class="box ' + classname
+ '"><div class="boxarea"></div></div>');
893 $('#kiwi').append(tmp
);
894 this.box
= $('#' + this.id
);
895 this.content
= $('#' + this.id
+ ' .boxarea');
897 this.box
.draggable({ stack
: ".box" });
898 this.content
.click(function () {});
899 //this.box.click(function(){ $(this)..css });
901 Box
.prototype.create = function (name
, classname
) {
904 Box
.prototype.id
= null;
905 Box
.prototype.box
= null;
906 Box
.prototype.content
= null;
907 Box
.prototype.destroy = function () {
910 for (name
in kiwi
.front
.boxes
) {
911 if (kiwi
.front
.boxes
[name
].id
=== this.id
) {
912 delete kiwi
.front
.boxes
[name
];
916 Box
.prototype.height = function () {
917 return this.box
.height();