14630ab8df17f581899df63caa21286687a90d0e
1 _kiwi
.view
.Channel
= _kiwi
.view
.Panel
.extend({
3 var parent_events
= this.constructor.__super__
.events
;
5 if(_
.isFunction(parent_events
)){
6 parent_events
= parent_events();
8 return _
.extend({}, parent_events
, {
9 'click .msg .nick' : 'nickClick',
10 'click .msg .inline-nick' : 'nickClick',
11 "click .chan": "chanClick",
12 'click .media .open': 'mediaClick',
13 'mouseenter .msg .nick': 'msgEnter',
14 'mouseleave .msg .nick': 'msgLeave'
18 initialize: function (options
) {
19 this.initializePanel(options
);
21 // Container for all the messages
22 this.$messages
= $('<div class="messages"></div>');
23 this.$el
.append(this.$messages
);
25 this.model
.bind('change:topic', this.topic
, this);
26 this.model
.bind('change:topic_set_by', this.topicSetBy
, this);
28 if (this.model
.get('members')) {
29 // When we join the memberlist, we have officially joined the channel
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 () {
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 () {
48 // Only show the loader if this is a channel (ie. not a query)
49 if (this.model
.isChannel()) {
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>');
53 this.model
.bind('msg', this.newMsg
, this);
61 this.$messages
.empty();
62 _
.each(this.model
.get('scrollback'), function (msg
) {
68 newMsg: function(msg
) {
70 // Parse the msg object into properties fit for displaying
71 msg
= this.generateMessageDisplayObj(msg
);
73 _kiwi
.global
.events
.emit('message:display', {panel
: this.model
, message
: msg
})
74 .then(_
.bind(function() {
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
|| ''});
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>';
82 this.$messages
.append($(_
.template(line_msg
, display_obj
)).data('message', msg
));
84 this.updateLastSeenMarker();
86 // Activity/alerts based on the type of new message
87 if (msg
.type
.match(/^action /)) {
90 } else if (msg
.is_highlight
) {
91 _kiwi
.app
.view
.alertWindow('* ' + _kiwi
.global
.i18n
.translate('client_views_panel_activity').fetch());
92 _kiwi
.app
.view
.favicon
.newHighlight();
93 _kiwi
.app
.view
.playSound('highlight');
94 _kiwi
.app
.view
.showNotification(this.model
.get('name'), msg
.unparsed_msg
);
95 this.alert('highlight');
98 // If this is the active panel, send an alert out
99 if (this.model
.isActive()) {
100 _kiwi
.app
.view
.alertWindow('* ' + _kiwi
.global
.i18n
.translate('client_views_panel_activity').fetch());
102 this.alert('activity');
105 if (this.model
.isQuery() && !this.model
.isActive()) {
106 _kiwi
.app
.view
.alertWindow('* ' + _kiwi
.global
.i18n
.translate('client_views_panel_activity').fetch());
108 // Highlights have already been dealt with above
109 if (!msg
.is_highlight
) {
110 _kiwi
.app
.view
.favicon
.newHighlight();
113 _kiwi
.app
.view
.showNotification(this.model
.get('name'), msg
.unparsed_msg
);
114 _kiwi
.app
.view
.playSound('highlight');
117 // Update the activity counters
119 // Only inrement the counters if we're not the active panel
120 if (this.model
.isActive()) return;
122 var count_all_activity
= _kiwi
.global
.settings
.get('count_all_activity'),
123 exclude_message_types
, new_count
;
125 // Set the default config value
126 if (typeof count_all_activity
=== 'undefined') {
127 count_all_activity
= false;
130 // Do not increment the counter for these message types
131 exclude_message_types
= [
140 if (count_all_activity
|| _
.indexOf(exclude_message_types
, msg
.type
) === -1) {
141 new_count
= this.model
.get('activity_counter') || 0;
143 this.model
.set('activity_counter', new_count
);
148 if(this.model
.isActive()) this.scrollToBottom();
150 // Make sure our DOM isn't getting too large (Acts as scrollback)
152 if (this.msg_count
> (parseInt(_kiwi
.global
.settings
.get('scrollback'), 10) || 250)) {
153 $('.msg:first', this.$messages
).remove();
160 // Let nicks be clickable + colourise within messages
161 parseMessageNicks: function(word
, colourise
) {
162 var members
, member
, colour
= '';
164 members
= this.model
.get('members');
169 member
= members
.getByNick(word
);
174 if (colourise
!== false) {
175 // Use the nick from the member object so the colour matches the letter casing
176 colour
= this.getNickColour(member
.get('nick'));
177 colour
= 'color:' + colour
;
180 return _
.template('<span class="inline-nick" style="<%- colour %>;cursor:pointer;" data-nick="<%- nick %>"><%- nick %></span>', {
188 // Make channels clickable
189 parseMessageChannels: function(word
) {
192 network
= this.model
.get('network');
198 re
= new RegExp('(^|\\s)([' + escapeRegex(network
.get('channel_prefix')) + '][^ ,\\007]+)', 'g');
200 if (!word
.match(re
)) {
204 parsed
= word
.replace(re
, function (m1
, m2
) {
205 return m2
+ '<a class="chan" data-channel="' + _
.escape(m1
.trim()) + '">' + _
.escape(m1
.trim()) + '</a>';
212 parseMessageUrls: function(word
) {
213 var found_a_url
= false,
216 parsed_url
= word
.replace(/^(([A-Za-z][A-Za-z0-9\-]*\:\/\/)|(www\.))([\w.\-]+)([a-zA-Z]{2,6})(:[0-9]+)?(\/[\w!:.?$'()[\]*,;~+=&%@!\-\/]*)?(#.*)?$/gi, function (url
) {
220 // Don't allow javascript execution
221 if (url
.match(/^javascript:/)) {
227 // Add the http if no protoocol was found
228 if (url
.match(/^www\./)) {
229 url
= 'http://' + url
;
232 // Shorten the displayed URL if it's going to be too long
233 if (nice
.length
> 100) {
234 nice
= nice
.substr(0, 100) + '...';
237 // Get any media HTML if supported
238 extra_html
= _kiwi
.view
.MediaMessage
.buildHtml(url
);
240 // Make the link clickable
241 return '<a class="link_ext" target="_blank" rel="nofollow" href="' + url
.replace(/"/g, '%22') + '">' + _.escape(nice) + '</a>' + extra_html;
244 return found_a_url ? parsed_url : false;
248 // Get a colour from a nick (Method based on IRSSIs nickcolor.pl)
249 getNickColour: function(nick) {
250 var nick_int = 0, rgb;
252 _.map(nick.split(''), function (i) { nick_int += i.charCodeAt(0); });
253 rgb = hsl2rgb(nick_int % 255, 70, 35);
254 rgb = rgb[2] | (rgb[1] << 8) | (rgb[0] << 16);
256 return '#' + rgb.toString(16);
260 // Takes an IRC message object and parses it for displaying
261 generateMessageDisplayObj: function(msg) {
262 var nick_hex, time_difference,
264 sb = this.model.get('scrollback
'),
265 prev_msg = sb[sb.length-2],
266 hour, pm, am_pm_locale_key;
268 // Clone the msg object so we dont modify the original
272 msg.css_classes = '';
274 msg.is_highlight = false;
275 msg.time_string = '';
278 // Nick highlight detecting
279 var nick = _kiwi.app.connections.active_connection.get('nick
');
280 if ((new RegExp('(^|\\W
)(' + escapeRegex(nick) + ')(\\W
|$)', 'i
')).test(msg.msg)) {
281 // Do not highlight the user's own input
282 if (msg
.nick
.localeCompare(nick
) !== 0) {
283 msg
.is_highlight
= true;
284 msg
.css_classes
+= ' highlight';
288 message_words
= msg
.msg
.split(' ');
289 message_words
= _
.map(message_words
, function(word
) {
292 parsed_word
= this.parseMessageUrls(word
);
293 if (typeof parsed_word
=== 'string') return parsed_word
;
295 parsed_word
= this.parseMessageChannels(word
);
296 if (typeof parsed_word
=== 'string') return parsed_word
;
298 parsed_word
= this.parseMessageNicks(word
, (msg
.type
=== 'privmsg'));
299 if (typeof parsed_word
=== 'string') return parsed_word
;
301 parsed_word
= _
.escape(word
);
303 // Replace text emoticons with images
304 if (_kiwi
.global
.settings
.get('show_emoticons')) {
305 parsed_word
= emoticonFromText(parsed_word
);
311 msg
.unparsed_msg
= msg
.msg
;
312 msg
.msg
= message_words
.join(' ');
314 // Convert IRC formatting into HTML formatting
315 msg
.msg
= formatIRCMsg(msg
.msg
);
317 // Add some colours to the nick
318 msg
.nick_style
= 'color:' + this.getNickColour(msg
.nick
) + ';';
320 // Generate a hex string from the nick to be used as a CSS class name
323 _
.map(msg
.nick
.split(''), function (char) {
324 nick_hex
+= char.charCodeAt(0).toString(16);
326 msg
.css_classes
+= ' nick_' + nick_hex
;
330 // Time difference between this message and the last (in minutes)
331 time_difference
= (msg
.time
.getTime() - prev_msg
.time
.getTime())/1000/60;
332 if (prev_msg
.nick
=== msg
.nick
&& time_difference
< 1) {
333 msg
.css_classes
+= ' repeated_nick';
337 // Build up and add the line
338 if (_kiwi
.global
.settings
.get('use_24_hour_timestamps')) {
339 msg
.time_string
= msg
.time
.getHours().toString().lpad(2, "0") + ":" + msg
.time
.getMinutes().toString().lpad(2, "0") + ":" + msg
.time
.getSeconds().toString().lpad(2, "0");
341 hour
= msg
.time
.getHours();
348 am_pm_locale_key
= pm
?
349 'client_views_panel_timestamp_pm' :
350 'client_views_panel_timestamp_am';
352 msg
.time_string
= translateText(am_pm_locale_key
, hour
+ ":" + msg
.time
.getMinutes().toString().lpad(2, "0") + ":" + msg
.time
.getSeconds().toString().lpad(2, "0"));
359 topic: function (topic
) {
360 if (typeof topic
!== 'string' || !topic
) {
361 topic
= this.model
.get("topic");
364 this.model
.addMsg('', styleText('channel_topic', {text
: topic
, channel
: this.model
.get('name')}), 'topic');
366 // If this is the active channel then update the topic bar
367 if (_kiwi
.app
.panels().active
=== this.model
) {
368 _kiwi
.app
.topicbar
.setCurrentTopicFromChannel(this.model
);
372 topicSetBy: function (topic
) {
373 // If this is the active channel then update the topic bar
374 if (_kiwi
.app
.panels().active
=== this.model
) {
375 _kiwi
.app
.topicbar
.setCurrentTopicFromChannel(this.model
);
379 // Click on a nickname
380 nickClick: function (event
) {
381 var $target
= $(event
.currentTarget
),
383 members
= this.model
.get('members'),
386 event
.stopPropagation();
388 // Check this current element for a nick before resorting to the main message
389 // (eg. inline nicks has the nick on its own element within the message)
390 nick
= $target
.data('nick');
392 nick
= $target
.parent('.msg').data('message').nick
;
395 // Make sure this nick is still in the channel
396 member
= members
? members
.getByNick(nick
) : null;
401 _kiwi
.global
.events
.emit('nick:select', {target
: $target
, member
: member
, source
: 'message'})
402 .then(_
.bind(this.openUserMenuForNick
, this, $target
, member
));
406 updateLastSeenMarker: function() {
407 var last_seen
, last_message
;
408 if (this.model
.isActive() && _kiwi
.app
.view
.has_focus
) {
409 // Remove the previous last seen classes
410 last_seen
= this.$(".last_seen");
411 if (last_seen
&& last_seen
.length
) {
412 last_seen
.removeClass("last_seen");
415 // Mark the last message the user saw
416 last_message
= this.$messages
.children().last();
418 last_message
.addClass("last_seen");
424 openUserMenuForNick: function ($target
, member
) {
425 var members
= this.model
.get('members'),
426 are_we_an_op
= !!members
.getByNick(_kiwi
.app
.connections
.active_connection
.get('nick')).get('is_op'),
429 userbox
= new _kiwi
.view
.UserBox();
430 userbox
.setTargets(member
, this.model
);
431 userbox
.displayOpItems(are_we_an_op
);
433 menubox
= new _kiwi
.view
.MenuBox(member
.get('nick') || 'User');
434 menubox
.addItem('userbox', userbox
.$el
);
435 menubox
.showFooter(false);
437 _kiwi
.global
.events
.emit('usermenu:created', {menu
: menubox
, userbox
: userbox
, user
: member
})
438 .then(_
.bind(function() {
441 // Position the userbox + menubox
442 var target_offset
= $target
.offset(),
443 t
= target_offset
.top
,
444 m_bottom
= t
+ menubox
.$el
.outerHeight(), // Where the bottom of menu will be
445 memberlist_bottom
= this.$el
.parent().offset().top
+ this.$el
.parent().outerHeight();
447 // If the bottom of the userbox is going to be too low.. raise it
448 if (m_bottom
> memberlist_bottom
){
449 t
= memberlist_bottom
- menubox
.$el
.outerHeight();
452 // Set the new positon
454 left
: target_offset
.left
,
458 .catch(_
.bind(function() {
467 chanClick: function (event
) {
468 var target
= (event
.target
) ? $(event
.target
).data('channel') : $(event
.srcElement
).data('channel');
470 _kiwi
.app
.connections
.active_connection
.gateway
.join(target
);
474 mediaClick: function (event
) {
475 var $media
= $(event
.target
).parents('.media');
478 if ($media
.data('media')) {
479 media_message
= $media
.data('media');
481 media_message
= new _kiwi
.view
.MediaMessage({el
: $media
[0]});
483 // Cache this MediaMessage instance for when it's opened again
484 $media
.data('media', media_message
);
487 media_message
.toggle();
491 // Cursor hovers over a message
492 msgEnter: function (event
) {
495 // Find a valid class that this element has
496 _
.each($(event
.currentTarget
).parent('.msg').attr('class').split(' '), function (css_class
) {
497 if (css_class
.match(/^nick_[a-z0-9]+/i)) {
498 nick_class
= css_class
;
502 // If no class was found..
503 if (!nick_class
) return;
505 $('.'+nick_class
).addClass('global_nick_highlight');
509 // Cursor leaves message
510 msgLeave: function (event
) {
513 // Find a valid class that this element has
514 _
.each($(event
.currentTarget
).parent('.msg').attr('class').split(' '), function (css_class
) {
515 if (css_class
.match(/^nick_[a-z0-9]+/i)) {
516 nick_class
= css_class
;
520 // If no class was found..
521 if (!nick_class
) return;
523 $('.'+nick_class
).removeClass('global_nick_highlight');