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 this.model
.get('members').bind('add', function (member
) {
30 if (member
.get('nick') === this.model
.collection
.network
.get('nick')) {
31 this.$el
.find('.initial_loader').slideUp(function () {
38 // Only show the loader if this is a channel (ie. not a query)
39 if (this.model
.isChannel()) {
40 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>');
43 this.model
.bind('msg', this.newMsg
, this);
51 this.$messages
.empty();
52 _
.each(this.model
.get('scrollback'), function (msg
) {
58 newMsg: function(msg
) {
60 // Parse the msg object into properties fit for displaying
61 msg
= this.generateMessageDisplayObj(msg
);
63 _kiwi
.global
.events
.emit('message:display', {panel
: this.model
, message
: msg
})
64 .then(_
.bind(function() {
67 // Format the nick to the config defined format
68 var display_obj
= _
.clone(msg
);
69 display_obj
.nick
= styleText('message_nick', {nick
: msg
.nick
, prefix
: msg
.nick_prefix
|| ''});
71 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>';
72 this.$messages
.append($(_
.template(line_msg
, display_obj
)).data('message', msg
));
74 // Activity/alerts based on the type of new message
75 if (msg
.type
.match(/^action /)) {
78 } else if (msg
.is_highlight
) {
79 _kiwi
.app
.view
.alertWindow('* ' + _kiwi
.global
.i18n
.translate('client_views_panel_activity').fetch());
80 _kiwi
.app
.view
.favicon
.newHighlight();
81 _kiwi
.app
.view
.playSound('highlight');
82 _kiwi
.app
.view
.showNotification(this.model
.get('name'), msg
.unparsed_msg
);
83 this.alert('highlight');
86 // If this is the active panel, send an alert out
87 if (this.model
.isActive()) {
88 _kiwi
.app
.view
.alertWindow('* ' + _kiwi
.global
.i18n
.translate('client_views_panel_activity').fetch());
90 this.alert('activity');
93 if (this.model
.isQuery() && !this.model
.isActive()) {
94 _kiwi
.app
.view
.alertWindow('* ' + _kiwi
.global
.i18n
.translate('client_views_panel_activity').fetch());
96 // Highlights have already been dealt with above
97 if (!msg
.is_highlight
) {
98 _kiwi
.app
.view
.favicon
.newHighlight();
101 _kiwi
.app
.view
.showNotification(this.model
.get('name'), msg
.unparsed_msg
);
102 _kiwi
.app
.view
.playSound('highlight');
105 // Update the activity counters
107 // Only inrement the counters if we're not the active panel
108 if (this.model
.isActive()) return;
110 var count_all_activity
= _kiwi
.global
.settings
.get('count_all_activity'),
111 exclude_message_types
, new_count
;
113 // Set the default config value
114 if (typeof count_all_activity
=== 'undefined') {
115 count_all_activity
= false;
118 // Do not increment the counter for these message types
119 exclude_message_types
= [
128 if (count_all_activity
|| _
.indexOf(exclude_message_types
, msg
.type
) === -1) {
129 new_count
= this.model
.get('activity_counter') || 0;
131 this.model
.set('activity_counter', new_count
);
136 if(this.model
.isActive()) this.scrollToBottom();
138 // Make sure our DOM isn't getting too large (Acts as scrollback)
140 if (this.msg_count
> (parseInt(_kiwi
.global
.settings
.get('scrollback'), 10) || 250)) {
141 $('.msg:first', this.$messages
).remove();
148 // Let nicks be clickable + colourise within messages
149 parseMessageNicks: function(word
, colourise
) {
150 var members
, member
, colour
= '';
152 members
= this.model
.get('members');
157 member
= members
.getByNick(word
);
162 if (colourise
!== false) {
163 // Use the nick from the member object so the colour matches the letter casing
164 colour
= this.getNickColour(member
.get('nick'));
165 colour
= 'color:' + colour
;
168 return _
.template('<span class="inline-nick" style="<%- colour %>;cursor:pointer;" data-nick="<%- nick %>"><%- nick %></span>', {
176 // Make channels clickable
177 parseMessageChannels: function(word
) {
180 network
= this.model
.get('network');
186 re
= new RegExp('(^|\\s)([' + escapeRegex(network
.get('channel_prefix')) + '][^ ,\\007]+)', 'g');
188 if (!word
.match(re
)) {
192 parsed
= word
.replace(re
, function (m1
, m2
) {
193 return m2
+ '<a class="chan" data-channel="' + _
.escape(m1
.trim()) + '">' + _
.escape(m1
.trim()) + '</a>';
200 parseMessageUrls: function(word
) {
201 var found_a_url
= false,
204 parsed_url
= word
.replace(/(([A-Za-z][A-Za-z0-9\-]*\:\/\/)|(www\.))([\w.\-]+)([a-zA-Z]{2,6})(:[0-9]+)?(\/[\w#!:.?$'()[\]*,;~+=&%@!\-\/]*)?/gi, function (url
) {
208 // Don't allow javascript execution
209 if (url
.match(/^javascript:/)) {
215 // Add the http if no protoocol was found
216 if (url
.match(/^www\./)) {
217 url
= 'http://' + url
;
220 // Shorten the displayed URL if it's going to be too long
221 if (nice
.length
> 100) {
222 nice
= nice
.substr(0, 100) + '...';
225 // Get any media HTML if supported
226 extra_html
= _kiwi
.view
.MediaMessage
.buildHtml(url
);
228 // Make the link clickable
229 return '<a class="link_ext" target="_blank" rel="nofollow" href="' + url
+ '">' + nice
+ '</a>' + extra_html
;
232 return found_a_url
? parsed_url
: false;
236 // Get a colour from a nick (Method based on IRSSIs nickcolor.pl)
237 getNickColour: function(nick
) {
238 var nick_int
= 0, rgb
;
240 _
.map(nick
.split(''), function (i
) { nick_int
+= i
.charCodeAt(0); });
241 rgb
= hsl2rgb(nick_int
% 255, 70, 35);
242 rgb
= rgb
[2] | (rgb
[1] << 8) | (rgb
[0] << 16);
244 return '#' + rgb
.toString(16);
248 // Takes an IRC message object and parses it for displaying
249 generateMessageDisplayObj: function(msg
) {
250 var nick_hex
, time_difference
,
252 sb
= this.model
.get('scrollback'),
253 prev_msg
= sb
[sb
.length
-2],
254 hour
, pm
, am_pm_locale_key
;
256 // Clone the msg object so we dont modify the original
260 msg
.css_classes
= '';
262 msg
.is_highlight
= false;
263 msg
.time_string
= '';
266 // Nick highlight detecting
267 var nick
= _kiwi
.app
.connections
.active_connection
.get('nick');
268 if ((new RegExp('(^|\\W)(' + escapeRegex(nick
) + ')(\\W|$)', 'i')).test(msg
.msg
)) {
269 // Do not highlight the user's own input
270 if (msg
.nick
.localeCompare(nick
) !== 0) {
271 msg
.is_highlight
= true;
272 msg
.css_classes
+= ' highlight';
276 message_words
= msg
.msg
.split(' ');
277 message_words
= _
.map(message_words
, function(word
) {
280 parsed_word
= this.parseMessageUrls(word
);
281 if (typeof parsed_word
=== 'string') return parsed_word
;
283 parsed_word
= this.parseMessageChannels(word
);
284 if (typeof parsed_word
=== 'string') return parsed_word
;
286 parsed_word
= this.parseMessageNicks(word
, (msg
.type
=== 'privmsg'));
287 if (typeof parsed_word
=== 'string') return parsed_word
;
289 parsed_word
= _
.escape(word
);
291 // Replace text emoticons with images
292 if (_kiwi
.global
.settings
.get('show_emoticons')) {
293 parsed_word
= emoticonFromText(parsed_word
);
299 msg
.unparsed_msg
= msg
.msg
;
300 msg
.msg
= message_words
.join(' ');
302 // Convert IRC formatting into HTML formatting
303 msg
.msg
= formatIRCMsg(msg
.msg
);
305 // Add some colours to the nick
306 msg
.nick_style
= 'color:' + this.getNickColour(msg
.nick
) + ';';
308 // Generate a hex string from the nick to be used as a CSS class name
311 _
.map(msg
.nick
.split(''), function (char) {
312 nick_hex
+= char.charCodeAt(0).toString(16);
314 msg
.css_classes
+= ' nick_' + nick_hex
;
318 // Time difference between this message and the last (in minutes)
319 time_difference
= (msg
.time
.getTime() - prev_msg
.time
.getTime())/1000/60;
320 if (prev_msg
.nick
=== msg
.nick
&& time_difference
< 1) {
321 msg
.css_classes
+= ' repeated_nick';
325 // Build up and add the line
326 if (_kiwi
.global
.settings
.get('use_24_hour_timestamps')) {
327 msg
.time_string
= msg
.time
.getHours().toString().lpad(2, "0") + ":" + msg
.time
.getMinutes().toString().lpad(2, "0") + ":" + msg
.time
.getSeconds().toString().lpad(2, "0");
329 hour
= msg
.time
.getHours();
336 am_pm_locale_key
= pm
?
337 'client_views_panel_timestamp_pm' :
338 'client_views_panel_timestamp_am';
340 msg
.time_string
= translateText(am_pm_locale_key
, hour
+ ":" + msg
.time
.getMinutes().toString().lpad(2, "0") + ":" + msg
.time
.getSeconds().toString().lpad(2, "0"));
347 topic: function (topic
) {
348 if (typeof topic
!== 'string' || !topic
) {
349 topic
= this.model
.get("topic");
352 this.model
.addMsg('', styleText('channel_topic', {text
: topic
, channel
: this.model
.get('name')}), 'topic');
354 // If this is the active channel then update the topic bar
355 if (_kiwi
.app
.panels().active
=== this.model
) {
356 _kiwi
.app
.topicbar
.setCurrentTopicFromChannel(this.model
);
360 topicSetBy: function (topic
) {
361 // If this is the active channel then update the topic bar
362 if (_kiwi
.app
.panels().active
=== this.model
) {
363 _kiwi
.app
.topicbar
.setCurrentTopicFromChannel(this.model
);
367 // Click on a nickname
368 nickClick: function (event
) {
370 members
= this.model
.get('members'),
371 are_we_an_op
= !!members
.getByNick(_kiwi
.app
.connections
.active_connection
.get('nick')).get('is_op'),
372 member
, query
, userbox
, menubox
;
374 event
.stopPropagation();
376 // Check this current element for a nick before resorting to the main message
377 // (eg. inline nicks has the nick on its own element within the message)
378 nick
= $(event
.currentTarget
).data('nick');
380 nick
= $(event
.currentTarget
).parent('.msg').data('message').nick
;
384 member
= members
.getByNick(nick
);
386 userbox
= new _kiwi
.view
.UserBox();
387 userbox
.setTargets(member
, this.model
);
388 userbox
.displayOpItems(are_we_an_op
);
390 menubox
= new _kiwi
.view
.MenuBox(member
.get('nick') || 'User');
391 menubox
.addItem('userbox', userbox
.$el
);
392 menubox
.showFooter(false);
395 // Position the userbox + menubox
398 m_bottom
= t
+ menubox
.$el
.outerHeight(), // Where the bottom of menu will be
399 memberlist_bottom
= this.$el
.parent().offset().top
+ this.$el
.parent().outerHeight();
401 // If the bottom of the userbox is going to be too low.. raise it
402 if (m_bottom
> memberlist_bottom
){
403 t
= memberlist_bottom
- menubox
.$el
.outerHeight();
406 // Set the new positon
417 chanClick: function (event
) {
418 var target
= (event
.target
) ? $(event
.target
).data('channel') : $(event
.srcElement
).data('channel');
420 _kiwi
.app
.connections
.active_connection
.gateway
.join(target
);
424 mediaClick: function (event
) {
425 var $media
= $(event
.target
).parents('.media');
428 if ($media
.data('media')) {
429 media_message
= $media
.data('media');
431 media_message
= new _kiwi
.view
.MediaMessage({el
: $media
[0]});
433 // Cache this MediaMessage instance for when it's opened again
434 $media
.data('media', media_message
);
437 media_message
.toggle();
441 // Cursor hovers over a message
442 msgEnter: function (event
) {
445 // Find a valid class that this element has
446 _
.each($(event
.currentTarget
).parent('.msg').attr('class').split(' '), function (css_class
) {
447 if (css_class
.match(/^nick_[a-z0-9]+/i)) {
448 nick_class
= css_class
;
452 // If no class was found..
453 if (!nick_class
) return;
455 $('.'+nick_class
).addClass('global_nick_highlight');
459 // Cursor leaves message
460 msgLeave: function (event
) {
463 // Find a valid class that this element has
464 _
.each($(event
.currentTarget
).parent('.msg').attr('class').split(' '), function (css_class
) {
465 if (css_class
.match(/^nick_[a-z0-9]+/i)) {
466 nick_class
= css_class
;
470 // If no class was found..
471 if (!nick_class
) return;
473 $('.'+nick_class
).removeClass('global_nick_highlight');