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