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