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