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