Meta key ignored on global focus for OSX
[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
cee337bb 580 'keydown div': 'process'\r
696a66f8
D
581 },\r
582\r
583 initialize: function () {\r
584 kiwi.app.panels.bind('active', function (active_panel) {\r
6c719c05
D
585 // If it's a channel topic, update and make editable\r
586 if (active_panel.isChannel()) {\r
587 this.setCurrentTopic(active_panel.get('topic') || '');\r
588 this.$el.find('div').attr('contentEditable', true);\r
589\r
590 } else {\r
591 // Not a channel topic.. clear and make uneditable\r
592 this.$el.find('div').attr('contentEditable', false)\r
593 .text('');\r
594 }\r
696a66f8
D
595 }, this);\r
596 },\r
597\r
598 process: function (ev) {\r
599 var inp = $(ev.currentTarget),\r
cee337bb
JA
600 inp_val = inp.text();\r
601 \r
6c719c05
D
602 // Only allow topic editing if this is a channel panel\r
603 if (!kiwi.app.panels.active.isChannel()) {\r
604 return false;\r
605 }\r
cee337bb 606\r
6c719c05
D
607 // If hit return key, update the current topic\r
608 if (ev.keyCode === 13) {\r
696a66f8 609 kiwi.gateway.topic(kiwi.app.panels.active.get('name'), inp_val);\r
6c719c05 610 return false;\r
696a66f8
D
611 }\r
612 },\r
613\r
614 setCurrentTopic: function (new_topic) {\r
615 new_topic = new_topic || '';\r
616\r
617 // We only want a plain text version\r
cee337bb 618 $('div', this.$el).html(formatIRCMsg(_.escape(new_topic)));\r
696a66f8
D
619 }\r
620});\r
621\r
622\r
623\r
624kiwi.view.ControlBox = Backbone.View.extend({\r
625 events: {\r
626 'keydown input.inp': 'process',\r
627 'click .nick': 'showNickChange'\r
628 },\r
629\r
630 initialize: function () {\r
631 var that = this;\r
632\r
633 this.buffer = []; // Stores previously run commands\r
634 this.buffer_pos = 0; // The current position in the buffer\r
635\r
636 this.preprocessor = new InputPreProcessor();\r
637 this.preprocessor.recursive_depth = 5;\r
638\r
639 // Hold tab autocomplete data\r
640 this.tabcomplete = {active: false, data: [], prefix: ''};\r
641\r
642 kiwi.gateway.bind('change:nick', function () {\r
643 $('.nick', that.$el).text(this.get('nick'));\r
644 });\r
645 },\r
646\r
647 showNickChange: function (ev) {\r
648 (new kiwi.view.NickChangeBox()).render();\r
649 },\r
650\r
651 process: function (ev) {\r
652 var that = this,\r
653 inp = $(ev.currentTarget),\r
654 inp_val = inp.val(),\r
655 meta;\r
656\r
657 if (navigator.appVersion.indexOf("Mac") !== -1) {\r
658 meta = ev.ctrlKey;\r
659 } else {\r
660 meta = ev.altKey;\r
661 }\r
662\r
663 // If not a tab key, reset the tabcomplete data\r
664 if (this.tabcomplete.active && ev.keyCode !== 9) {\r
665 this.tabcomplete.active = false;\r
666 this.tabcomplete.data = [];\r
667 this.tabcomplete.prefix = '';\r
668 }\r
669 \r
670 switch (true) {\r
671 case (ev.keyCode === 13): // return\r
672 inp_val = inp_val.trim();\r
673\r
674 if (inp_val) {\r
675 this.processInput(inp_val);\r
676\r
677 this.buffer.push(inp_val);\r
678 this.buffer_pos = this.buffer.length;\r
679 }\r
680\r
681 inp.val('');\r
682\r
683 break;\r
684\r
685 case (ev.keyCode === 38): // up\r
686 if (this.buffer_pos > 0) {\r
687 this.buffer_pos--;\r
688 inp.val(this.buffer[this.buffer_pos]);\r
689 }\r
690 break;\r
691\r
692 case (ev.keyCode === 40): // down\r
693 if (this.buffer_pos < this.buffer.length) {\r
694 this.buffer_pos++;\r
695 inp.val(this.buffer[this.buffer_pos]);\r
696 }\r
697 break;\r
698\r
699 case (ev.keyCode === 37 && meta): // left\r
700 kiwi.app.panels.view.prev();\r
701 return false;\r
702\r
703 case (ev.keyCode === 39 && meta): // right\r
704 kiwi.app.panels.view.next();\r
705 return false;\r
706\r
707 case (ev.keyCode === 9): // tab\r
708 this.tabcomplete.active = true;\r
709 if (_.isEqual(this.tabcomplete.data, [])) {\r
710 // Get possible autocompletions\r
711 var ac_data = [];\r
712 $.each(kiwi.app.panels.active.get('members').models, function (i, member) {\r
713 if (!member) return;\r
714 ac_data.push(member.get('nick'));\r
715 });\r
716 ac_data = _.sortBy(ac_data, function (nick) {\r
717 return nick;\r
718 });\r
719 this.tabcomplete.data = ac_data;\r
720 }\r
721\r
722 if (inp_val[inp[0].selectionStart - 1] === ' ') {\r
723 return false;\r
724 }\r
725 \r
726 (function () {\r
727 var tokens = inp_val.substring(0, inp[0].selectionStart).split(' '),\r
728 val,\r
729 p1,\r
730 newnick,\r
731 range,\r
732 nick = tokens[tokens.length - 1];\r
733 if (this.tabcomplete.prefix === '') {\r
734 this.tabcomplete.prefix = nick;\r
735 }\r
736\r
737 this.tabcomplete.data = _.select(this.tabcomplete.data, function (n) {\r
738 return (n.toLowerCase().indexOf(that.tabcomplete.prefix.toLowerCase()) === 0);\r
739 });\r
740\r
741 if (this.tabcomplete.data.length > 0) {\r
742 p1 = inp[0].selectionStart - (nick.length);\r
743 val = inp_val.substr(0, p1);\r
744 newnick = this.tabcomplete.data.shift();\r
745 this.tabcomplete.data.push(newnick);\r
746 val += newnick;\r
747 val += inp_val.substr(inp[0].selectionStart);\r
748 inp.val(val);\r
749\r
750 if (inp[0].setSelectionRange) {\r
751 inp[0].setSelectionRange(p1 + newnick.length, p1 + newnick.length);\r
752 } else if (inp[0].createTextRange) { // not sure if this bit is actually needed....\r
753 range = inp[0].createTextRange();\r
754 range.collapse(true);\r
755 range.moveEnd('character', p1 + newnick.length);\r
756 range.moveStart('character', p1 + newnick.length);\r
757 range.select();\r
758 }\r
759 }\r
760 }).apply(this);\r
761 return false;\r
762 }\r
763 },\r
764\r
765\r
766 processInput: function (command_raw) {\r
767 var command, params,\r
768 pre_processed;\r
769 \r
770 // The default command\r
771 if (command_raw[0] !== '/') {\r
772 command_raw = '/msg ' + kiwi.app.panels.active.get('name') + ' ' + command_raw;\r
773 }\r
774\r
775 // Process the raw command for any aliases\r
776 this.preprocessor.vars.server = kiwi.gateway.get('name');\r
777 this.preprocessor.vars.channel = kiwi.app.panels.active.get('name');\r
778 this.preprocessor.vars.destination = this.preprocessor.vars.channel;\r
779 command_raw = this.preprocessor.process(command_raw);\r
780\r
781 // Extract the command and parameters\r
782 params = command_raw.split(' ');\r
783 if (params[0][0] === '/') {\r
784 command = params[0].substr(1).toLowerCase();\r
785 params = params.splice(1, params.length - 1);\r
786 } else {\r
787 // Default command\r
788 command = 'msg';\r
789 params.unshift(kiwi.app.panels.active.get('name'));\r
790 }\r
791\r
792 // Trigger the command events\r
793 this.trigger('command', {command: command, params: params});\r
794 this.trigger('command_' + command, {command: command, params: params});\r
795\r
796 // If we didn't have any listeners for this event, fire a special case\r
797 // TODO: This feels dirty. Should this really be done..?\r
798 if (!this._callbacks['command_' + command]) {\r
799 this.trigger('unknown_command', {command: command, params: params});\r
800 }\r
801 }\r
802});\r
803\r
804\r
805\r
806\r
807kiwi.view.StatusMessage = Backbone.View.extend({\r
808 initialize: function () {\r
809 this.$el.hide();\r
810\r
811 // Timer for hiding the message after X seconds\r
812 this.tmr = null;\r
813 },\r
814\r
815 text: function (text, opt) {\r
816 // Defaults\r
817 opt = opt || {};\r
818 opt.type = opt.type || '';\r
819 opt.timeout = opt.timeout || 5000;\r
820\r
821 this.$el.text(text).attr('class', opt.type);\r
822 this.$el.slideDown(kiwi.app.view.doLayout);\r
823\r
824 if (opt.timeout) this.doTimeout(opt.timeout);\r
825 },\r
826\r
827 html: function (html, opt) {\r
828 // Defaults\r
829 opt = opt || {};\r
830 opt.type = opt.type || '';\r
831 opt.timeout = opt.timeout || 5000;\r
832\r
833 this.$el.html(text).attr('class', opt.type);\r
834 this.$el.slideDown(kiwi.app.view.doLayout);\r
835\r
836 if (opt.timeout) this.doTimeout(opt.timeout);\r
837 },\r
838\r
839 hide: function () {\r
840 this.$el.slideUp(kiwi.app.view.doLayout);\r
841 },\r
842\r
843 doTimeout: function (length) {\r
844 if (this.tmr) clearTimeout(this.tmr);\r
845 var that = this;\r
846 this.tmr = setTimeout(function () { that.hide(); }, length);\r
847 }\r
848});\r
849\r
850\r
851\r
852\r
853kiwi.view.ResizeHandler = Backbone.View.extend({\r
854 events: {\r
855 'mousedown': 'startDrag',\r
856 'mouseup': 'stopDrag'\r
857 },\r
858\r
859 initialize: function () {\r
860 this.dragging = false;\r
861 this.starting_width = {};\r
862\r
863 $(window).on('mousemove', $.proxy(this.onDrag, this));\r
864 },\r
865\r
866 startDrag: function (event) {\r
867 this.dragging = true;\r
868 },\r
869\r
870 stopDrag: function (event) {\r
871 this.dragging = false;\r
872 },\r
873\r
874 onDrag: function (event) {\r
875 if (!this.dragging) return;\r
876\r
877 this.$el.css('left', event.clientX - (this.$el.outerWidth(true) / 2));\r
878 $('#memberlists').css('width', this.$el.parent().width() - (this.$el.position().left + this.$el.outerWidth()));\r
879 kiwi.app.view.doLayout();\r
880 }\r
881});\r
882\r
883\r
884\r
7de3dd03
D
885kiwi.view.AppToolbar = Backbone.View.extend({\r
886 events: {\r
887 'click .settings': 'clickSettings'\r
888 },\r
889\r
890 initialize: function () {\r
891 console.log('apptoolbar created', this.$el);\r
892 },\r
893\r
894 clickSettings: function (event) {\r
895 console.log('clicked');\r
896 kiwi.app.controlbox.processInput('/settings');\r
897 }\r
898});\r
899\r
900\r
901\r
696a66f8
D
902kiwi.view.Application = Backbone.View.extend({\r
903 initialize: function () {\r
904 $(window).resize(this.doLayout);\r
905 $('#toolbar').resize(this.doLayout);\r
906 $('#controlbox').resize(this.doLayout);\r
907\r
908 this.doLayout();\r
909\r
910 $(document).keydown(this.setKeyFocus);\r
911\r
912 // Confirmation require to leave the page\r
913 window.onbeforeunload = function () {\r
914 if (kiwi.gateway.isConnected()) {\r
915 return 'This will close all KiwiIRC conversations. Are you sure you want to close this window?';\r
916 }\r
917 };\r
918 },\r
919\r
920\r
921 // Globally shift focus to the command input box on a keypress\r
922 setKeyFocus: function (ev) {\r
923 // If we're copying text, don't shift focus\r
6c80fe5c 924 if (ev.ctrlKey || ev.altKey || ev.metaKey) {\r
696a66f8
D
925 return;\r
926 }\r
927\r
928 // If we're typing into an input box somewhere, ignore\r
6c719c05 929 if ((ev.target.tagName.toLowerCase() === 'input') || $(ev.target).attr('contenteditable')) {\r
696a66f8
D
930 return;\r
931 }\r
932\r
933 $('#controlbox .inp').focus();\r
934 },\r
935\r
936\r
937 doLayout: function () {\r
938 var el_panels = $('#panels');\r
939 var el_memberlists = $('#memberlists');\r
940 var el_toolbar = $('#toolbar');\r
941 var el_controlbox = $('#controlbox');\r
942 var el_resize_handle = $('#memberlists_resize_handle');\r
943\r
944 var css_heights = {\r
945 top: el_toolbar.outerHeight(true),\r
946 bottom: el_controlbox.outerHeight(true)\r
947 };\r
948\r
949 el_panels.css(css_heights);\r
950 el_memberlists.css(css_heights);\r
951 el_resize_handle.css(css_heights);\r
952\r
953 if (el_memberlists.css('display') != 'none') {\r
954 // Handle + panels to the side of the memberlist\r
955 el_panels.css('right', el_memberlists.outerWidth(true) + el_resize_handle.outerWidth(true));\r
956 el_resize_handle.css('left', el_memberlists.position().left - el_resize_handle.outerWidth(true));\r
957 } else {\r
958 // Memberlist is hidden so handle + panels to the right edge\r
959 el_panels.css('right', el_resize_handle.outerWidth(true));\r
960 el_resize_handle.css('left', el_panels.outerWidth(true));\r
961 }\r
962 },\r
963\r
964\r
965 alertWindow: function (title) {\r
966 if (!this.alertWindowTimer) {\r
967 this.alertWindowTimer = new (function () {\r
968 var that = this;\r
969 var tmr;\r
970 var has_focus = true;\r
971 var state = 0;\r
972 var default_title = 'Kiwi IRC';\r
973 var title = 'Kiwi IRC';\r
974\r
975 this.setTitle = function (new_title) {\r
976 new_title = new_title || default_title;\r
977 window.document.title = new_title;\r
978 return new_title;\r
979 };\r
980\r
981 this.start = function (new_title) {\r
982 // Don't alert if we already have focus\r
983 if (has_focus) return;\r
984\r
985 title = new_title;\r
986 if (tmr) return;\r
987 tmr = setInterval(this.update, 1000);\r
988 };\r
989\r
990 this.stop = function () {\r
991 // Stop the timer and clear the title\r
992 if (tmr) clearInterval(tmr);\r
993 tmr = null;\r
994 this.setTitle();\r
995\r
996 // Some browsers don't always update the last title correctly\r
997 // Wait a few seconds and then reset\r
998 setTimeout(this.reset, 2000);\r
999 };\r
1000\r
1001 this.reset = function () {\r
1002 if (tmr) return;\r
1003 that.setTitle();\r
1004 };\r
1005\r
1006\r
1007 this.update = function () {\r
1008 if (state === 0) {\r
1009 that.setTitle(title);\r
1010 state = 1;\r
1011 } else {\r
1012 that.setTitle();\r
1013 state = 0;\r
1014 }\r
1015 };\r
1016\r
1017 $(window).focus(function (event) {\r
1018 has_focus = true;\r
1019 that.stop();\r
1020\r
1021 // Some browsers don't always update the last title correctly\r
1022 // Wait a few seconds and then reset\r
1023 setTimeout(that.reset, 2000);\r
1024 });\r
1025\r
1026 $(window).blur(function (event) {\r
1027 has_focus = false;\r
1028 });\r
1029 })();\r
1030 }\r
1031\r
1032 this.alertWindowTimer.start(title);\r
1033 },\r
1034\r
1035\r
1036 barsHide: function (instant) {\r
1037 var that = this;\r
1038\r
1039 if (!instant) {\r
1040 $('#toolbar').slideUp({queue: false, duration: 400, step: this.doLayout});\r
1041 $('#controlbox').slideUp({queue: false, duration: 400, step: this.doLayout});\r
1042 } else {\r
1043 $('#toolbar').slideUp(0);\r
1044 $('#controlbox').slideUp(0);\r
1045 this.doLayout();\r
1046 }\r
1047 },\r
1048\r
1049 barsShow: function (instant) {\r
1050 var that = this;\r
1051\r
1052 if (!instant) {\r
1053 $('#toolbar').slideDown({queue: false, duration: 400, step: this.doLayout});\r
1054 $('#controlbox').slideDown({queue: false, duration: 400, step: this.doLayout});\r
1055 } else {\r
1056 $('#toolbar').slideDown(0);\r
1057 $('#controlbox').slideDown(0);\r
1058 this.doLayout();\r
1059 }\r
1060 }\r
1061});