replace server icon with font-awesome
[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
9d5e7df8
D
17 var prefix_css_class = (member.get('modes') || []).join(' ');\r
18 $('<li class="mode ' + prefix_css_class + '"><a class="nick"><span class="prefix">' + member.get("prefix") + '</span>' + member.get("nick") + '</a></li>')\r
696a66f8
D
19 .appendTo($this)\r
20 .data('member', member);\r
21 });\r
22 },\r
23 nickClick: function (x) {\r
495a8dc7
D
24 var $target = $(x.currentTarget).parent('li'),\r
25 member = $target.data('member'),\r
26 userbox;\r
696a66f8 27 \r
495a8dc7
D
28 // If the userbox already exists here, hide it\r
29 if ($target.find('.userbox').length > 0) {\r
30 $('.userbox', this.$el).remove();\r
31 return;\r
32 }\r
33\r
34 userbox = new _kiwi.view.UserBox();\r
696a66f8 35 userbox.member = member;\r
062bae80 36 userbox.channel = this.model.channel;\r
495a8dc7
D
37\r
38 // Remove any existing userboxes\r
696a66f8 39 $('.userbox', this.$el).remove();\r
648e2c01 40\r
062bae80
JA
41 if (!this.model.getByNick(_kiwi.gateway.get('nick')).get('is_op')) {\r
42 userbox.$el.children('.if_op').remove();\r
43 }\r
495a8dc7 44 $target.append(userbox.$el);\r
696a66f8
D
45 },\r
46 show: function () {\r
47 $('#memberlists').children().removeClass('active');\r
48 $(this.el).addClass('active');\r
49 }\r
50});\r
51\r
52\r
53\r
eaaf73b0 54_kiwi.view.UserBox = Backbone.View.extend({\r
696a66f8
D
55 events: {\r
56 'click .query': 'queryClick',\r
57 'click .info': 'infoClick',\r
062bae80
JA
58 'click .slap': 'slapClick',\r
59 'click .op': 'opClick',\r
60 'click .deop': 'deopClick',\r
61 'click .voice': 'voiceClick',\r
62 'click .devoice': 'devoiceClick',\r
63 'click .kick': 'kickClick',\r
64 'click .ban': 'banClick'\r
696a66f8
D
65 },\r
66\r
67 initialize: function () {\r
68 this.$el = $($('#tmpl_userbox').html());\r
69 },\r
70\r
71 queryClick: function (event) {\r
eaaf73b0
D
72 var panel = new _kiwi.model.Query({name: this.member.get('nick')});\r
73 _kiwi.app.panels.add(panel);\r
696a66f8
D
74 panel.view.show();\r
75 },\r
76\r
77 infoClick: function (event) {\r
eaaf73b0 78 _kiwi.app.controlbox.processInput('/whois ' + this.member.get('nick'));\r
696a66f8
D
79 },\r
80\r
81 slapClick: function (event) {\r
eaaf73b0 82 _kiwi.app.controlbox.processInput('/slap ' + this.member.get('nick'));\r
062bae80
JA
83 },\r
84\r
85 opClick: function (event) {\r
86 _kiwi.app.controlbox.processInput('/mode ' + this.channel.get('name') + ' +o ' + this.member.get('nick'));\r
87 },\r
88\r
89 deopClick: function (event) {\r
90 _kiwi.app.controlbox.processInput('/mode ' + this.channel.get('name') + ' -o ' + this.member.get('nick'));\r
91 },\r
92\r
93 voiceClick: function (event) {\r
94 _kiwi.app.controlbox.processInput('/mode ' + this.channel.get('name') + ' +v ' + this.member.get('nick'));\r
95 },\r
96\r
97 devoiceClick: function (event) {\r
98 _kiwi.app.controlbox.processInput('/mode ' + this.channel.get('name') + ' -v ' + this.member.get('nick'));\r
99 },\r
100\r
101 kickClick: function (event) {\r
102 // TODO: Enable the use of a custom kick message\r
103 _kiwi.app.controlbox.processInput('/kick ' + this.member.get('nick') + ' Bye!');\r
104 },\r
105\r
106 banClick: function (event) {\r
107 // TODO: Set ban on host, not just on nick\r
108 _kiwi.app.controlbox.processInput('/mode ' + this.channel.get('name') + ' +b ' + this.member.get('nick') + '!*');\r
696a66f8
D
109 }\r
110});\r
111\r
eaaf73b0 112_kiwi.view.NickChangeBox = Backbone.View.extend({\r
696a66f8
D
113 events: {\r
114 'submit': 'changeNick',\r
115 'click .cancel': 'close'\r
116 },\r
117 \r
118 initialize: function () {\r
119 this.$el = $($('#tmpl_nickchange').html());\r
120 },\r
121 \r
122 render: function () {\r
123 // Add the UI component and give it focus\r
eaaf73b0 124 _kiwi.app.controlbox.$el.prepend(this.$el);\r
696a66f8
D
125 this.$el.find('input').focus();\r
126\r
eaaf73b0 127 this.$el.css('bottom', _kiwi.app.controlbox.$el.outerHeight(true));\r
696a66f8
D
128 },\r
129 \r
130 close: function () {\r
131 this.$el.remove();\r
132\r
133 },\r
134\r
135 changeNick: function (event) {\r
136 var that = this;\r
eaaf73b0 137 _kiwi.gateway.changeNick(this.$el.find('input').val(), function (err, val) {\r
696a66f8
D
138 that.close();\r
139 });\r
140 return false;\r
141 }\r
142});\r
143\r
eaaf73b0 144_kiwi.view.ServerSelect = function () {\r
696a66f8
D
145 // Are currently showing all the controlls or just a nick_change box?\r
146 var state = 'all';\r
147\r
148 var model = Backbone.View.extend({\r
149 events: {\r
150 'submit form': 'submitForm',\r
bac3c32e
D
151 'click .show_more': 'showMore',\r
152 'change .have_pass input': 'showPass'\r
696a66f8
D
153 },\r
154\r
155 initialize: function () {\r
3adc7463
D
156 var that = this;\r
157\r
696a66f8
D
158 this.$el = $($('#tmpl_server_select').html());\r
159\r
93e84f75
D
160 // Remove the 'more' link if the server has disabled server changing\r
161 if (_kiwi.app.server_settings && _kiwi.app.server_settings.connection) {\r
162 if (!_kiwi.app.server_settings.connection.allow_change) {\r
163 this.$el.find('.show_more').remove();\r
164 this.$el.addClass('single_server');\r
165 }\r
166 }\r
167\r
168\r
eaaf73b0
D
169 _kiwi.gateway.bind('onconnect', this.networkConnected, this);\r
170 _kiwi.gateway.bind('connecting', this.networkConnecting, this);\r
696a66f8 171\r
eaaf73b0 172 _kiwi.gateway.bind('onirc_error', function (data) {\r
696a66f8
D
173 $('button', this.$el).attr('disabled', null);\r
174\r
175 if (data.error == 'nickname_in_use') {\r
176 this.setStatus('Nickname already taken');\r
177 this.show('nick_change');\r
178 }\r
3adc7463
D
179\r
180 if (data.error == 'password_mismatch') {\r
181 this.setStatus('Incorrect Password');\r
182 this.show('nick_change');\r
183 that.$el.find('.password').select();\r
184 }\r
696a66f8
D
185 }, this);\r
186 },\r
187\r
188 submitForm: function (event) {\r
848e2dca
D
189 event.preventDefault();\r
190\r
191 // Make sure a nick is chosen\r
192 if (!$('input.nick', this.$el).val().trim()) {\r
193 this.setStatus('Select a nickname first!');\r
194 $('input.nick', this.$el).select();\r
195 return;\r
196 }\r
197\r
696a66f8
D
198 if (state === 'nick_change') {\r
199 this.submitNickChange(event);\r
200 } else {\r
201 this.submitLogin(event);\r
202 }\r
203\r
204 $('button', this.$el).attr('disabled', 1);\r
848e2dca 205 return;\r
696a66f8
D
206 },\r
207\r
208 submitLogin: function (event) {\r
209 // If submitting is disabled, don't do anything\r
210 if ($('button', this.$el).attr('disabled')) return;\r
211 \r
212 var values = {\r
bac3c32e
D
213 nick: $('input.nick', this.$el).val(),\r
214 server: $('input.server', this.$el).val(),\r
215 port: $('input.port', this.$el).val(),\r
216 ssl: $('input.ssl', this.$el).prop('checked'),\r
217 password: $('input.password', this.$el).val(),\r
218 channel: $('input.channel', this.$el).val(),\r
219 channel_key: $('input.channel_key', this.$el).val()\r
696a66f8
D
220 };\r
221\r
222 this.trigger('server_connect', values);\r
223 },\r
224\r
225 submitNickChange: function (event) {\r
bac3c32e 226 _kiwi.gateway.changeNick($('input.nick', this.$el).val());\r
696a66f8
D
227 this.networkConnecting();\r
228 },\r
229\r
bac3c32e
D
230 showPass: function (event) {\r
231 if (this.$el.find('tr.have_pass input').is(':checked')) {\r
232 this.$el.find('tr.pass').show().find('input').focus();\r
233 } else {\r
234 this.$el.find('tr.pass').hide().find('input').val('');\r
235 }\r
236 },\r
237\r
696a66f8
D
238 showMore: function (event) {\r
239 $('.more', this.$el).slideDown('fast');\r
bac3c32e 240 $('input.server', this.$el).select();\r
696a66f8
D
241 },\r
242\r
243 populateFields: function (defaults) {\r
2f54e55e 244 var nick, server, port, channel, channel_key, ssl, password;\r
696a66f8
D
245\r
246 defaults = defaults || {};\r
247\r
248 nick = defaults.nick || '';\r
249 server = defaults.server || '';\r
250 port = defaults.port || 6667;\r
251 ssl = defaults.ssl || 0;\r
252 password = defaults.password || '';\r
253 channel = defaults.channel || '';\r
2f54e55e 254 channel_key = defaults.channel_key || '';\r
696a66f8 255\r
bac3c32e
D
256 $('input.nick', this.$el).val(nick);\r
257 $('input.server', this.$el).val(server);\r
258 $('input.port', this.$el).val(port);\r
259 $('input.ssl', this.$el).prop('checked', ssl);\r
260 $('input.password', this.$el).val(password);\r
261 $('input.channel', this.$el).val(channel);\r
262 $('input.channel_key', this.$el).val(channel_key);\r
696a66f8
D
263 },\r
264\r
265 hide: function () {\r
266 this.$el.slideUp();\r
267 },\r
268\r
269 show: function (new_state) {\r
270 new_state = new_state || 'all';\r
271\r
272 this.$el.show();\r
273\r
274 if (new_state === 'all') {\r
275 $('.show_more', this.$el).show();\r
276\r
277 } else if (new_state === 'more') {\r
278 $('.more', this.$el).slideDown('fast');\r
279\r
280 } else if (new_state === 'nick_change') {\r
281 $('.more', this.$el).hide();\r
282 $('.show_more', this.$el).hide();\r
bac3c32e 283 $('input.nick', this.$el).select();\r
696a66f8
D
284 }\r
285\r
286 state = new_state;\r
287 },\r
288\r
289 setStatus: function (text, class_name) {\r
290 $('.status', this.$el)\r
291 .text(text)\r
292 .attr('class', 'status')\r
848e2dca 293 .addClass(class_name||'')\r
696a66f8
D
294 .show();\r
295 },\r
296 clearStatus: function () {\r
297 $('.status', this.$el).hide();\r
298 },\r
299\r
300 networkConnected: function (event) {\r
301 this.setStatus('Connected :)', 'ok');\r
302 $('form', this.$el).hide();\r
303 },\r
304\r
305 networkConnecting: function (event) {\r
306 this.setStatus('Connecting..', 'ok');\r
307 },\r
308\r
309 showError: function (event) {\r
310 this.setStatus('Error connecting', 'error');\r
311 $('button', this.$el).attr('disabled', null);\r
312 this.show();\r
313 }\r
314 });\r
315\r
316\r
317 return new model(arguments);\r
318};\r
319\r
320\r
eaaf73b0 321_kiwi.view.Panel = Backbone.View.extend({\r
696a66f8
D
322 tagName: "div",\r
323 className: "messages",\r
324 events: {\r
0197b21c 325 "click .chan": "chanClick",\r
04d615ad 326 'click .media .open': 'mediaClick',\r
0197b21c
D
327 'mouseenter .msg .nick': 'msgEnter',\r
328 'mouseleave .msg .nick': 'msgLeave'\r
696a66f8
D
329 },\r
330\r
331 initialize: function (options) {\r
332 this.initializePanel(options);\r
333 },\r
334\r
335 initializePanel: function (options) {\r
336 this.$el.css('display', 'none');\r
337 options = options || {};\r
338\r
339 // Containing element for this panel\r
340 if (options.container) {\r
341 this.$container = $(options.container);\r
342 } else {\r
343 this.$container = $('#panels .container1');\r
344 }\r
345\r
346 this.$el.appendTo(this.$container);\r
347\r
348 this.alert_level = 0;\r
349\r
350 this.model.bind('msg', this.newMsg, this);\r
351 this.msg_count = 0;\r
352\r
353 this.model.set({"view": this}, {"silent": true});\r
354 },\r
355\r
356 render: function () {\r
648e2c01
D
357 var that = this;\r
358\r
696a66f8 359 this.$el.empty();\r
648e2c01
D
360 _.each(this.model.get('scrollback'), function (msg) {\r
361 that.newMsg(msg);\r
362 });\r
696a66f8 363 },\r
648e2c01 364\r
696a66f8 365 newMsg: function (msg) {\r
696a66f8 366 var re, line_msg, $this = this.$el,\r
bf3bd4e5
D
367 nick_colour_hex, nick_hex, is_highlight, msg_css_classes = '';\r
368\r
369 // Nick highlight detecting\r
370 if ((new RegExp('\\b' + _kiwi.gateway.get('nick') + '\\b', 'i')).test(msg.msg)) {\r
371 is_highlight = true;\r
372 msg_css_classes += ' highlight';\r
373 }\r
696a66f8
D
374\r
375 // Escape any HTML that may be in here\r
376 msg.msg = $('<div />').text(msg.msg).html();\r
377\r
378 // Make the channels clickable\r
04d615ad 379 re = new RegExp('(?:^|\\s)([' + _kiwi.gateway.get('channel_prefix') + '][^ ,.\\007]+)', 'g');\r
696a66f8 380 msg.msg = msg.msg.replace(re, function (match) {\r
04d615ad 381 return '<a class="chan" data-channel="' + match.trim() + '">' + match + '</a>';\r
696a66f8
D
382 });\r
383\r
384\r
04d615ad 385 // Parse any links found\r
d064a195 386 msg.msg = msg.msg.replace(/(([A-Za-z0-9\-]+\:\/\/)|(www\.))([\w.\-]+)([a-zA-Z]{2,6})(:[0-9]+)?(\/[\w#!:.?$'()[\]*,;~+=&%@!\-\/]*)?/gi, function (url) {\r
8e8c21e1
D
387 var nice = url,\r
388 extra_html = '';\r
696a66f8 389\r
04d615ad 390 // Add the http if no protoocol was found\r
696a66f8
D
391 if (url.match(/^www\./)) {\r
392 url = 'http://' + url;\r
393 }\r
394\r
04d615ad 395 // Shorten the displayed URL if it's going to be too long\r
696a66f8
D
396 if (nice.length > 100) {\r
397 nice = nice.substr(0, 100) + '...';\r
398 }\r
399\r
04d615ad
D
400 // Get any media HTML if supported\r
401 extra_html = _kiwi.view.MediaMessage.buildHtml(url);\r
402\r
403 // Make the link clickable\r
404 return '<a class="link_ext" target="_blank" rel="nofollow" href="' + url + '">' + nice + '</a> ' + extra_html;\r
696a66f8
D
405 });\r
406\r
407\r
408 // Convert IRC formatting into HTML formatting\r
409 msg.msg = formatIRCMsg(msg.msg);\r
410\r
411\r
412 // Add some colours to the nick (Method based on IRSSIs nickcolor.pl)\r
413 nick_colour_hex = (function (nick) {\r
414 var nick_int = 0, rgb;\r
415\r
416 _.map(nick.split(''), function (i) { nick_int += i.charCodeAt(0); });\r
417 rgb = hsl2rgb(nick_int % 255, 70, 35);\r
418 rgb = rgb[2] | (rgb[1] << 8) | (rgb[0] << 16);\r
419\r
420 return '#' + rgb.toString(16);\r
421 })(msg.nick);\r
422\r
423 msg.nick_style = 'color:' + nick_colour_hex + ';';\r
424\r
0197b21c
D
425 // Generate a hex string from the nick to be used as a CSS class name\r
426 nick_hex = msg.nick_css_class = '';\r
427 if (msg.nick) {\r
428 _.map(msg.nick.split(''), function (char) {\r
429 nick_hex += char.charCodeAt(0).toString(16);\r
430 });\r
bf3bd4e5 431 msg_css_classes += ' nick_' + nick_hex;\r
0197b21c
D
432 }\r
433\r
696a66f8 434 // Build up and add the line\r
bf3bd4e5
D
435 msg.msg_css_classes = msg_css_classes;\r
436 line_msg = '<div class="msg <%= type %> <%= msg_css_classes %>"><div class="time"><%- time %></div><div class="nick" style="<%= nick_style %>"><%- nick %></div><div class="text" style="<%= style %>"><%= msg %> </div></div>';\r
696a66f8
D
437 $this.append(_.template(line_msg, msg));\r
438\r
439 // Activity/alerts based on the type of new message\r
440 if (msg.type.match(/^action /)) {\r
441 this.alert('action');\r
3adc7463 442\r
bf3bd4e5 443 } else if (is_highlight) {\r
eaaf73b0 444 _kiwi.app.view.alertWindow('* People are talking!');\r
e5989472 445 _kiwi.app.view.playSound('highlight');\r
696a66f8 446 this.alert('highlight');\r
3adc7463 447\r
696a66f8
D
448 } else {\r
449 // If this is the active panel, send an alert out\r
450 if (this.model.isActive()) {\r
eaaf73b0 451 _kiwi.app.view.alertWindow('* People are talking!');\r
696a66f8
D
452 }\r
453 this.alert('activity');\r
454 }\r
455\r
e5989472
D
456 if (this.model.isQuery() && !this.model.isActive()) {\r
457 _kiwi.app.view.alertWindow('* People are talking!');\r
458 _kiwi.app.view.playSound('highlight');\r
459 }\r
460\r
3adc7463
D
461 // Update the activity counters\r
462 (function () {\r
463 // Only inrement the counters if we're not the active panel\r
464 if (this.model.isActive()) return;\r
465\r
466 var $act = this.model.tab.find('.activity');\r
467 $act.text((parseInt($act.text(), 10) || 0) + 1);\r
468 if ($act.text() === '0') {\r
469 $act.addClass('zero');\r
470 } else {\r
471 $act.removeClass('zero');\r
472 }\r
473 }).apply(this);\r
474\r
696a66f8
D
475 this.scrollToBottom();\r
476\r
477 // Make sure our DOM isn't getting too large (Acts as scrollback)\r
478 this.msg_count++;\r
f0999cef 479 if (this.msg_count > (parseInt(_kiwi.global.settings.get('scrollback'), 10) || 250)) {\r
696a66f8
D
480 $('.msg:first', this.$el).remove();\r
481 this.msg_count--;\r
482 }\r
483 },\r
484 chanClick: function (event) {\r
485 if (event.target) {\r
04d615ad 486 _kiwi.gateway.join($(event.target).data('channel'));\r
696a66f8
D
487 } else {\r
488 // IE...\r
04d615ad 489 _kiwi.gateway.join($(event.srcElement).data('channel'));\r
696a66f8
D
490 }\r
491 },\r
0197b21c 492\r
04d615ad
D
493 mediaClick: function (event) {\r
494 var $media = $(event.target).parents('.media');\r
495 var media_message;\r
496\r
497 if ($media.data('media')) {\r
498 media_message = $media.data('media');\r
499 } else {\r
500 media_message = new _kiwi.view.MediaMessage({el: $media[0]});\r
501 $media.data('media', media_message);\r
502 }\r
503\r
504 $media.data('media', media_message);\r
505\r
506 media_message.open();\r
507 },\r
508\r
3adc7463 509 // Cursor hovers over a message\r
0197b21c
D
510 msgEnter: function (event) {\r
511 var nick_class;\r
512\r
513 // Find a valid class that this element has\r
514 _.each($(event.currentTarget).parent('.msg').attr('class').split(' '), function (css_class) {\r
515 if (css_class.match(/^nick_[a-z0-9]+/i)) {\r
516 nick_class = css_class;\r
517 }\r
518 });\r
519\r
520 // If no class was found..\r
521 if (!nick_class) return;\r
522\r
523 $('.'+nick_class).addClass('global_nick_highlight');\r
524 },\r
525\r
3adc7463 526 // Cursor leaves message\r
0197b21c
D
527 msgLeave: function (event) {\r
528 var nick_class;\r
529\r
530 // Find a valid class that this element has\r
531 _.each($(event.currentTarget).parent('.msg').attr('class').split(' '), function (css_class) {\r
532 if (css_class.match(/^nick_[a-z0-9]+/i)) {\r
533 nick_class = css_class;\r
534 }\r
535 });\r
536\r
537 // If no class was found..\r
538 if (!nick_class) return;\r
539\r
540 $('.'+nick_class).removeClass('global_nick_highlight');\r
541 },\r
542\r
696a66f8
D
543 show: function () {\r
544 var $this = this.$el;\r
545\r
546 // Hide all other panels and show this one\r
547 this.$container.children().css('display', 'none');\r
548 $this.css('display', 'block');\r
549\r
550 // Show this panels memberlist\r
551 var members = this.model.get("members");\r
552 if (members) {\r
e579ff0d 553 $('#memberlists').removeClass('disabled');\r
696a66f8
D
554 members.view.show();\r
555 } else {\r
556 // Memberlist not found for this panel, hide any active ones\r
e579ff0d 557 $('#memberlists').addClass('disabled').children().removeClass('active');\r
696a66f8
D
558 }\r
559\r
eaaf73b0 560 _kiwi.app.view.doLayout();\r
3adc7463
D
561\r
562 // Remove any alerts and activity counters for this panel\r
696a66f8 563 this.alert('none');\r
3adc7463 564 this.model.tab.find('.activity').text('0').addClass('zero');\r
696a66f8
D
565\r
566 this.trigger('active', this.model);\r
3adc7463 567 _kiwi.app.panels.trigger('active', this.model, _kiwi.app.panels.active);\r
aad21f17
D
568\r
569 this.scrollToBottom(true);\r
696a66f8
D
570 },\r
571\r
572\r
573 alert: function (level) {\r
574 // No need to highlight if this si the active panel\r
eaaf73b0 575 if (this.model == _kiwi.app.panels.active) return;\r
696a66f8
D
576\r
577 var types, type_idx;\r
578 types = ['none', 'action', 'activity', 'highlight'];\r
579\r
580 // Default alert level\r
581 level = level || 'none';\r
582\r
583 // If this alert level does not exist, assume clearing current level\r
584 type_idx = _.indexOf(types, level);\r
585 if (!type_idx) {\r
586 level = 'none';\r
587 type_idx = 0;\r
588 }\r
589\r
590 // Only 'upgrade' the alert. Never down (unless clearing)\r
591 if (type_idx !== 0 && type_idx <= this.alert_level) {\r
592 return;\r
593 }\r
594\r
595 // Clear any existing levels\r
596 this.model.tab.removeClass(function (i, css) {\r
597 return (css.match(/\balert_\S+/g) || []).join(' ');\r
598 });\r
599\r
600 // Add the new level if there is one\r
601 if (level !== 'none') {\r
602 this.model.tab.addClass('alert_' + level);\r
603 }\r
604\r
605 this.alert_level = type_idx;\r
606 },\r
607\r
608\r
609 // Scroll to the bottom of the panel\r
aad21f17
D
610 scrollToBottom: function (force_down) {\r
611 // If this isn't the active panel, don't scroll\r
612 if (this.model !== _kiwi.app.panels.active) return;\r
613\r
614 // Don't scroll down if we're scrolled up the panel a little\r
615 if (force_down || this.$container.scrollTop() + this.$container.height() > this.$el.outerHeight() - 150) {\r
616 this.$container[0].scrollTop = this.$container[0].scrollHeight;\r
617 }\r
696a66f8
D
618 }\r
619});\r
620\r
eaaf73b0 621_kiwi.view.Applet = _kiwi.view.Panel.extend({\r
696a66f8
D
622 className: 'applet',\r
623 initialize: function (options) {\r
624 this.initializePanel(options);\r
625 }\r
626});\r
627\r
eaaf73b0 628_kiwi.view.Channel = _kiwi.view.Panel.extend({\r
696a66f8
D
629 initialize: function (options) {\r
630 this.initializePanel(options);\r
631 this.model.bind('change:topic', this.topic, this);\r
567a2f79 632\r
4c2d8d02
D
633 // Only show the loader if this is a channel (ie. not a query)\r
634 if (this.model.isChannel()) {\r
635 this.$el.append('<div class="initial_loader" style="margin:1em;text-align:center;">Joining channel.. <span class="loader"></span></div>');\r
636 }\r
567a2f79
D
637 },\r
638\r
639 // Override the existing newMsg() method to remove the joining channel loader\r
640 newMsg: function () {\r
641 this.$el.find('.initial_loader').slideUp(function () {\r
642 $(this).remove();\r
643 });\r
4c2d8d02 644\r
567a2f79 645 return this.constructor.__super__.newMsg.apply(this, arguments);\r
696a66f8
D
646 },\r
647\r
648 topic: function (topic) {\r
649 if (typeof topic !== 'string' || !topic) {\r
650 topic = this.model.get("topic");\r
651 }\r
652 \r
653 this.model.addMsg('', '== Topic for ' + this.model.get('name') + ' is: ' + topic, 'topic');\r
654\r
655 // If this is the active channel then update the topic bar\r
eaaf73b0
D
656 if (_kiwi.app.panels.active === this) {\r
657 _kiwi.app.topicbar.setCurrentTopic(this.model.get("topic"));\r
696a66f8
D
658 }\r
659 }\r
660});\r
661\r
eaaf73b0
D
662// Model for this = _kiwi.model.PanelList\r
663_kiwi.view.Tabs = Backbone.View.extend({\r
696a66f8
D
664 events: {\r
665 'click li': 'tabClick',\r
666 'click li .part': 'partClick'\r
667 },\r
668\r
669 initialize: function () {\r
670 this.model.on("add", this.panelAdded, this);\r
671 this.model.on("remove", this.panelRemoved, this);\r
672 this.model.on("reset", this.render, this);\r
673\r
674 this.model.on('active', this.panelActive, this);\r
675\r
676 this.tabs_applets = $('ul.applets', this.$el);\r
677 this.tabs_msg = $('ul.channels', this.$el);\r
678\r
eaaf73b0 679 _kiwi.gateway.on('change:name', function (gateway, new_val) {\r
696a66f8
D
680 $('span', this.model.server.tab).text(new_val);\r
681 }, this);\r
682 },\r
683 render: function () {\r
684 var that = this;\r
685\r
686 this.tabs_msg.empty();\r
687 \r
688 // Add the server tab first\r
689 this.model.server.tab\r
690 .data('panel_id', this.model.server.cid)\r
691 .appendTo(this.tabs_msg);\r
692\r
693 // Go through each panel adding its tab\r
694 this.model.forEach(function (panel) {\r
695 // If this is the server panel, ignore as it's already added\r
696 if (panel == that.model.server) return;\r
697\r
698 panel.tab\r
699 .data('panel_id', panel.cid)\r
700 .appendTo(panel.isApplet() ? this.tabs_applets : this.tabs_msg);\r
701 });\r
702\r
eaaf73b0 703 _kiwi.app.view.doLayout();\r
696a66f8
D
704 },\r
705\r
706 updateTabTitle: function (panel, new_title) {\r
707 $('span', panel.tab).text(new_title);\r
708 },\r
709\r
710 panelAdded: function (panel) {\r
711 // Add a tab to the panel\r
3adc7463 712 panel.tab = $('<li><span>' + (panel.get('title') || panel.get('name')) + '</span><div class="activity"></div></li>');\r
696a66f8
D
713\r
714 if (panel.isServer()) {\r
715 panel.tab.addClass('server');\r
4cce3bd7 716 panel.tab.addClass('icon-nonexistant');\r
696a66f8
D
717 }\r
718\r
719 panel.tab.data('panel_id', panel.cid)\r
720 .appendTo(panel.isApplet() ? this.tabs_applets : this.tabs_msg);\r
721\r
722 panel.bind('change:title', this.updateTabTitle);\r
eaaf73b0 723 _kiwi.app.view.doLayout();\r
696a66f8
D
724 },\r
725 panelRemoved: function (panel) {\r
726 panel.tab.remove();\r
727 delete panel.tab;\r
728\r
eaaf73b0 729 _kiwi.app.view.doLayout();\r
696a66f8
D
730 },\r
731\r
3adc7463 732 panelActive: function (panel, previously_active_panel) {\r
696a66f8
D
733 // Remove any existing tabs or part images\r
734 $('.part', this.$el).remove();\r
735 this.tabs_applets.children().removeClass('active');\r
736 this.tabs_msg.children().removeClass('active');\r
737\r
738 panel.tab.addClass('active');\r
739\r
740 // Only show the part image on non-server tabs\r
741 if (!panel.isServer()) {\r
e97e4eb4 742 panel.tab.append('<span class="part icon-nonexistant"></span>');\r
696a66f8
D
743 }\r
744 },\r
745\r
746 tabClick: function (e) {\r
747 var tab = $(e.currentTarget);\r
748\r
749 var panel = this.model.getByCid(tab.data('panel_id'));\r
750 if (!panel) {\r
751 // A panel wasn't found for this tab... wadda fuck\r
752 return;\r
753 }\r
754\r
755 panel.view.show();\r
756 },\r
757\r
758 partClick: function (e) {\r
759 var tab = $(e.currentTarget).parent();\r
760 var panel = this.model.getByCid(tab.data('panel_id'));\r
761\r
762 // Only need to part if it's a channel\r
763 // If the nicklist is empty, we haven't joined the channel as yet\r
764 if (panel.isChannel() && panel.get('members').models.length > 0) {\r
eaaf73b0 765 _kiwi.gateway.part(panel.get('name'));\r
696a66f8
D
766 } else {\r
767 panel.close();\r
768 }\r
769 },\r
770\r
771 next: function () {\r
eaaf73b0 772 var next = _kiwi.app.panels.active.tab.next();\r
696a66f8
D
773 if (!next.length) next = $('li:first', this.tabs_msgs);\r
774\r
775 next.click();\r
776 },\r
777 prev: function () {\r
eaaf73b0 778 var prev = _kiwi.app.panels.active.tab.prev();\r
696a66f8
D
779 if (!prev.length) prev = $('li:last', this.tabs_msgs);\r
780\r
781 prev.click();\r
782 }\r
783});\r
784\r
785\r
786\r
eaaf73b0 787_kiwi.view.TopicBar = Backbone.View.extend({\r
696a66f8 788 events: {\r
cee337bb 789 'keydown div': 'process'\r
696a66f8
D
790 },\r
791\r
792 initialize: function () {\r
eaaf73b0 793 _kiwi.app.panels.bind('active', function (active_panel) {\r
6c719c05
D
794 // If it's a channel topic, update and make editable\r
795 if (active_panel.isChannel()) {\r
796 this.setCurrentTopic(active_panel.get('topic') || '');\r
797 this.$el.find('div').attr('contentEditable', true);\r
798\r
799 } else {\r
800 // Not a channel topic.. clear and make uneditable\r
801 this.$el.find('div').attr('contentEditable', false)\r
802 .text('');\r
803 }\r
696a66f8
D
804 }, this);\r
805 },\r
806\r
807 process: function (ev) {\r
808 var inp = $(ev.currentTarget),\r
cee337bb
JA
809 inp_val = inp.text();\r
810 \r
6c719c05 811 // Only allow topic editing if this is a channel panel\r
eaaf73b0 812 if (!_kiwi.app.panels.active.isChannel()) {\r
6c719c05
D
813 return false;\r
814 }\r
cee337bb 815\r
6c719c05
D
816 // If hit return key, update the current topic\r
817 if (ev.keyCode === 13) {\r
eaaf73b0 818 _kiwi.gateway.topic(_kiwi.app.panels.active.get('name'), inp_val);\r
6c719c05 819 return false;\r
696a66f8
D
820 }\r
821 },\r
822\r
823 setCurrentTopic: function (new_topic) {\r
824 new_topic = new_topic || '';\r
825\r
826 // We only want a plain text version\r
cee337bb 827 $('div', this.$el).html(formatIRCMsg(_.escape(new_topic)));\r
696a66f8
D
828 }\r
829});\r
830\r
831\r
832\r
eaaf73b0 833_kiwi.view.ControlBox = Backbone.View.extend({\r
696a66f8 834 events: {\r
5998fd56 835 'keydown .inp': 'process',\r
696a66f8
D
836 'click .nick': 'showNickChange'\r
837 },\r
838\r
839 initialize: function () {\r
840 var that = this;\r
841\r
842 this.buffer = []; // Stores previously run commands\r
843 this.buffer_pos = 0; // The current position in the buffer\r
844\r
845 this.preprocessor = new InputPreProcessor();\r
846 this.preprocessor.recursive_depth = 5;\r
847\r
848 // Hold tab autocomplete data\r
849 this.tabcomplete = {active: false, data: [], prefix: ''};\r
850\r
eaaf73b0 851 _kiwi.gateway.bind('change:nick', function () {\r
696a66f8
D
852 $('.nick', that.$el).text(this.get('nick'));\r
853 });\r
854 },\r
855\r
856 showNickChange: function (ev) {\r
eaaf73b0 857 (new _kiwi.view.NickChangeBox()).render();\r
696a66f8
D
858 },\r
859\r
860 process: function (ev) {\r
861 var that = this,\r
862 inp = $(ev.currentTarget),\r
863 inp_val = inp.val(),\r
864 meta;\r
865\r
866 if (navigator.appVersion.indexOf("Mac") !== -1) {\r
7bfc8043 867 meta = ev.metaKey;\r
696a66f8
D
868 } else {\r
869 meta = ev.altKey;\r
870 }\r
871\r
872 // If not a tab key, reset the tabcomplete data\r
873 if (this.tabcomplete.active && ev.keyCode !== 9) {\r
874 this.tabcomplete.active = false;\r
875 this.tabcomplete.data = [];\r
876 this.tabcomplete.prefix = '';\r
877 }\r
878 \r
879 switch (true) {\r
880 case (ev.keyCode === 13): // return\r
881 inp_val = inp_val.trim();\r
882\r
883 if (inp_val) {\r
5998fd56
D
884 $.each(inp_val.split('\n'), function (idx, line) {\r
885 that.processInput(line);\r
886 });\r
696a66f8
D
887\r
888 this.buffer.push(inp_val);\r
889 this.buffer_pos = this.buffer.length;\r
890 }\r
891\r
892 inp.val('');\r
5998fd56 893 return false;\r
696a66f8
D
894\r
895 break;\r
896\r
897 case (ev.keyCode === 38): // up\r
898 if (this.buffer_pos > 0) {\r
899 this.buffer_pos--;\r
900 inp.val(this.buffer[this.buffer_pos]);\r
901 }\r
902 break;\r
903\r
904 case (ev.keyCode === 40): // down\r
905 if (this.buffer_pos < this.buffer.length) {\r
906 this.buffer_pos++;\r
907 inp.val(this.buffer[this.buffer_pos]);\r
908 }\r
909 break;\r
910\r
7bfc8043 911 case (ev.keyCode === 219 && meta): // [ + meta\r
eaaf73b0 912 _kiwi.app.panels.view.prev();\r
696a66f8
D
913 return false;\r
914\r
7bfc8043 915 case (ev.keyCode === 221 && meta): // ] + meta\r
eaaf73b0 916 _kiwi.app.panels.view.next();\r
696a66f8
D
917 return false;\r
918\r
919 case (ev.keyCode === 9): // tab\r
920 this.tabcomplete.active = true;\r
921 if (_.isEqual(this.tabcomplete.data, [])) {\r
922 // Get possible autocompletions\r
923 var ac_data = [];\r
eaaf73b0 924 $.each(_kiwi.app.panels.active.get('members').models, function (i, member) {\r
696a66f8
D
925 if (!member) return;\r
926 ac_data.push(member.get('nick'));\r
927 });\r
928 ac_data = _.sortBy(ac_data, function (nick) {\r
929 return nick;\r
930 });\r
931 this.tabcomplete.data = ac_data;\r
932 }\r
933\r
934 if (inp_val[inp[0].selectionStart - 1] === ' ') {\r
935 return false;\r
936 }\r
937 \r
938 (function () {\r
50f92d87
D
939 var tokens, // Words before the cursor position\r
940 val, // New value being built up\r
941 p1, // Position in the value just before the nick \r
942 newnick, // New nick to be displayed (cycles through)\r
943 range, // TextRange for setting new text cursor position\r
944 nick, // Current nick in the value\r
945 trailing = ': '; // Text to be inserted after a tabbed nick\r
946\r
947 tokens = inp_val.substring(0, inp[0].selectionStart).split(' ');\r
948 if (tokens[tokens.length-1] == ':')\r
949 tokens.pop();\r
950\r
951 nick = tokens[tokens.length - 1];\r
952\r
696a66f8
D
953 if (this.tabcomplete.prefix === '') {\r
954 this.tabcomplete.prefix = nick;\r
955 }\r
956\r
957 this.tabcomplete.data = _.select(this.tabcomplete.data, function (n) {\r
958 return (n.toLowerCase().indexOf(that.tabcomplete.prefix.toLowerCase()) === 0);\r
959 });\r
960\r
961 if (this.tabcomplete.data.length > 0) {\r
50f92d87 962 // Get the current value before cursor position\r
696a66f8
D
963 p1 = inp[0].selectionStart - (nick.length);\r
964 val = inp_val.substr(0, p1);\r
50f92d87
D
965\r
966 // Include the current selected nick\r
696a66f8
D
967 newnick = this.tabcomplete.data.shift();\r
968 this.tabcomplete.data.push(newnick);\r
969 val += newnick;\r
50f92d87
D
970\r
971 if (inp_val.substr(inp[0].selectionStart, 2) !== trailing)\r
972 val += trailing;\r
973\r
974 // Now include the rest of the current value\r
696a66f8 975 val += inp_val.substr(inp[0].selectionStart);\r
50f92d87 976\r
696a66f8
D
977 inp.val(val);\r
978\r
50f92d87 979 // Move the cursor position to the end of the nick\r
696a66f8 980 if (inp[0].setSelectionRange) {\r
50f92d87 981 inp[0].setSelectionRange(p1 + newnick.length + trailing.length, p1 + newnick.length + trailing.length);\r
696a66f8
D
982 } else if (inp[0].createTextRange) { // not sure if this bit is actually needed....\r
983 range = inp[0].createTextRange();\r
984 range.collapse(true);\r
50f92d87
D
985 range.moveEnd('character', p1 + newnick.length + trailing.length);\r
986 range.moveStart('character', p1 + newnick.length + trailing.length);\r
696a66f8
D
987 range.select();\r
988 }\r
989 }\r
990 }).apply(this);\r
991 return false;\r
992 }\r
993 },\r
994\r
995\r
996 processInput: function (command_raw) {\r
997 var command, params,\r
998 pre_processed;\r
999 \r
1000 // The default command\r
c6f09dd0
D
1001 if (command_raw[0] !== '/' || command_raw.substr(0, 2) === '//') {\r
1002 // Remove any slash escaping at the start (ie. //)\r
1003 command_raw = command_raw.replace(/^\/\//, '/');\r
1004\r
1005 // Prepend the default command\r
eaaf73b0 1006 command_raw = '/msg ' + _kiwi.app.panels.active.get('name') + ' ' + command_raw;\r
696a66f8
D
1007 }\r
1008\r
1009 // Process the raw command for any aliases\r
eaaf73b0
D
1010 this.preprocessor.vars.server = _kiwi.gateway.get('name');\r
1011 this.preprocessor.vars.channel = _kiwi.app.panels.active.get('name');\r
696a66f8
D
1012 this.preprocessor.vars.destination = this.preprocessor.vars.channel;\r
1013 command_raw = this.preprocessor.process(command_raw);\r
1014\r
1015 // Extract the command and parameters\r
1016 params = command_raw.split(' ');\r
1017 if (params[0][0] === '/') {\r
1018 command = params[0].substr(1).toLowerCase();\r
1019 params = params.splice(1, params.length - 1);\r
1020 } else {\r
1021 // Default command\r
1022 command = 'msg';\r
eaaf73b0 1023 params.unshift(_kiwi.app.panels.active.get('name'));\r
696a66f8
D
1024 }\r
1025\r
1026 // Trigger the command events\r
1027 this.trigger('command', {command: command, params: params});\r
6c58c492 1028 this.trigger('command:' + command, {command: command, params: params});\r
696a66f8
D
1029\r
1030 // If we didn't have any listeners for this event, fire a special case\r
1031 // TODO: This feels dirty. Should this really be done..?\r
6c58c492 1032 if (!this._callbacks['command:' + command]) {\r
696a66f8
D
1033 this.trigger('unknown_command', {command: command, params: params});\r
1034 }\r
4133f39b
D
1035 },\r
1036\r
1037\r
1038 addPluginIcon: function ($icon) {\r
1039 var $tool = $('<div class="tool"></div>').append($icon);\r
1040 this.$el.find('.input_tools').append($tool);\r
1041 _kiwi.app.view.doLayout();\r
696a66f8
D
1042 }\r
1043});\r
1044\r
1045\r
1046\r
1047\r
eaaf73b0 1048_kiwi.view.StatusMessage = Backbone.View.extend({\r
696a66f8
D
1049 initialize: function () {\r
1050 this.$el.hide();\r
1051\r
1052 // Timer for hiding the message after X seconds\r
1053 this.tmr = null;\r
1054 },\r
1055\r
1056 text: function (text, opt) {\r
1057 // Defaults\r
1058 opt = opt || {};\r
1059 opt.type = opt.type || '';\r
1060 opt.timeout = opt.timeout || 5000;\r
1061\r
1062 this.$el.text(text).attr('class', opt.type);\r
a6e88a4c 1063 this.$el.slideDown($.proxy(_kiwi.app.view.doLayout, this));\r
696a66f8
D
1064\r
1065 if (opt.timeout) this.doTimeout(opt.timeout);\r
1066 },\r
1067\r
1068 html: function (html, opt) {\r
1069 // Defaults\r
1070 opt = opt || {};\r
1071 opt.type = opt.type || '';\r
1072 opt.timeout = opt.timeout || 5000;\r
1073\r
1074 this.$el.html(text).attr('class', opt.type);\r
eaaf73b0 1075 this.$el.slideDown(_kiwi.app.view.doLayout);\r
696a66f8
D
1076\r
1077 if (opt.timeout) this.doTimeout(opt.timeout);\r
1078 },\r
1079\r
1080 hide: function () {\r
a6e88a4c 1081 this.$el.slideUp($.proxy(_kiwi.app.view.doLayout, this));\r
696a66f8
D
1082 },\r
1083\r
1084 doTimeout: function (length) {\r
1085 if (this.tmr) clearTimeout(this.tmr);\r
1086 var that = this;\r
1087 this.tmr = setTimeout(function () { that.hide(); }, length);\r
1088 }\r
1089});\r
1090\r
1091\r
1092\r
1093\r
eaaf73b0 1094_kiwi.view.ResizeHandler = Backbone.View.extend({\r
696a66f8
D
1095 events: {\r
1096 'mousedown': 'startDrag',\r
1097 'mouseup': 'stopDrag'\r
1098 },\r
1099\r
1100 initialize: function () {\r
1101 this.dragging = false;\r
1102 this.starting_width = {};\r
1103\r
1104 $(window).on('mousemove', $.proxy(this.onDrag, this));\r
1105 },\r
1106\r
1107 startDrag: function (event) {\r
1108 this.dragging = true;\r
1109 },\r
1110\r
1111 stopDrag: function (event) {\r
1112 this.dragging = false;\r
1113 },\r
1114\r
1115 onDrag: function (event) {\r
1116 if (!this.dragging) return;\r
1117\r
1118 this.$el.css('left', event.clientX - (this.$el.outerWidth(true) / 2));\r
1119 $('#memberlists').css('width', this.$el.parent().width() - (this.$el.position().left + this.$el.outerWidth()));\r
eaaf73b0 1120 _kiwi.app.view.doLayout();\r
696a66f8
D
1121 }\r
1122});\r
1123\r
1124\r
1125\r
eaaf73b0 1126_kiwi.view.AppToolbar = Backbone.View.extend({\r
7de3dd03
D
1127 events: {\r
1128 'click .settings': 'clickSettings'\r
1129 },\r
1130\r
1131 initialize: function () {\r
7de3dd03
D
1132 },\r
1133\r
1134 clickSettings: function (event) {\r
eaaf73b0 1135 _kiwi.app.controlbox.processInput('/settings');\r
7de3dd03
D
1136 }\r
1137});\r
1138\r
1139\r
1140\r
eaaf73b0 1141_kiwi.view.Application = Backbone.View.extend({\r
696a66f8 1142 initialize: function () {\r
a6e88a4c
D
1143 var that = this;\r
1144\r
1145 $(window).resize(function() { that.doLayout.apply(that); });\r
1146 $('#toolbar').resize(function() { that.doLayout.apply(that); });\r
1147 $('#controlbox').resize(function() { that.doLayout.apply(that); });\r
696a66f8 1148\r
5bed0536
D
1149 // Change the theme when the config is changed\r
1150 _kiwi.global.settings.on('change:theme', this.updateTheme, this);\r
f55cb404 1151 this.updateTheme(getQueryVariable('theme'));\r
5bed0536 1152\r
039a3156
D
1153 _kiwi.global.settings.on('change:channel_list_style', this.setTabLayout, this);\r
1154 this.setTabLayout(_kiwi.global.settings.get('channel_list_style'));\r
1155\r
2bbb5225
D
1156 _kiwi.global.settings.on('change:show_timestamps', this.displayTimestamps, this);\r
1157 this.displayTimestamps(_kiwi.global.settings.get('show_timestamps'));\r
1158\r
696a66f8
D
1159 this.doLayout();\r
1160\r
1161 $(document).keydown(this.setKeyFocus);\r
1162\r
1163 // Confirmation require to leave the page\r
1164 window.onbeforeunload = function () {\r
eaaf73b0 1165 if (_kiwi.gateway.isConnected()) {\r
696a66f8
D
1166 return 'This will close all KiwiIRC conversations. Are you sure you want to close this window?';\r
1167 }\r
1168 };\r
e5989472
D
1169\r
1170 this.initSound();\r
696a66f8
D
1171 },\r
1172\r
1173\r
5bed0536
D
1174\r
1175 updateTheme: function (theme_name) {\r
1176 // If called by the settings callback, get the correct new_value\r
1177 if (theme_name === _kiwi.global.settings) {\r
1178 theme_name = arguments[1];\r
1179 }\r
1180\r
1181 // If we have no theme specified, get it from the settings\r
1182 if (!theme_name) theme_name = _kiwi.global.settings.get('theme');\r
1183\r
1184 // Clear any current theme\r
1185 this.$el.removeClass(function (i, css) {\r
43ad0845 1186 return (css.match(/\btheme_\S+/g) || []).join(' ');\r
5bed0536
D
1187 });\r
1188\r
1189 // Apply the new theme\r
6d6365e4 1190 this.$el.addClass('theme_' + (theme_name || 'relaxed'));\r
5bed0536
D
1191 },\r
1192\r
1193\r
039a3156
D
1194 setTabLayout: function (layout_style) {\r
1195 // If called by the settings callback, get the correct new_value\r
1196 if (layout_style === _kiwi.global.settings) {\r
1197 layout_style = arguments[1];\r
1198 }\r
1199 \r
1200 if (layout_style == 'list') {\r
1201 this.$el.addClass('chanlist_treeview');\r
1202 } else {\r
1203 this.$el.removeClass('chanlist_treeview');\r
1204 }\r
1205 \r
1206 this.doLayout();\r
1207 },\r
1208\r
1209\r
2bbb5225
D
1210 displayTimestamps: function (show_timestamps) {\r
1211 // If called by the settings callback, get the correct new_value\r
1212 if (show_timestamps === _kiwi.global.settings) {\r
1213 show_timestamps = arguments[1];\r
1214 }\r
50f92d87 1215\r
2bbb5225
D
1216 if (show_timestamps) {\r
1217 this.$el.addClass('timestamps');\r
1218 } else {\r
1219 this.$el.removeClass('timestamps');\r
1220 }\r
1221 },\r
1222\r
1223\r
696a66f8
D
1224 // Globally shift focus to the command input box on a keypress\r
1225 setKeyFocus: function (ev) {\r
1226 // If we're copying text, don't shift focus\r
6c80fe5c 1227 if (ev.ctrlKey || ev.altKey || ev.metaKey) {\r
696a66f8
D
1228 return;\r
1229 }\r
1230\r
1231 // If we're typing into an input box somewhere, ignore\r
22373da6 1232 if ((ev.target.tagName.toLowerCase() === 'input') || (ev.target.tagName.toLowerCase() === 'textarea') || $(ev.target).attr('contenteditable')) {\r
696a66f8
D
1233 return;\r
1234 }\r
1235\r
1236 $('#controlbox .inp').focus();\r
1237 },\r
1238\r
1239\r
1240 doLayout: function () {\r
61542fde
D
1241 var el_kiwi = this.$el;\r
1242 var el_panels = $('#kiwi #panels');\r
1243 var el_memberlists = $('#kiwi #memberlists');\r
1244 var el_toolbar = $('#kiwi #toolbar');\r
1245 var el_controlbox = $('#kiwi #controlbox');\r
1246 var el_resize_handle = $('#kiwi #memberlists_resize_handle');\r
696a66f8
D
1247\r
1248 var css_heights = {\r
1249 top: el_toolbar.outerHeight(true),\r
1250 bottom: el_controlbox.outerHeight(true)\r
1251 };\r
1252\r
cacc0359
D
1253\r
1254 // If any elements are not visible, full size the panals instead\r
1255 if (!el_toolbar.is(':visible')) {\r
1256 css_heights.top = 0;\r
1257 }\r
1258\r
1259 if (!el_controlbox.is(':visible')) {\r
1260 css_heights.bottom = 0;\r
1261 }\r
1262\r
1263 // Apply the CSS sizes\r
696a66f8
D
1264 el_panels.css(css_heights);\r
1265 el_memberlists.css(css_heights);\r
1266 el_resize_handle.css(css_heights);\r
1267\r
3adc7463
D
1268 // If we have channel tabs on the side, adjust the height\r
1269 if (el_kiwi.hasClass('chanlist_treeview')) {\r
a6e88a4c 1270 $('#tabs', el_kiwi).css(css_heights);\r
3adc7463
D
1271 }\r
1272\r
e579ff0d
D
1273 // Determine if we have a narrow window (mobile/tablet/or even small desktop window)\r
1274 if (el_kiwi.outerWidth() < 400) {\r
1275 el_kiwi.addClass('narrow');\r
1276 } else {\r
1277 el_kiwi.removeClass('narrow');\r
1278 }\r
1279\r
cacc0359 1280 // Set the panels width depending on the memberlist visibility\r
696a66f8 1281 if (el_memberlists.css('display') != 'none') {\r
daab5c1b
D
1282 // Panels to the side of the memberlist\r
1283 el_panels.css('right', el_memberlists.outerWidth(true));\r
1284 // The resize handle sits overlapping the panels and memberlist\r
1285 el_resize_handle.css('left', el_memberlists.position().left - (el_resize_handle.outerWidth(true) / 2));\r
696a66f8 1286 } else {\r
daab5c1b
D
1287 // Memberlist is hidden so panels to the right edge\r
1288 el_panels.css('right', 0);\r
1289 // And move the handle just out of sight to the right\r
696a66f8
D
1290 el_resize_handle.css('left', el_panels.outerWidth(true));\r
1291 }\r
fb989cb2
D
1292\r
1293 var input_wrap_width = parseInt($('#kiwi #controlbox .input_tools').outerWidth());\r
1294 el_controlbox.find('.input_wrap').css('right', input_wrap_width + 7);\r
696a66f8
D
1295 },\r
1296\r
1297\r
1298 alertWindow: function (title) {\r
1299 if (!this.alertWindowTimer) {\r
1300 this.alertWindowTimer = new (function () {\r
1301 var that = this;\r
1302 var tmr;\r
1303 var has_focus = true;\r
1304 var state = 0;\r
1305 var default_title = 'Kiwi IRC';\r
1306 var title = 'Kiwi IRC';\r
1307\r
1308 this.setTitle = function (new_title) {\r
1309 new_title = new_title || default_title;\r
1310 window.document.title = new_title;\r
1311 return new_title;\r
1312 };\r
1313\r
1314 this.start = function (new_title) {\r
1315 // Don't alert if we already have focus\r
1316 if (has_focus) return;\r
1317\r
1318 title = new_title;\r
1319 if (tmr) return;\r
1320 tmr = setInterval(this.update, 1000);\r
1321 };\r
1322\r
1323 this.stop = function () {\r
1324 // Stop the timer and clear the title\r
1325 if (tmr) clearInterval(tmr);\r
1326 tmr = null;\r
1327 this.setTitle();\r
1328\r
1329 // Some browsers don't always update the last title correctly\r
1330 // Wait a few seconds and then reset\r
1331 setTimeout(this.reset, 2000);\r
1332 };\r
1333\r
1334 this.reset = function () {\r
1335 if (tmr) return;\r
1336 that.setTitle();\r
1337 };\r
1338\r
1339\r
1340 this.update = function () {\r
1341 if (state === 0) {\r
1342 that.setTitle(title);\r
1343 state = 1;\r
1344 } else {\r
1345 that.setTitle();\r
1346 state = 0;\r
1347 }\r
1348 };\r
1349\r
1350 $(window).focus(function (event) {\r
1351 has_focus = true;\r
1352 that.stop();\r
1353\r
1354 // Some browsers don't always update the last title correctly\r
1355 // Wait a few seconds and then reset\r
1356 setTimeout(that.reset, 2000);\r
1357 });\r
1358\r
1359 $(window).blur(function (event) {\r
1360 has_focus = false;\r
1361 });\r
1362 })();\r
1363 }\r
1364\r
1365 this.alertWindowTimer.start(title);\r
1366 },\r
1367\r
1368\r
1369 barsHide: function (instant) {\r
1370 var that = this;\r
1371\r
1372 if (!instant) {\r
a6e88a4c
D
1373 $('#toolbar').slideUp({queue: false, duration: 400, step: $.proxy(this.doLayout, this)});\r
1374 $('#controlbox').slideUp({queue: false, duration: 400, step: $.proxy(this.doLayout, this)});\r
696a66f8
D
1375 } else {\r
1376 $('#toolbar').slideUp(0);\r
1377 $('#controlbox').slideUp(0);\r
1378 this.doLayout();\r
1379 }\r
1380 },\r
1381\r
1382 barsShow: function (instant) {\r
1383 var that = this;\r
1384\r
1385 if (!instant) {\r
a6e88a4c
D
1386 $('#toolbar').slideDown({queue: false, duration: 400, step: $.proxy(this.doLayout, this)});\r
1387 $('#controlbox').slideDown({queue: false, duration: 400, step: $.proxy(this.doLayout, this)});\r
696a66f8
D
1388 } else {\r
1389 $('#toolbar').slideDown(0);\r
1390 $('#controlbox').slideDown(0);\r
1391 this.doLayout();\r
1392 }\r
e5989472
D
1393 },\r
1394\r
1395\r
1396 initSound: function () {\r
1397 var that = this,\r
1398 base_path = this.model.get('base_path');\r
1399\r
1400 $script(base_path + '/assets/libs/soundmanager2/soundmanager2-nodebug-jsmin.js', function() {\r
1401 if (typeof soundManager === 'undefined')\r
1402 return;\r
1403\r
1404 soundManager.setup({\r
1405 url: base_path + '/assets/libs/soundmanager2/',\r
1406 flashVersion: 9, // optional: shiny features (default = 8)// optional: ignore Flash where possible, use 100% HTML5 mode\r
1407 preferFlash: true,\r
1408\r
1409 onready: function() {\r
1410 that.sound_object = soundManager.createSound({\r
1411 id: 'highlight',\r
1412 url: base_path + '/assets/sound/highlight.mp3'\r
1413 });\r
1414 }\r
1415 });\r
1416 });\r
1417 },\r
1418\r
1419\r
1420 playSound: function (sound_id) {\r
1421 if (!this.sound_object) return;\r
b2f25f45
D
1422\r
1423 if (_kiwi.global.settings.get('mute_sounds'))\r
1424 return;\r
1425 \r
e5989472 1426 soundManager.play(sound_id);\r
696a66f8 1427 }\r
04d615ad
D
1428});\r
1429\r
1430\r
1431\r
1432\r
1433\r
1434\r
1435\r
1436\r
1437\r
1438_kiwi.view.MediaMessage = Backbone.View.extend({\r
1439 events: {\r
1440 'click .media_close': 'close'\r
1441 },\r
1442\r
1443 initialize: function () {\r
b025bb57 1444 // Get the URL from the data\r
04d615ad
D
1445 this.url = this.$el.data('url');\r
1446 },\r
1447\r
b025bb57 1448 // Close the media content and remove it from display\r
04d615ad 1449 close: function () {\r
b025bb57
D
1450 var that = this;\r
1451 this.$content.slideUp('fast', function () {\r
1452 that.$content.remove();\r
1453 });\r
04d615ad
D
1454 },\r
1455\r
b025bb57 1456 // Open the media content within its wrapper\r
04d615ad 1457 open: function () {\r
b025bb57
D
1458 // Create the content div if we haven't already\r
1459 if (!this.$content) {\r
f443e240 1460 this.$content = $('<div class="media_content"><a class="media_close"><i class="icon-chevron-up"></i> Close media</a><br /><div class="content"></div></div>');\r
f443e240 1461 this.$content.find('.content').append(this.mediaTypes[this.$el.data('type')].apply(this, []) || 'Not found :(');\r
b025bb57 1462 }\r
04d615ad 1463\r
b025bb57
D
1464 // Now show the content if not already\r
1465 if (!this.$content.is(':visible')) {\r
1466 // Hide it first so the slideDown always plays\r
1467 this.$content.hide();\r
1468\r
1469 // Add the media content and slide it into view\r
1470 this.$el.append(this.$content);\r
1471 this.$content.slideDown();\r
1472 }\r
f443e240
D
1473 },\r
1474\r
1475\r
1476\r
1477 // Generate the media content for each recognised type\r
1478 mediaTypes: {\r
1479 twitter: function () {\r
1480 var tweet_id = this.$el.data('tweetid');\r
1481 var that = this;\r
1482\r
1483 $.getJSON('https://api.twitter.com/1/statuses/oembed.json?id=' + tweet_id + '&callback=?', function (data) {\r
b9149cd0 1484 that.$content.find('.content').html(data.html);\r
f443e240
D
1485 });\r
1486\r
1487 return $('<div>Loading tweet..</div>');\r
1488 },\r
1489\r
1490\r
1491 image: function () {\r
1492 return $('<a href="' + this.url + '" target="_blank"><img height="100" src="' + this.url + '" /></a>');\r
b9149cd0
D
1493 },\r
1494\r
1495\r
1496 reddit: function () {\r
1497 var that = this;\r
1498 var matches = (/reddit\.com\/r\/([a-zA-Z0-9_\-]+)\/comments\/([a-z0-9]+)\/([^\/]+)?/gi).exec(this.url);\r
1499\r
1500 $.getJSON('http://www.' + matches[0] + '.json?jsonp=?', function (data) {\r
1501 console.log('Loaded reddit data', data);\r
1502 var post = data[0].data.children[0].data;\r
1503 var thumb = '';\r
1504\r
1505 // Show a thumbnail if there is one\r
1506 if (post.thumbnail) {\r
1507 //post.thumbnail = 'http://www.eurotunnel.com/uploadedImages/commercial/back-steps-icon-arrow.png';\r
1508\r
1509 // Hide the thumbnail if an over_18 image\r
1510 if (post.over_18) {\r
1511 thumb = '<span class="thumbnail_nsfw" onclick="$(this).find(\'p\').remove(); $(this).find(\'img\').css(\'visibility\', \'visible\');">';\r
1512 thumb += '<p style="font-size:0.9em;line-height:1.2em;cursor:pointer;">Show<br />NSFW</p>';\r
1513 thumb += '<img src="' + post.thumbnail + '" class="thumbnail" style="visibility:hidden;" />';\r
1514 thumb += '</span>';\r
1515 } else {\r
1516 thumb = '<img src="' + post.thumbnail + '" class="thumbnail" />';\r
1517 }\r
1518 }\r
1519\r
1520 // Build the template string up\r
1521 var tmpl = '<div>' + thumb + '<b><%- title %></b><br />Posted by <%- author %>. &nbsp;&nbsp; ';\r
1522 tmpl += '<i class="icon-arrow-up"></i> <%- ups %> &nbsp;&nbsp; <i class="icon-arrow-down"></i> <%- downs %><br />';\r
1523 tmpl += '<%- num_comments %> comments made. <a href="http://www.reddit.com<%- permalink %>">View post</a></div>';\r
1524\r
1525 that.$content.find('.content').html(_.template(tmpl, post));\r
1526 });\r
1527\r
1528 return $('<div>Loading Reddit thread..</div>');\r
f443e240 1529 }\r
04d615ad
D
1530 }\r
1531\r
1532}, {\r
1533\r
b025bb57 1534 // Build the closed media HTML from a URL\r
04d615ad 1535 buildHtml: function (url) {\r
f443e240 1536 var html = '', matches;\r
04d615ad
D
1537\r
1538 // Is it an image?\r
1539 if (url.match(/(\.jpe?g|\.gif|\.bmp|\.png)\??$/i)) {\r
f443e240
D
1540 html += '<span class="media image" data-type="image" data-url="' + url + '" title="Open Image"><a class="open"><i class="icon-chevron-right"></i></a></span>';\r
1541 }\r
1542\r
1543 // Is it a tweet?\r
1544 matches = (/https?:\/\/twitter.com\/([a-zA-Z0-9_]+)\/status\/([0-9]+)/ig).exec(url);\r
1545 if (matches) {\r
1546 html += '<span class="media twitter" data-type="twitter" data-url="' + url + '" data-tweetid="' + matches[2] + '" title="Show tweet information"><a class="open"><i class="icon-chevron-right"></i></a></span>';\r
04d615ad
D
1547 }\r
1548\r
b9149cd0
D
1549 // Is reddit?\r
1550 matches = (/reddit\.com\/r\/([a-zA-Z0-9_\-]+)\/comments\/([a-z0-9]+)\/([^\/]+)?/gi).exec(url);\r
1551 if (matches) {\r
1552 html += '<span class="media reddit" data-type="reddit" data-url="' + url + '" title="Reddit thread"><a class="open"><i class="icon-chevron-right"></i></a></span>';\r
1553 }\r
1554\r
04d615ad
D
1555 return html;\r
1556 }\r
e97e4eb4 1557});\r