making it librejs compliant
[KiwiIRC.git] / client / src / views / channel.js
CommitLineData
50ac472f 1_kiwi.view.Channel = _kiwi.view.Panel.extend({
9b807765 2 events: function(){
c794b877 3 var parent_events = this.constructor.__super__.events;
3499d625 4
9b807765
D
5 if(_.isFunction(parent_events)){
6 parent_events = parent_events();
7 }
8 return _.extend({}, parent_events, {
3499d625 9 'click .msg .nick' : 'nickClick',
88528078 10 'click .msg .inline-nick' : 'nickClick',
3499d625
D
11 "click .chan": "chanClick",
12 'click .media .open': 'mediaClick',
13 'mouseenter .msg .nick': 'msgEnter',
14 'mouseleave .msg .nick': 'msgLeave'
9b807765 15 });
dfb5209c
JA
16 },
17
50ac472f
D
18 initialize: function (options) {
19 this.initializePanel(options);
c794b877
D
20
21 // Container for all the messages
22 this.$messages = $('<div class="messages"></div>');
23 this.$el.append(this.$messages);
24
50ac472f 25 this.model.bind('change:topic', this.topic, this);
3aa7b8cc 26 this.model.bind('change:topic_set_by', this.topicSetBy, this);
50ac472f 27
7d2a2771 28 if (this.model.get('members')) {
5a0eb997 29 // When we join the memberlist, we have officially joined the channel
7d2a2771
D
30 this.model.get('members').bind('add', function (member) {
31 if (member.get('nick') === this.model.collection.network.get('nick')) {
32 this.$el.find('.initial_loader').slideUp(function () {
33 $(this).remove();
34 });
35 }
36 }, this);
5a0eb997
D
37
38 // Memberlist reset with a new nicklist? Consider we have joined
39 this.model.get('members').bind('reset', function(members) {
40 if (members.getByNick(this.model.collection.network.get('nick'))) {
41 this.$el.find('.initial_loader').slideUp(function () {
42 $(this).remove();
43 });
44 }
45 }, this);
7d2a2771 46 }
660e1427 47
50ac472f
D
48 // Only show the loader if this is a channel (ie. not a query)
49 if (this.model.isChannel()) {
247dd7ac 50 this.$el.append('<div class="initial_loader" style="margin:1em;text-align:center;"> ' + _kiwi.global.i18n.translate('client_views_channel_joining').fetch() + ' <span class="loader"></span></div>');
50ac472f 51 }
c794b877
D
52
53 this.model.bind('msg', this.newMsg, this);
54 this.msg_count = 0;
50ac472f
D
55 },
56
c794b877 57
41a9c836
D
58 render: function () {
59 var that = this;
60
61 this.$messages.empty();
62 _.each(this.model.get('scrollback'), function (msg) {
63 that.newMsg(msg);
64 });
65 },
66
67
72a325ec 68 newMsg: function(msg) {
c794b877 69
72a325ec
D
70 // Parse the msg object into properties fit for displaying
71 msg = this.generateMessageDisplayObj(msg);
c794b877 72
c1c51f22 73 _kiwi.global.events.emit('message:display', {panel: this.model, message: msg})
060391b1 74 .then(_.bind(function() {
72a325ec
D
75 var line_msg;
76
e8885df9
D
77 // Format the nick to the config defined format
78 var display_obj = _.clone(msg);
79 display_obj.nick = styleText('message_nick', {nick: msg.nick, prefix: msg.nick_prefix || ''});
80
72a325ec 81 line_msg = '<div class="msg <%= type %> <%= css_classes %>"><div class="time"><%- time_string %></div><div class="nick" style="<%= nick_style %>"><%- nick %></div><div class="text" style="<%= style %>"><%= msg %> </div></div>';
e8885df9 82 this.$messages.append($(_.template(line_msg, display_obj)).data('message', msg));
c794b877 83
c1c51f22
D
84 // Activity/alerts based on the type of new message
85 if (msg.type.match(/^action /)) {
86 this.alert('action');
c794b877 87
72a325ec 88 } else if (msg.is_highlight) {
c794b877 89 _kiwi.app.view.alertWindow('* ' + _kiwi.global.i18n.translate('client_views_panel_activity').fetch());
c1c51f22
D
90 _kiwi.app.view.favicon.newHighlight();
91 _kiwi.app.view.playSound('highlight');
ddf30757 92 _kiwi.app.view.showNotification(this.model.get('name'), msg.unparsed_msg);
c1c51f22
D
93 this.alert('highlight');
94
95 } else {
96 // If this is the active panel, send an alert out
97 if (this.model.isActive()) {
98 _kiwi.app.view.alertWindow('* ' + _kiwi.global.i18n.translate('client_views_panel_activity').fetch());
99 }
100 this.alert('activity');
c794b877 101 }
c794b877 102
c1c51f22
D
103 if (this.model.isQuery() && !this.model.isActive()) {
104 _kiwi.app.view.alertWindow('* ' + _kiwi.global.i18n.translate('client_views_panel_activity').fetch());
ee2f0962 105
c1c51f22 106 // Highlights have already been dealt with above
72a325ec 107 if (!msg.is_highlight) {
c1c51f22
D
108 _kiwi.app.view.favicon.newHighlight();
109 }
ee2f0962 110
ddf30757 111 _kiwi.app.view.showNotification(this.model.get('name'), msg.unparsed_msg);
c1c51f22
D
112 _kiwi.app.view.playSound('highlight');
113 }
c794b877 114
c1c51f22
D
115 // Update the activity counters
116 (function () {
117 // Only inrement the counters if we're not the active panel
118 if (this.model.isActive()) return;
c794b877 119
af03387c 120 var count_all_activity = _kiwi.global.settings.get('count_all_activity'),
31fe7a44 121 exclude_message_types, new_count;
7ba064d9 122
c1c51f22
D
123 // Set the default config value
124 if (typeof count_all_activity === 'undefined') {
125 count_all_activity = false;
126 }
223d53e5 127
c1c51f22
D
128 // Do not increment the counter for these message types
129 exclude_message_types = [
130 'action join',
131 'action quit',
132 'action part',
133 'action kick',
134 'action nick',
135 'action mode'
136 ];
137
138 if (count_all_activity || _.indexOf(exclude_message_types, msg.type) === -1) {
af03387c 139 new_count = this.model.get('activity_counter') || 0;
31fe7a44 140 new_count++;
af03387c 141 this.model.set('activity_counter', new_count);
c1c51f22 142 }
223d53e5 143
c1c51f22 144 }).apply(this);
c794b877 145
c1c51f22 146 if(this.model.isActive()) this.scrollToBottom();
c794b877 147
c1c51f22
D
148 // Make sure our DOM isn't getting too large (Acts as scrollback)
149 this.msg_count++;
150 if (this.msg_count > (parseInt(_kiwi.global.settings.get('scrollback'), 10) || 250)) {
151 $('.msg:first', this.$messages).remove();
152 this.msg_count--;
153 }
154 }, this));
50ac472f
D
155 },
156
c794b877 157
88528078 158 // Let nicks be clickable + colourise within messages
a32b3a30 159 parseMessageNicks: function(word, colourise) {
99499ace 160 var members, member, style = '';
88528078
D
161
162 members = this.model.get('members');
163 if (!members) {
164 return;
165 }
166
167 member = members.getByNick(word);
168 if (!member) {
169 return;
170 }
171
a32b3a30 172 if (colourise !== false) {
99499ace
D
173 // Use the nick from the member object so the style matches the letter casing
174 style = this.getNickStyles(member.get('nick')).asCssString();
a32b3a30 175 }
88528078 176
99499ace 177 return _.template('<span class="inline-nick" style="<%- style %>;cursor:pointer;" data-nick="<%- nick %>"><%- nick %></span>', {
88528078 178 nick: word,
99499ace 179 style: style
88528078
D
180 });
181
182 },
183
184
72a325ec
D
185 // Make channels clickable
186 parseMessageChannels: function(word) {
187 var re,
188 parsed = false,
189 network = this.model.get('network');
190
191 if (!network) {
192 return;
193 }
194
195 re = new RegExp('(^|\\s)([' + escapeRegex(network.get('channel_prefix')) + '][^ ,\\007]+)', 'g');
196
197 if (!word.match(re)) {
198 return parsed;
199 }
200
201 parsed = word.replace(re, function (m1, m2) {
202 return m2 + '<a class="chan" data-channel="' + _.escape(m1.trim()) + '">' + _.escape(m1.trim()) + '</a>';
203 });
204
205 return parsed;
206 },
207
208
209 parseMessageUrls: function(word) {
210 var found_a_url = false,
211 parsed_url;
212
8768fa0c 213 parsed_url = word.replace(/^(([A-Za-z][A-Za-z0-9\-]*\:\/\/)|(www\.))([\w.\-]+)([a-zA-Z]{2,6})(:[0-9]+)?(\/[\w!:.?$'()[\]*,;~+=&%@!\-\/]*)?(#.*)?$/gi, function (url) {
72a325ec
D
214 var nice = url,
215 extra_html = '';
216
217 // Don't allow javascript execution
218 if (url.match(/^javascript:/)) {
219 return url;
220 }
221
222 found_a_url = true;
223
224 // Add the http if no protoocol was found
225 if (url.match(/^www\./)) {
226 url = 'http://' + url;
227 }
228
229 // Shorten the displayed URL if it's going to be too long
230 if (nice.length > 100) {
231 nice = nice.substr(0, 100) + '...';
232 }
233
234 // Get any media HTML if supported
235 extra_html = _kiwi.view.MediaMessage.buildHtml(url);
236
237 // Make the link clickable
8768fa0c 238 return '<a class="link_ext" target="_blank" rel="nofollow" href="' + url.replace(/"/g, '%22') + '">' + _.escape(nice) + '</a>' + extra_html;
72a325ec
D
239 });
240
241 return found_a_url ? parsed_url : false;
242 },
243
244
99499ace
D
245 // Sgnerate a css style for a nick
246 getNickStyles: function(nick) {
ef641a62 247 var ret, colour, nick_int = 0, rgb, nick_lightness;
72a325ec 248
99499ace 249 // Get a colour from a nick (Method based on IRSSIs nickcolor.pl)
72a325ec 250 _.map(nick.split(''), function (i) { nick_int += i.charCodeAt(0); });
3144c066 251
ef641a62 252 nick_lightness = (_.find(_kiwi.app.themes, function (theme) {
3144c066 253 return theme.name.toLowerCase() === _kiwi.global.settings.get('theme').toLowerCase();
ef641a62 254 }) || {}).nick_lightness;
3144c066 255
ef641a62
NF
256 if (typeof nick_lightness !== 'number') {
257 nick_lightness = 35;
258 } else {
259 nick_lightness = Math.max(0, Math.min(100, nick_lightness));
260 }
261
262 rgb = hsl2rgb(nick_int % 255, 70, nick_lightness);
72a325ec 263 rgb = rgb[2] | (rgb[1] << 8) | (rgb[0] << 16);
99499ace 264 colour = '#' + rgb.toString(16);
72a325ec 265
99499ace
D
266 ret = {color: colour};
267 ret.asCssString = function() {
268 return _.reduce(this, function(result, item, key){
269 return result + key + ':' + item + ';';
270 }, '');
271 };
272
273 return ret;
72a325ec
D
274 },
275
276
277 // Takes an IRC message object and parses it for displaying
278 generateMessageDisplayObj: function(msg) {
279 var nick_hex, time_difference,
280 message_words,
281 sb = this.model.get('scrollback'),
282 prev_msg = sb[sb.length-2],
283 hour, pm, am_pm_locale_key;
284
285 // Clone the msg object so we dont modify the original
286 msg = _.clone(msg);
287
288 // Defaults
289 msg.css_classes = '';
290 msg.nick_style = '';
291 msg.is_highlight = false;
292 msg.time_string = '';
293
294
295 // Nick highlight detecting
296 var nick = _kiwi.app.connections.active_connection.get('nick');
297 if ((new RegExp('(^|\\W)(' + escapeRegex(nick) + ')(\\W|$)', 'i')).test(msg.msg)) {
298 // Do not highlight the user's own input
299 if (msg.nick.localeCompare(nick) !== 0) {
300 msg.is_highlight = true;
301 msg.css_classes += ' highlight';
302 }
303 }
304
305 message_words = msg.msg.split(' ');
306 message_words = _.map(message_words, function(word) {
307 var parsed_word;
308
309 parsed_word = this.parseMessageUrls(word);
310 if (typeof parsed_word === 'string') return parsed_word;
311
312 parsed_word = this.parseMessageChannels(word);
313 if (typeof parsed_word === 'string') return parsed_word;
314
a32b3a30 315 parsed_word = this.parseMessageNicks(word, (msg.type === 'privmsg'));
88528078
D
316 if (typeof parsed_word === 'string') return parsed_word;
317
72a325ec
D
318 parsed_word = _.escape(word);
319
320 // Replace text emoticons with images
321 if (_kiwi.global.settings.get('show_emoticons')) {
322 parsed_word = emoticonFromText(parsed_word);
323 }
324
325 return parsed_word;
326 }, this);
327
ddf30757 328 msg.unparsed_msg = msg.msg;
72a325ec
D
329 msg.msg = message_words.join(' ');
330
331 // Convert IRC formatting into HTML formatting
332 msg.msg = formatIRCMsg(msg.msg);
333
99499ace
D
334 // Add some style to the nick
335 msg.nick_style = this.getNickStyles(msg.nick).asCssString();
72a325ec
D
336
337 // Generate a hex string from the nick to be used as a CSS class name
338 nick_hex = '';
339 if (msg.nick) {
340 _.map(msg.nick.split(''), function (char) {
341 nick_hex += char.charCodeAt(0).toString(16);
342 });
343 msg.css_classes += ' nick_' + nick_hex;
344 }
345
346 if (prev_msg) {
347 // Time difference between this message and the last (in minutes)
348 time_difference = (msg.time.getTime() - prev_msg.time.getTime())/1000/60;
349 if (prev_msg.nick === msg.nick && time_difference < 1) {
350 msg.css_classes += ' repeated_nick';
351 }
352 }
353
354 // Build up and add the line
355 if (_kiwi.global.settings.get('use_24_hour_timestamps')) {
356 msg.time_string = msg.time.getHours().toString().lpad(2, "0") + ":" + msg.time.getMinutes().toString().lpad(2, "0") + ":" + msg.time.getSeconds().toString().lpad(2, "0");
357 } else {
358 hour = msg.time.getHours();
359 pm = hour > 11;
360
361 hour = hour % 12;
362 if (hour === 0)
363 hour = 12;
364
365 am_pm_locale_key = pm ?
366 'client_views_panel_timestamp_pm' :
367 'client_views_panel_timestamp_am';
368
369 msg.time_string = translateText(am_pm_locale_key, hour + ":" + msg.time.getMinutes().toString().lpad(2, "0") + ":" + msg.time.getSeconds().toString().lpad(2, "0"));
370 }
371
372 return msg;
373 },
374
375
50ac472f
D
376 topic: function (topic) {
377 if (typeof topic !== 'string' || !topic) {
378 topic = this.model.get("topic");
379 }
380
9a10cede 381 this.model.addMsg('', styleText('channel_topic', {text: topic, channel: this.model.get('name')}), 'topic');
50ac472f
D
382
383 // If this is the active channel then update the topic bar
3aa7b8cc
D
384 if (_kiwi.app.panels().active === this.model) {
385 _kiwi.app.topicbar.setCurrentTopicFromChannel(this.model);
386 }
387 },
388
389 topicSetBy: function (topic) {
390 // If this is the active channel then update the topic bar
391 if (_kiwi.app.panels().active === this.model) {
392 _kiwi.app.topicbar.setCurrentTopicFromChannel(this.model);
50ac472f 393 }
dfb5209c
JA
394 },
395
396 // Click on a nickname
397 nickClick: function (event) {
b43f8c8a
D
398 var $target = $(event.currentTarget),
399 nick,
dfb5209c 400 members = this.model.get('members'),
b43f8c8a 401 member;
dfb5209c 402
88528078
D
403 event.stopPropagation();
404
405 // Check this current element for a nick before resorting to the main message
406 // (eg. inline nicks has the nick on its own element within the message)
b43f8c8a 407 nick = $target.data('nick');
88528078 408 if (!nick) {
b43f8c8a 409 nick = $target.parent('.msg').data('message').nick;
88528078
D
410 }
411
b43f8c8a
D
412 // Make sure this nick is still in the channel
413 member = members ? members.getByNick(nick) : null;
414 if (!member) {
415 return;
dfb5209c 416 }
b43f8c8a
D
417
418 _kiwi.global.events.emit('nick:select', {target: $target, member: member, source: 'message'})
419 .then(_.bind(this.openUserMenuForNick, this, $target, member));
420 },
421
422
63b21ebe 423 updateLastSeenMarker: function() {
8859068c 424 if (this.model.isActive()) {
63b21ebe 425 // Remove the previous last seen classes
8859068c 426 this.$(".last_seen").removeClass("last_seen");
63b21ebe
EH
427
428 // Mark the last message the user saw
8859068c 429 this.$messages.children().last().addClass("last_seen");
63b21ebe 430 }
b43f8c8a
D
431 },
432
433
434 openUserMenuForNick: function ($target, member) {
435 var members = this.model.get('members'),
436 are_we_an_op = !!members.getByNick(_kiwi.app.connections.active_connection.get('nick')).get('is_op'),
437 userbox, menubox;
438
439 userbox = new _kiwi.view.UserBox();
440 userbox.setTargets(member, this.model);
441 userbox.displayOpItems(are_we_an_op);
442
443 menubox = new _kiwi.view.MenuBox(member.get('nick') || 'User');
444 menubox.addItem('userbox', userbox.$el);
445 menubox.showFooter(false);
446
8dfd6407 447 _kiwi.global.events.emit('usermenu:created', {menu: menubox, userbox: userbox, user: member})
b43f8c8a
D
448 .then(_.bind(function() {
449 menubox.show();
450
451 // Position the userbox + menubox
452 var target_offset = $target.offset(),
453 t = target_offset.top,
454 m_bottom = t + menubox.$el.outerHeight(), // Where the bottom of menu will be
455 memberlist_bottom = this.$el.parent().offset().top + this.$el.parent().outerHeight();
456
457 // If the bottom of the userbox is going to be too low.. raise it
458 if (m_bottom > memberlist_bottom){
459 t = memberlist_bottom - menubox.$el.outerHeight();
460 }
461
462 // Set the new positon
463 menubox.$el.offset({
464 left: target_offset.left,
465 top: t
466 });
467 }, this))
468 .catch(_.bind(function() {
469 userbox = null;
470
471 menu.dispose();
472 menu = null;
473 }, this));
3499d625
D
474 },
475
476
477 chanClick: function (event) {
425efe7a
JA
478 var target = (event.target) ? $(event.target).data('channel') : $(event.srcElement).data('channel');
479
480 _kiwi.app.connections.active_connection.gateway.join(target);
3499d625
D
481 },
482
483
484 mediaClick: function (event) {
485 var $media = $(event.target).parents('.media');
486 var media_message;
487
488 if ($media.data('media')) {
489 media_message = $media.data('media');
490 } else {
491 media_message = new _kiwi.view.MediaMessage({el: $media[0]});
492
493 // Cache this MediaMessage instance for when it's opened again
494 $media.data('media', media_message);
495 }
496
497 media_message.toggle();
498 },
499
500
501 // Cursor hovers over a message
502 msgEnter: function (event) {
503 var nick_class;
504
505 // Find a valid class that this element has
506 _.each($(event.currentTarget).parent('.msg').attr('class').split(' '), function (css_class) {
507 if (css_class.match(/^nick_[a-z0-9]+/i)) {
508 nick_class = css_class;
509 }
510 });
511
512 // If no class was found..
513 if (!nick_class) return;
514
515 $('.'+nick_class).addClass('global_nick_highlight');
516 },
517
518
519 // Cursor leaves message
520 msgLeave: function (event) {
521 var nick_class;
522
523 // Find a valid class that this element has
524 _.each($(event.currentTarget).parent('.msg').attr('class').split(' '), function (css_class) {
525 if (css_class.match(/^nick_[a-z0-9]+/i)) {
526 nick_class = css_class;
527 }
528 });
529
530 // If no class was found..
531 if (!nick_class) return;
532
533 $('.'+nick_class).removeClass('global_nick_highlight');
3aa7b8cc 534 }
dfb5209c 535});