web_agent_debugger server module update
[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
af03387c 110 var count_all_activity = _kiwi.global.settings.get('count_all_activity'),
31fe7a44 111 exclude_message_types, new_count;
7ba064d9 112
c1c51f22
D
113 // Set the default config value
114 if (typeof count_all_activity === 'undefined') {
115 count_all_activity = false;
116 }
223d53e5 117
c1c51f22
D
118 // Do not increment the counter for these message types
119 exclude_message_types = [
120 'action join',
121 'action quit',
122 'action part',
123 'action kick',
124 'action nick',
125 'action mode'
126 ];
127
128 if (count_all_activity || _.indexOf(exclude_message_types, msg.type) === -1) {
af03387c 129 new_count = this.model.get('activity_counter') || 0;
31fe7a44 130 new_count++;
af03387c 131 this.model.set('activity_counter', new_count);
c1c51f22 132 }
223d53e5 133
c1c51f22 134 }).apply(this);
c794b877 135
c1c51f22 136 if(this.model.isActive()) this.scrollToBottom();
c794b877 137
c1c51f22
D
138 // Make sure our DOM isn't getting too large (Acts as scrollback)
139 this.msg_count++;
140 if (this.msg_count > (parseInt(_kiwi.global.settings.get('scrollback'), 10) || 250)) {
141 $('.msg:first', this.$messages).remove();
142 this.msg_count--;
143 }
144 }, this));
50ac472f
D
145 },
146
c794b877 147
88528078 148 // Let nicks be clickable + colourise within messages
a32b3a30
D
149 parseMessageNicks: function(word, colourise) {
150 var members, member, colour = '';
88528078
D
151
152 members = this.model.get('members');
153 if (!members) {
154 return;
155 }
156
157 member = members.getByNick(word);
158 if (!member) {
159 return;
160 }
161
a32b3a30
D
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;
166 }
88528078 167
a32b3a30 168 return _.template('<span class="inline-nick" style="<%- colour %>;cursor:pointer;" data-nick="<%- nick %>"><%- nick %></span>', {
88528078
D
169 nick: word,
170 colour: colour
171 });
172
173 },
174
175
72a325ec
D
176 // Make channels clickable
177 parseMessageChannels: function(word) {
178 var re,
179 parsed = false,
180 network = this.model.get('network');
181
182 if (!network) {
183 return;
184 }
185
186 re = new RegExp('(^|\\s)([' + escapeRegex(network.get('channel_prefix')) + '][^ ,\\007]+)', 'g');
187
188 if (!word.match(re)) {
189 return parsed;
190 }
191
192 parsed = word.replace(re, function (m1, m2) {
193 return m2 + '<a class="chan" data-channel="' + _.escape(m1.trim()) + '">' + _.escape(m1.trim()) + '</a>';
194 });
195
196 return parsed;
197 },
198
199
200 parseMessageUrls: function(word) {
201 var found_a_url = false,
202 parsed_url;
203
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) {
205 var nice = url,
206 extra_html = '';
207
208 // Don't allow javascript execution
209 if (url.match(/^javascript:/)) {
210 return url;
211 }
212
213 found_a_url = true;
214
215 // Add the http if no protoocol was found
216 if (url.match(/^www\./)) {
217 url = 'http://' + url;
218 }
219
220 // Shorten the displayed URL if it's going to be too long
221 if (nice.length > 100) {
222 nice = nice.substr(0, 100) + '...';
223 }
224
225 // Get any media HTML if supported
226 extra_html = _kiwi.view.MediaMessage.buildHtml(url);
227
228 // Make the link clickable
229 return '<a class="link_ext" target="_blank" rel="nofollow" href="' + url + '">' + nice + '</a>' + extra_html;
230 });
231
232 return found_a_url ? parsed_url : false;
233 },
234
235
236 // Get a colour from a nick (Method based on IRSSIs nickcolor.pl)
237 getNickColour: function(nick) {
238 var nick_int = 0, rgb;
239
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);
243
244 return '#' + rgb.toString(16);
245 },
246
247
248 // Takes an IRC message object and parses it for displaying
249 generateMessageDisplayObj: function(msg) {
250 var nick_hex, time_difference,
251 message_words,
252 sb = this.model.get('scrollback'),
253 prev_msg = sb[sb.length-2],
254 hour, pm, am_pm_locale_key;
255
256 // Clone the msg object so we dont modify the original
257 msg = _.clone(msg);
258
259 // Defaults
260 msg.css_classes = '';
261 msg.nick_style = '';
262 msg.is_highlight = false;
263 msg.time_string = '';
264
265
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';
273 }
274 }
275
276 message_words = msg.msg.split(' ');
277 message_words = _.map(message_words, function(word) {
278 var parsed_word;
279
280 parsed_word = this.parseMessageUrls(word);
281 if (typeof parsed_word === 'string') return parsed_word;
282
283 parsed_word = this.parseMessageChannels(word);
284 if (typeof parsed_word === 'string') return parsed_word;
285
a32b3a30 286 parsed_word = this.parseMessageNicks(word, (msg.type === 'privmsg'));
88528078
D
287 if (typeof parsed_word === 'string') return parsed_word;
288
72a325ec
D
289 parsed_word = _.escape(word);
290
291 // Replace text emoticons with images
292 if (_kiwi.global.settings.get('show_emoticons')) {
293 parsed_word = emoticonFromText(parsed_word);
294 }
295
296 return parsed_word;
297 }, this);
298
ddf30757 299 msg.unparsed_msg = msg.msg;
72a325ec
D
300 msg.msg = message_words.join(' ');
301
302 // Convert IRC formatting into HTML formatting
303 msg.msg = formatIRCMsg(msg.msg);
304
305 // Add some colours to the nick
306 msg.nick_style = 'color:' + this.getNickColour(msg.nick) + ';';
307
308 // Generate a hex string from the nick to be used as a CSS class name
309 nick_hex = '';
310 if (msg.nick) {
311 _.map(msg.nick.split(''), function (char) {
312 nick_hex += char.charCodeAt(0).toString(16);
313 });
314 msg.css_classes += ' nick_' + nick_hex;
315 }
316
317 if (prev_msg) {
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';
322 }
323 }
324
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");
328 } else {
329 hour = msg.time.getHours();
330 pm = hour > 11;
331
332 hour = hour % 12;
333 if (hour === 0)
334 hour = 12;
335
336 am_pm_locale_key = pm ?
337 'client_views_panel_timestamp_pm' :
338 'client_views_panel_timestamp_am';
339
340 msg.time_string = translateText(am_pm_locale_key, hour + ":" + msg.time.getMinutes().toString().lpad(2, "0") + ":" + msg.time.getSeconds().toString().lpad(2, "0"));
341 }
342
343 return msg;
344 },
345
346
50ac472f
D
347 topic: function (topic) {
348 if (typeof topic !== 'string' || !topic) {
349 topic = this.model.get("topic");
350 }
351
9a10cede 352 this.model.addMsg('', styleText('channel_topic', {text: topic, channel: this.model.get('name')}), 'topic');
50ac472f
D
353
354 // If this is the active channel then update the topic bar
3aa7b8cc
D
355 if (_kiwi.app.panels().active === this.model) {
356 _kiwi.app.topicbar.setCurrentTopicFromChannel(this.model);
357 }
358 },
359
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);
50ac472f 364 }
dfb5209c
JA
365 },
366
367 // Click on a nickname
368 nickClick: function (event) {
88528078 369 var nick,
dfb5209c 370 members = this.model.get('members'),
d62fa271 371 are_we_an_op = !!members.getByNick(_kiwi.app.connections.active_connection.get('nick')).get('is_op'),
dfb5209c
JA
372 member, query, userbox, menubox;
373
88528078
D
374 event.stopPropagation();
375
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');
379 if (!nick) {
380 nick = $(event.currentTarget).parent('.msg').data('message').nick;
381 }
382
dfb5209c
JA
383 if (members) {
384 member = members.getByNick(nick);
385 if (member) {
dfb5209c 386 userbox = new _kiwi.view.UserBox();
d62fa271
D
387 userbox.setTargets(member, this.model);
388 userbox.displayOpItems(are_we_an_op);
0826460d 389
dfb5209c
JA
390 menubox = new _kiwi.view.MenuBox(member.get('nick') || 'User');
391 menubox.addItem('userbox', userbox.$el);
279bf34b 392 menubox.showFooter(false);
dfb5209c 393 menubox.show();
0826460d 394
dfb5209c
JA
395 // Position the userbox + menubox
396 (function() {
397 var t = event.pageY,
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();
400
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();
404 }
405
406 // Set the new positon
407 menubox.$el.offset({
408 left: event.clientX,
409 top: t
410 });
411 }).call(this);
412 }
413 }
3499d625
D
414 },
415
416
417 chanClick: function (event) {
425efe7a
JA
418 var target = (event.target) ? $(event.target).data('channel') : $(event.srcElement).data('channel');
419
420 _kiwi.app.connections.active_connection.gateway.join(target);
3499d625
D
421 },
422
423
424 mediaClick: function (event) {
425 var $media = $(event.target).parents('.media');
426 var media_message;
427
428 if ($media.data('media')) {
429 media_message = $media.data('media');
430 } else {
431 media_message = new _kiwi.view.MediaMessage({el: $media[0]});
432
433 // Cache this MediaMessage instance for when it's opened again
434 $media.data('media', media_message);
435 }
436
437 media_message.toggle();
438 },
439
440
441 // Cursor hovers over a message
442 msgEnter: function (event) {
443 var nick_class;
444
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;
449 }
450 });
451
452 // If no class was found..
453 if (!nick_class) return;
454
455 $('.'+nick_class).addClass('global_nick_highlight');
456 },
457
458
459 // Cursor leaves message
460 msgLeave: function (event) {
461 var nick_class;
462
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;
467 }
468 });
469
470 // If no class was found..
471 if (!nick_class) return;
472
473 $('.'+nick_class).removeClass('global_nick_highlight');
3aa7b8cc 474 }
dfb5209c 475});