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