Merge branch 'text_themes' of https://github.com/CoryChaplin/KiwiIRC into CoryChaplin...
[KiwiIRC.git] / client / src / views / channel.js
1 _kiwi.view.Channel = _kiwi.view.Panel.extend({
2 events: function(){
3 var parent_events = this.constructor.__super__.events;
4
5 if(_.isFunction(parent_events)){
6 parent_events = parent_events();
7 }
8 return _.extend({}, parent_events, {
9 'click .msg .nick' : 'nickClick',
10 "click .chan": "chanClick",
11 'click .media .open': 'mediaClick',
12 'mouseenter .msg .nick': 'msgEnter',
13 'mouseleave .msg .nick': 'msgLeave'
14 });
15 },
16
17 initialize: function (options) {
18 this.initializePanel(options);
19
20 // Container for all the messages
21 this.$messages = $('<div class="messages"></div>');
22 this.$el.append(this.$messages);
23
24 this.model.bind('change:topic', this.topic, this);
25
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 }
35
36 // Only show the loader if this is a channel (ie. not a query)
37 if (this.model.isChannel()) {
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>');
39 }
40
41 this.model.bind('msg', this.newMsg, this);
42 this.msg_count = 0;
43 },
44
45
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
56 newMsg: function (msg) {
57 var re, line_msg,
58 nick_colour_hex, nick_hex, is_highlight, msg_css_classes = '',
59 time_difference,
60 sb = this.model.get('scrollback'),
61 prev_msg = sb[sb.length-2],
62 network, hour, pm;
63
64 // Nick highlight detecting
65 if ((new RegExp('(^|\\W)(' + escapeRegex(_kiwi.app.connections.active_connection.get('nick')) + ')(\\W|$)', 'i')).test(msg.msg)) {
66 is_highlight = true;
67 msg_css_classes += ' highlight';
68 }
69
70 // Escape any HTML that may be in here
71 msg.msg = $('<div />').text(msg.msg).html();
72
73 // Make the channels clickable
74 if ((network = this.model.get('network'))) {
75 re = new RegExp('(?:^|\\s)([' + escapeRegex(network.get('channel_prefix')) + '][^ ,\\007]+)', 'g');
76 msg.msg = msg.msg.replace(re, function (match) {
77 return '<a class="chan" data-channel="' + _.escape(match.trim()) + '">' + _.escape(match.trim()) + '</a>';
78 });
79 }
80
81
82 // Parse any links found
83 msg.msg = msg.msg.replace(/(([A-Za-z][A-Za-z0-9\-]*\:\/\/)|(www\.))([\w.\-]+)([a-zA-Z]{2,6})(:[0-9]+)?(\/[\w#!:.?$'()[\]*,;~+=&%@!\-\/]*)?/gi, function (url) {
84 var nice = url,
85 extra_html = '';
86
87 // Add the http if no protoocol was found
88 if (url.match(/^www\./)) {
89 url = 'http://' + url;
90 }
91
92 // Shorten the displayed URL if it's going to be too long
93 if (nice.length > 100) {
94 nice = nice.substr(0, 100) + '...';
95 }
96
97 // Get any media HTML if supported
98 extra_html = _kiwi.view.MediaMessage.buildHtml(url);
99
100 // Make the link clickable
101 return '<a class="link_ext" target="_blank" rel="nofollow" href="' + url + '">' + nice + '</a>' + extra_html;
102 });
103
104
105 // Convert IRC formatting into HTML formatting
106 msg.msg = formatIRCMsg(msg.msg);
107
108 // Replace text emoticons with images
109 if (_kiwi.global.settings.get('show_emoticons')) {
110 msg.msg = emoticonFromText(msg.msg);
111 }
112
113 // Add some colours to the nick (Method based on IRSSIs nickcolor.pl)
114 nick_colour_hex = (function (nick) {
115 var nick_int = 0, rgb;
116
117 _.map(nick.split(''), function (i) { nick_int += i.charCodeAt(0); });
118 rgb = hsl2rgb(nick_int % 255, 70, 35);
119 rgb = rgb[2] | (rgb[1] << 8) | (rgb[0] << 16);
120
121 return '#' + rgb.toString(16);
122 })(msg.nick);
123
124 msg.nick_style = 'color:' + nick_colour_hex + ';';
125
126 // Generate a hex string from the nick to be used as a CSS class name
127 nick_hex = msg.nick_css_class = '';
128 if (msg.nick) {
129 _.map(msg.nick.split(''), function (char) {
130 nick_hex += char.charCodeAt(0).toString(16);
131 });
132 msg_css_classes += ' nick_' + nick_hex;
133 }
134
135 if (prev_msg) {
136 // Time difference between this message and the last (in minutes)
137 time_difference = (msg.time.getTime() - prev_msg.time.getTime())/1000/60;
138 if (prev_msg.nick === msg.nick && time_difference < 1) {
139 msg_css_classes += ' repeated_nick';
140 }
141 }
142
143 // Build up and add the line
144 msg.msg_css_classes = msg_css_classes;
145 if (_kiwi.global.settings.get('use_24_hour_timestamps')) {
146 msg.time_string = msg.time.getHours().toString().lpad(2, "0") + ":" + msg.time.getMinutes().toString().lpad(2, "0") + ":" + msg.time.getSeconds().toString().lpad(2, "0");
147 } else {
148 hour = msg.time.getHours();
149 pm = hour > 11;
150
151 hour = hour % 12;
152 if (hour === 0)
153 hour = 12;
154
155 if (pm) {
156 msg.time_string = _kiwi.global.i18n.translate('client_views_panel_timestamp_pm').fetch(hour + ":" + msg.time.getMinutes().toString().lpad(2, "0") + ":" + msg.time.getSeconds().toString().lpad(2, "0"));
157 } else {
158 msg.time_string = _kiwi.global.i18n.translate('client_views_panel_timestamp_am').fetch(hour + ":" + msg.time.getMinutes().toString().lpad(2, "0") + ":" + msg.time.getSeconds().toString().lpad(2, "0"));
159 }
160 }
161 line_msg = '<div class="msg <%= type %> <%= msg_css_classes %>"><div class="time"><%- time_string %></div><div class="nick" style="<%= nick_style %>"><%- nick %></div><div class="text" style="<%= style %>"><%= msg %> </div></div>';
162 this.$messages.append(_.template(line_msg, msg));
163
164 // Activity/alerts based on the type of new message
165 if (msg.type.match(/^action /)) {
166 this.alert('action');
167
168 } else if (is_highlight) {
169 _kiwi.app.view.alertWindow('* ' + _kiwi.global.i18n.translate('client_views_panel_activity').fetch());
170 _kiwi.app.view.favicon.newHighlight();
171 _kiwi.app.view.playSound('highlight');
172 _kiwi.app.view.showNotification(this.model.get('name'), msg.msg);
173 this.alert('highlight');
174
175 } else {
176 // If this is the active panel, send an alert out
177 if (this.model.isActive()) {
178 _kiwi.app.view.alertWindow('* ' + _kiwi.global.i18n.translate('client_views_panel_activity').fetch());
179 }
180 this.alert('activity');
181 }
182
183 if (this.model.isQuery() && !this.model.isActive()) {
184 _kiwi.app.view.alertWindow('* ' + _kiwi.global.i18n.translate('client_views_panel_activity').fetch());
185
186 // Highlights have already been dealt with above
187 if (!is_highlight) {
188 _kiwi.app.view.favicon.newHighlight();
189 }
190
191 _kiwi.app.view.showNotification(this.model.get('name'), msg.msg);
192 _kiwi.app.view.playSound('highlight');
193 }
194
195 // Update the activity counters
196 (function () {
197 // Only inrement the counters if we're not the active panel
198 if (this.model.isActive()) return;
199
200 var $act = this.model.tab.find('.activity'),
201 count_all_activity = _kiwi.global.settings.get('count_all_activity'),
202 exclude_message_types;
203
204 // Set the default config value
205 if (typeof count_all_activity === 'undefined') {
206 count_all_activity = false;
207 }
208
209 // Do not increment the counter for these message types
210 exclude_message_types = [
211 'action join',
212 'action quit',
213 'action part',
214 'action kick',
215 'action nick',
216 'action mode'
217 ];
218
219 if (count_all_activity || _.indexOf(exclude_message_types, msg.type) === -1) {
220 $act.text((parseInt($act.text(), 10) || 0) + 1);
221 }
222
223 if ($act.text() === '0') {
224 $act.addClass('zero');
225 } else {
226 $act.removeClass('zero');
227 }
228 }).apply(this);
229
230 if(this.model.isActive()) this.scrollToBottom();
231
232 // Make sure our DOM isn't getting too large (Acts as scrollback)
233 this.msg_count++;
234 if (this.msg_count > (parseInt(_kiwi.global.settings.get('scrollback'), 10) || 250)) {
235 $('.msg:first', this.$messages).remove();
236 this.msg_count--;
237 }
238 },
239
240
241 topic: function (topic) {
242 if (typeof topic !== 'string' || !topic) {
243 topic = this.model.get("topic");
244 }
245
246 this.model.addMsg('', styleText('client_views_channel_topic', {'%T': translateText('client_views_channel_topic', [this.model.get('name'), topic]), '%C': this.model.get('name')}), 'topic');
247
248 // If this is the active channel then update the topic bar
249 if (_kiwi.app.panels().active === this) {
250 _kiwi.app.topicbar.setCurrentTopic(this.model.get("topic"));
251 }
252 },
253
254 // Click on a nickname
255 nickClick: function (event) {
256 var nick = $(event.currentTarget).text(),
257 members = this.model.get('members'),
258 are_we_an_op = !!members.getByNick(_kiwi.app.connections.active_connection.get('nick')).get('is_op'),
259 member, query, userbox, menubox;
260
261 if (members) {
262 member = members.getByNick(nick);
263 if (member) {
264 userbox = new _kiwi.view.UserBox();
265 userbox.setTargets(member, this.model);
266 userbox.displayOpItems(are_we_an_op);
267
268 menubox = new _kiwi.view.MenuBox(member.get('nick') || 'User');
269 menubox.addItem('userbox', userbox.$el);
270 menubox.showFooter(false);
271 menubox.show();
272
273 // Position the userbox + menubox
274 (function() {
275 var t = event.pageY,
276 m_bottom = t + menubox.$el.outerHeight(), // Where the bottom of menu will be
277 memberlist_bottom = this.$el.parent().offset().top + this.$el.parent().outerHeight();
278
279 // If the bottom of the userbox is going to be too low.. raise it
280 if (m_bottom > memberlist_bottom){
281 t = memberlist_bottom - menubox.$el.outerHeight();
282 }
283
284 // Set the new positon
285 menubox.$el.offset({
286 left: event.clientX,
287 top: t
288 });
289 }).call(this);
290 }
291 }
292 },
293
294
295 chanClick: function (event) {
296 var target = (event.target) ? $(event.target).data('channel') : $(event.srcElement).data('channel');
297
298 _kiwi.app.connections.active_connection.gateway.join(target);
299 },
300
301
302 mediaClick: function (event) {
303 var $media = $(event.target).parents('.media');
304 var media_message;
305
306 if ($media.data('media')) {
307 media_message = $media.data('media');
308 } else {
309 media_message = new _kiwi.view.MediaMessage({el: $media[0]});
310
311 // Cache this MediaMessage instance for when it's opened again
312 $media.data('media', media_message);
313 }
314
315 media_message.toggle();
316 },
317
318
319 // Cursor hovers over a message
320 msgEnter: function (event) {
321 var nick_class;
322
323 // Find a valid class that this element has
324 _.each($(event.currentTarget).parent('.msg').attr('class').split(' '), function (css_class) {
325 if (css_class.match(/^nick_[a-z0-9]+/i)) {
326 nick_class = css_class;
327 }
328 });
329
330 // If no class was found..
331 if (!nick_class) return;
332
333 $('.'+nick_class).addClass('global_nick_highlight');
334 },
335
336
337 // Cursor leaves message
338 msgLeave: function (event) {
339 var nick_class;
340
341 // Find a valid class that this element has
342 _.each($(event.currentTarget).parent('.msg').attr('class').split(' '), function (css_class) {
343 if (css_class.match(/^nick_[a-z0-9]+/i)) {
344 nick_class = css_class;
345 }
346 });
347
348 // If no class was found..
349 if (!nick_class) return;
350
351 $('.'+nick_class).removeClass('global_nick_highlight');
352 },
353 });