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