Merge with development branch (New server 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
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
5998fd56 626 'keydown .inp': 'process',\r
696a66f8
D
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
5998fd56
D
675 $.each(inp_val.split('\n'), function (idx, line) {\r
676 that.processInput(line);\r
677 });\r
696a66f8
D
678\r
679 this.buffer.push(inp_val);\r
680 this.buffer_pos = this.buffer.length;\r
681 }\r
682\r
683 inp.val('');\r
5998fd56 684 return false;\r
696a66f8
D
685\r
686 break;\r
687\r
688 case (ev.keyCode === 38): // up\r
689 if (this.buffer_pos > 0) {\r
690 this.buffer_pos--;\r
691 inp.val(this.buffer[this.buffer_pos]);\r
692 }\r
693 break;\r
694\r
695 case (ev.keyCode === 40): // down\r
696 if (this.buffer_pos < this.buffer.length) {\r
697 this.buffer_pos++;\r
698 inp.val(this.buffer[this.buffer_pos]);\r
699 }\r
700 break;\r
701\r
702 case (ev.keyCode === 37 && meta): // left\r
703 kiwi.app.panels.view.prev();\r
704 return false;\r
705\r
706 case (ev.keyCode === 39 && meta): // right\r
707 kiwi.app.panels.view.next();\r
708 return false;\r
709\r
710 case (ev.keyCode === 9): // tab\r
711 this.tabcomplete.active = true;\r
712 if (_.isEqual(this.tabcomplete.data, [])) {\r
713 // Get possible autocompletions\r
714 var ac_data = [];\r
715 $.each(kiwi.app.panels.active.get('members').models, function (i, member) {\r
716 if (!member) return;\r
717 ac_data.push(member.get('nick'));\r
718 });\r
719 ac_data = _.sortBy(ac_data, function (nick) {\r
720 return nick;\r
721 });\r
722 this.tabcomplete.data = ac_data;\r
723 }\r
724\r
725 if (inp_val[inp[0].selectionStart - 1] === ' ') {\r
726 return false;\r
727 }\r
728 \r
729 (function () {\r
730 var tokens = inp_val.substring(0, inp[0].selectionStart).split(' '),\r
731 val,\r
732 p1,\r
733 newnick,\r
734 range,\r
735 nick = tokens[tokens.length - 1];\r
736 if (this.tabcomplete.prefix === '') {\r
737 this.tabcomplete.prefix = nick;\r
738 }\r
739\r
740 this.tabcomplete.data = _.select(this.tabcomplete.data, function (n) {\r
741 return (n.toLowerCase().indexOf(that.tabcomplete.prefix.toLowerCase()) === 0);\r
742 });\r
743\r
744 if (this.tabcomplete.data.length > 0) {\r
745 p1 = inp[0].selectionStart - (nick.length);\r
746 val = inp_val.substr(0, p1);\r
747 newnick = this.tabcomplete.data.shift();\r
748 this.tabcomplete.data.push(newnick);\r
749 val += newnick;\r
750 val += inp_val.substr(inp[0].selectionStart);\r
751 inp.val(val);\r
752\r
753 if (inp[0].setSelectionRange) {\r
754 inp[0].setSelectionRange(p1 + newnick.length, p1 + newnick.length);\r
755 } else if (inp[0].createTextRange) { // not sure if this bit is actually needed....\r
756 range = inp[0].createTextRange();\r
757 range.collapse(true);\r
758 range.moveEnd('character', p1 + newnick.length);\r
759 range.moveStart('character', p1 + newnick.length);\r
760 range.select();\r
761 }\r
762 }\r
763 }).apply(this);\r
764 return false;\r
765 }\r
766 },\r
767\r
768\r
769 processInput: function (command_raw) {\r
770 var command, params,\r
771 pre_processed;\r
772 \r
773 // The default command\r
774 if (command_raw[0] !== '/') {\r
775 command_raw = '/msg ' + kiwi.app.panels.active.get('name') + ' ' + command_raw;\r
776 }\r
777\r
778 // Process the raw command for any aliases\r
779 this.preprocessor.vars.server = kiwi.gateway.get('name');\r
780 this.preprocessor.vars.channel = kiwi.app.panels.active.get('name');\r
781 this.preprocessor.vars.destination = this.preprocessor.vars.channel;\r
782 command_raw = this.preprocessor.process(command_raw);\r
783\r
784 // Extract the command and parameters\r
785 params = command_raw.split(' ');\r
786 if (params[0][0] === '/') {\r
787 command = params[0].substr(1).toLowerCase();\r
788 params = params.splice(1, params.length - 1);\r
789 } else {\r
790 // Default command\r
791 command = 'msg';\r
792 params.unshift(kiwi.app.panels.active.get('name'));\r
793 }\r
794\r
795 // Trigger the command events\r
796 this.trigger('command', {command: command, params: params});\r
797 this.trigger('command_' + command, {command: command, params: params});\r
798\r
799 // If we didn't have any listeners for this event, fire a special case\r
800 // TODO: This feels dirty. Should this really be done..?\r
801 if (!this._callbacks['command_' + command]) {\r
802 this.trigger('unknown_command', {command: command, params: params});\r
803 }\r
804 }\r
805});\r
806\r
807\r
808\r
809\r
810kiwi.view.StatusMessage = Backbone.View.extend({\r
811 initialize: function () {\r
812 this.$el.hide();\r
813\r
814 // Timer for hiding the message after X seconds\r
815 this.tmr = null;\r
816 },\r
817\r
818 text: function (text, opt) {\r
819 // Defaults\r
820 opt = opt || {};\r
821 opt.type = opt.type || '';\r
822 opt.timeout = opt.timeout || 5000;\r
823\r
824 this.$el.text(text).attr('class', opt.type);\r
825 this.$el.slideDown(kiwi.app.view.doLayout);\r
826\r
827 if (opt.timeout) this.doTimeout(opt.timeout);\r
828 },\r
829\r
830 html: function (html, opt) {\r
831 // Defaults\r
832 opt = opt || {};\r
833 opt.type = opt.type || '';\r
834 opt.timeout = opt.timeout || 5000;\r
835\r
836 this.$el.html(text).attr('class', opt.type);\r
837 this.$el.slideDown(kiwi.app.view.doLayout);\r
838\r
839 if (opt.timeout) this.doTimeout(opt.timeout);\r
840 },\r
841\r
842 hide: function () {\r
843 this.$el.slideUp(kiwi.app.view.doLayout);\r
844 },\r
845\r
846 doTimeout: function (length) {\r
847 if (this.tmr) clearTimeout(this.tmr);\r
848 var that = this;\r
849 this.tmr = setTimeout(function () { that.hide(); }, length);\r
850 }\r
851});\r
852\r
853\r
854\r
855\r
856kiwi.view.ResizeHandler = Backbone.View.extend({\r
857 events: {\r
858 'mousedown': 'startDrag',\r
859 'mouseup': 'stopDrag'\r
860 },\r
861\r
862 initialize: function () {\r
863 this.dragging = false;\r
864 this.starting_width = {};\r
865\r
866 $(window).on('mousemove', $.proxy(this.onDrag, this));\r
867 },\r
868\r
869 startDrag: function (event) {\r
870 this.dragging = true;\r
871 },\r
872\r
873 stopDrag: function (event) {\r
874 this.dragging = false;\r
875 },\r
876\r
877 onDrag: function (event) {\r
878 if (!this.dragging) return;\r
879\r
880 this.$el.css('left', event.clientX - (this.$el.outerWidth(true) / 2));\r
881 $('#memberlists').css('width', this.$el.parent().width() - (this.$el.position().left + this.$el.outerWidth()));\r
882 kiwi.app.view.doLayout();\r
883 }\r
884});\r
885\r
886\r
887\r
7de3dd03
D
888kiwi.view.AppToolbar = Backbone.View.extend({\r
889 events: {\r
890 'click .settings': 'clickSettings'\r
891 },\r
892\r
893 initialize: function () {\r
894 console.log('apptoolbar created', this.$el);\r
895 },\r
896\r
897 clickSettings: function (event) {\r
898 console.log('clicked');\r
899 kiwi.app.controlbox.processInput('/settings');\r
900 }\r
901});\r
902\r
903\r
904\r
696a66f8
D
905kiwi.view.Application = Backbone.View.extend({\r
906 initialize: function () {\r
907 $(window).resize(this.doLayout);\r
908 $('#toolbar').resize(this.doLayout);\r
909 $('#controlbox').resize(this.doLayout);\r
910\r
911 this.doLayout();\r
912\r
913 $(document).keydown(this.setKeyFocus);\r
914\r
915 // Confirmation require to leave the page\r
916 window.onbeforeunload = function () {\r
917 if (kiwi.gateway.isConnected()) {\r
918 return 'This will close all KiwiIRC conversations. Are you sure you want to close this window?';\r
919 }\r
920 };\r
921 },\r
922\r
923\r
924 // Globally shift focus to the command input box on a keypress\r
925 setKeyFocus: function (ev) {\r
926 // If we're copying text, don't shift focus\r
6c80fe5c 927 if (ev.ctrlKey || ev.altKey || ev.metaKey) {\r
696a66f8
D
928 return;\r
929 }\r
930\r
931 // If we're typing into an input box somewhere, ignore\r
6c719c05 932 if ((ev.target.tagName.toLowerCase() === 'input') || $(ev.target).attr('contenteditable')) {\r
696a66f8
D
933 return;\r
934 }\r
935\r
936 $('#controlbox .inp').focus();\r
937 },\r
938\r
939\r
940 doLayout: function () {\r
941 var el_panels = $('#panels');\r
942 var el_memberlists = $('#memberlists');\r
943 var el_toolbar = $('#toolbar');\r
944 var el_controlbox = $('#controlbox');\r
945 var el_resize_handle = $('#memberlists_resize_handle');\r
946\r
947 var css_heights = {\r
948 top: el_toolbar.outerHeight(true),\r
949 bottom: el_controlbox.outerHeight(true)\r
950 };\r
951\r
cacc0359
D
952\r
953 // If any elements are not visible, full size the panals instead\r
954 if (!el_toolbar.is(':visible')) {\r
955 css_heights.top = 0;\r
956 }\r
957\r
958 if (!el_controlbox.is(':visible')) {\r
959 css_heights.bottom = 0;\r
960 }\r
961\r
962 // Apply the CSS sizes\r
696a66f8
D
963 el_panels.css(css_heights);\r
964 el_memberlists.css(css_heights);\r
965 el_resize_handle.css(css_heights);\r
966\r
cacc0359 967 // Set the panels width depending on the memberlist visibility\r
696a66f8
D
968 if (el_memberlists.css('display') != 'none') {\r
969 // Handle + panels to the side of the memberlist\r
970 el_panels.css('right', el_memberlists.outerWidth(true) + el_resize_handle.outerWidth(true));\r
971 el_resize_handle.css('left', el_memberlists.position().left - el_resize_handle.outerWidth(true));\r
972 } else {\r
973 // Memberlist is hidden so handle + panels to the right edge\r
974 el_panels.css('right', el_resize_handle.outerWidth(true));\r
975 el_resize_handle.css('left', el_panels.outerWidth(true));\r
976 }\r
977 },\r
978\r
979\r
980 alertWindow: function (title) {\r
981 if (!this.alertWindowTimer) {\r
982 this.alertWindowTimer = new (function () {\r
983 var that = this;\r
984 var tmr;\r
985 var has_focus = true;\r
986 var state = 0;\r
987 var default_title = 'Kiwi IRC';\r
988 var title = 'Kiwi IRC';\r
989\r
990 this.setTitle = function (new_title) {\r
991 new_title = new_title || default_title;\r
992 window.document.title = new_title;\r
993 return new_title;\r
994 };\r
995\r
996 this.start = function (new_title) {\r
997 // Don't alert if we already have focus\r
998 if (has_focus) return;\r
999\r
1000 title = new_title;\r
1001 if (tmr) return;\r
1002 tmr = setInterval(this.update, 1000);\r
1003 };\r
1004\r
1005 this.stop = function () {\r
1006 // Stop the timer and clear the title\r
1007 if (tmr) clearInterval(tmr);\r
1008 tmr = null;\r
1009 this.setTitle();\r
1010\r
1011 // Some browsers don't always update the last title correctly\r
1012 // Wait a few seconds and then reset\r
1013 setTimeout(this.reset, 2000);\r
1014 };\r
1015\r
1016 this.reset = function () {\r
1017 if (tmr) return;\r
1018 that.setTitle();\r
1019 };\r
1020\r
1021\r
1022 this.update = function () {\r
1023 if (state === 0) {\r
1024 that.setTitle(title);\r
1025 state = 1;\r
1026 } else {\r
1027 that.setTitle();\r
1028 state = 0;\r
1029 }\r
1030 };\r
1031\r
1032 $(window).focus(function (event) {\r
1033 has_focus = true;\r
1034 that.stop();\r
1035\r
1036 // Some browsers don't always update the last title correctly\r
1037 // Wait a few seconds and then reset\r
1038 setTimeout(that.reset, 2000);\r
1039 });\r
1040\r
1041 $(window).blur(function (event) {\r
1042 has_focus = false;\r
1043 });\r
1044 })();\r
1045 }\r
1046\r
1047 this.alertWindowTimer.start(title);\r
1048 },\r
1049\r
1050\r
1051 barsHide: function (instant) {\r
1052 var that = this;\r
1053\r
1054 if (!instant) {\r
1055 $('#toolbar').slideUp({queue: false, duration: 400, step: this.doLayout});\r
1056 $('#controlbox').slideUp({queue: false, duration: 400, step: this.doLayout});\r
1057 } else {\r
1058 $('#toolbar').slideUp(0);\r
1059 $('#controlbox').slideUp(0);\r
1060 this.doLayout();\r
1061 }\r
1062 },\r
1063\r
1064 barsShow: function (instant) {\r
1065 var that = this;\r
1066\r
1067 if (!instant) {\r
1068 $('#toolbar').slideDown({queue: false, duration: 400, step: this.doLayout});\r
1069 $('#controlbox').slideDown({queue: false, duration: 400, step: this.doLayout});\r
1070 } else {\r
1071 $('#toolbar').slideDown(0);\r
1072 $('#controlbox').slideDown(0);\r
1073 this.doLayout();\r
1074 }\r
1075 }\r
1076});