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