7250b367bcc9d21bb2a9618dbefc1cc63dc79f89
[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;
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="' + match.trim() + '">' + match + '</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 msg.time_string = msg.time.getHours().toString().lpad(2, "0") + ":" + msg.time.getMinutes().toString().lpad(2, "0") + ":" + msg.time.getSeconds().toString().lpad(2, "0");
146 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>';
147 this.$messages.append(_.template(line_msg, msg));
148
149 // Activity/alerts based on the type of new message
150 if (msg.type.match(/^action /)) {
151 this.alert('action');
152
153 } else if (is_highlight) {
154 _kiwi.app.view.alertWindow('* ' + _kiwi.global.i18n.translate('client_views_panel_activity').fetch());
155 _kiwi.app.view.favicon.newHighlight();
156 _kiwi.app.view.playSound('highlight');
157 _kiwi.app.view.showNotification(this.model.get('name'), msg.msg);
158 this.alert('highlight');
159
160 } else {
161 // If this is the active panel, send an alert out
162 if (this.model.isActive()) {
163 _kiwi.app.view.alertWindow('* ' + _kiwi.global.i18n.translate('client_views_panel_activity').fetch());
164 }
165 this.alert('activity');
166 }
167
168 if (this.model.isQuery() && !this.model.isActive()) {
169 _kiwi.app.view.alertWindow('* ' + _kiwi.global.i18n.translate('client_views_panel_activity').fetch());
170
171 // Highlights have already been dealt with above
172 if (!is_highlight) {
173 _kiwi.app.view.favicon.newHighlight();
174 }
175
176 _kiwi.app.view.showNotification(this.model.get('name'), msg.msg);
177 _kiwi.app.view.playSound('highlight');
178 }
179
180 // Update the activity counters
181 (function () {
182 // Only inrement the counters if we're not the active panel
183 if (this.model.isActive()) return;
184
185 var $act = this.model.tab.find('.activity'),
186 count_all_activity = _kiwi.global.settings.get('count_all_activity'),
187 exclude_message_types;
188
189 // Set the default config value
190 if (typeof count_all_activity === 'undefined') {
191 count_all_activity = false;
192 }
193
194 // Do not increment the counter for these message types
195 exclude_message_types = [
196 'action join',
197 'action quit',
198 'action part',
199 'action kick',
200 'action nick',
201 'action mode'
202 ];
203
204 if (count_all_activity || exclude_message_types.indexOf(msg.type) === -1) {
205 $act.text((parseInt($act.text(), 10) || 0) + 1);
206 }
207
208 if ($act.text() === '0') {
209 $act.addClass('zero');
210 } else {
211 $act.removeClass('zero');
212 }
213 }).apply(this);
214
215 if(this.model.isActive()) this.scrollToBottom();
216
217 // Make sure our DOM isn't getting too large (Acts as scrollback)
218 this.msg_count++;
219 if (this.msg_count > (parseInt(_kiwi.global.settings.get('scrollback'), 10) || 250)) {
220 $('.msg:first', this.$messages).remove();
221 this.msg_count--;
222 }
223 },
224
225
226 topic: function (topic) {
227 if (typeof topic !== 'string' || !topic) {
228 topic = this.model.get("topic");
229 }
230
231 this.model.addMsg('', _kiwi.global.text_theme.styleText('client_views_channel_topic', [this.model.get('name'), topic]), 'topic');
232
233 // If this is the active channel then update the topic bar
234 if (_kiwi.app.panels().active === this) {
235 _kiwi.app.topicbar.setCurrentTopic(this.model.get("topic"));
236 }
237 },
238
239 // Click on a nickname
240 nickClick: function (event) {
241 var nick = $(event.currentTarget).text(),
242 members = this.model.get('members'),
243 are_we_an_op = !!members.getByNick(_kiwi.app.connections.active_connection.get('nick')).get('is_op'),
244 member, query, userbox, menubox;
245
246 if (members) {
247 member = members.getByNick(nick);
248 if (member) {
249 userbox = new _kiwi.view.UserBox();
250 userbox.setTargets(member, this.model);
251 userbox.displayOpItems(are_we_an_op);
252
253 menubox = new _kiwi.view.MenuBox(member.get('nick') || 'User');
254 menubox.addItem('userbox', userbox.$el);
255 menubox.showFooter(false);
256 menubox.show();
257
258 // Position the userbox + menubox
259 (function() {
260 var t = event.pageY,
261 m_bottom = t + menubox.$el.outerHeight(), // Where the bottom of menu will be
262 memberlist_bottom = this.$el.parent().offset().top + this.$el.parent().outerHeight();
263
264 // If the bottom of the userbox is going to be too low.. raise it
265 if (m_bottom > memberlist_bottom){
266 t = memberlist_bottom - menubox.$el.outerHeight();
267 }
268
269 // Set the new positon
270 menubox.$el.offset({
271 left: event.clientX,
272 top: t
273 });
274 }).call(this);
275 }
276 }
277 },
278
279
280 chanClick: function (event) {
281 var target = (event.target) ? $(event.target).data('channel') : $(event.srcElement).data('channel');
282
283 _kiwi.app.connections.active_connection.gateway.join(target);
284 },
285
286
287 mediaClick: function (event) {
288 var $media = $(event.target).parents('.media');
289 var media_message;
290
291 if ($media.data('media')) {
292 media_message = $media.data('media');
293 } else {
294 media_message = new _kiwi.view.MediaMessage({el: $media[0]});
295
296 // Cache this MediaMessage instance for when it's opened again
297 $media.data('media', media_message);
298 }
299
300 media_message.toggle();
301 },
302
303
304 // Cursor hovers over a message
305 msgEnter: function (event) {
306 var nick_class;
307
308 // Find a valid class that this element has
309 _.each($(event.currentTarget).parent('.msg').attr('class').split(' '), function (css_class) {
310 if (css_class.match(/^nick_[a-z0-9]+/i)) {
311 nick_class = css_class;
312 }
313 });
314
315 // If no class was found..
316 if (!nick_class) return;
317
318 $('.'+nick_class).addClass('global_nick_highlight');
319 },
320
321
322 // Cursor leaves message
323 msgLeave: function (event) {
324 var nick_class;
325
326 // Find a valid class that this element has
327 _.each($(event.currentTarget).parent('.msg').attr('class').split(' '), function (css_class) {
328 if (css_class.match(/^nick_[a-z0-9]+/i)) {
329 nick_class = css_class;
330 }
331 });
332
333 // If no class was found..
334 if (!nick_class) return;
335
336 $('.'+nick_class).removeClass('global_nick_highlight');
337 },
338 });