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