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