Merge branch 'settings' into development
[KiwiIRC.git] / client / assets / src / views / panel.js
1 _kiwi.view.Panel = Backbone.View.extend({
2 tagName: "div",
3 className: "panel messages",
4
5 events: {
6 "click .chan": "chanClick",
7 'click .media .open': 'mediaClick',
8 'mouseenter .msg .nick': 'msgEnter',
9 'mouseleave .msg .nick': 'msgLeave'
10 },
11
12 initialize: function (options) {
13 this.initializePanel(options);
14 },
15
16 initializePanel: function (options) {
17 this.$el.css('display', 'none');
18 options = options || {};
19
20 // Containing element for this panel
21 if (options.container) {
22 this.$container = $(options.container);
23 } else {
24 this.$container = $('#kiwi .panels .container1');
25 }
26
27 this.$el.appendTo(this.$container);
28
29 this.alert_level = 0;
30
31 this.model.bind('msg', this.newMsg, this);
32 this.msg_count = 0;
33
34 this.model.set({"view": this}, {"silent": true});
35 },
36
37 render: function () {
38 var that = this;
39
40 this.$el.empty();
41 _.each(this.model.get('scrollback'), function (msg) {
42 that.newMsg(msg);
43 });
44 },
45
46 newMsg: function (msg) {
47 var re, line_msg, $this = this.$el,
48 nick_colour_hex, nick_hex, is_highlight, msg_css_classes = '',
49 time_difference,
50 sb = this.model.get('scrollback'),
51 prev_msg = sb[sb.length-2];
52
53 // Nick highlight detecting
54 if ((new RegExp('(^|\\W)(' + escapeRegex(_kiwi.app.connections.active_connection.get('nick')) + ')(\\W|$)', 'i')).test(msg.msg)) {
55 is_highlight = true;
56 msg_css_classes += ' highlight';
57 }
58
59 // Escape any HTML that may be in here
60 msg.msg = $('<div />').text(msg.msg).html();
61
62 // Make the channels clickable
63 re = new RegExp('(?:^|\\s)([' + escapeRegex(_kiwi.gateway.get('channel_prefix')) + '][^ ,.\\007]+)', 'g');
64 msg.msg = msg.msg.replace(re, function (match) {
65 return '<a class="chan" data-channel="' + match.trim() + '">' + match + '</a>';
66 });
67
68
69 // Parse any links found
70 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) {
71 var nice = url,
72 extra_html = '';
73
74 // Add the http if no protoocol was found
75 if (url.match(/^www\./)) {
76 url = 'http://' + url;
77 }
78
79 // Shorten the displayed URL if it's going to be too long
80 if (nice.length > 100) {
81 nice = nice.substr(0, 100) + '...';
82 }
83
84 // Get any media HTML if supported
85 extra_html = _kiwi.view.MediaMessage.buildHtml(url);
86
87 // Make the link clickable
88 return '<a class="link_ext" target="_blank" rel="nofollow" href="' + url + '">' + nice + '</a>' + extra_html;
89 });
90
91
92 // Convert IRC formatting into HTML formatting
93 msg.msg = formatIRCMsg(msg.msg);
94
95
96 // Add some colours to the nick (Method based on IRSSIs nickcolor.pl)
97 nick_colour_hex = (function (nick) {
98 var nick_int = 0, rgb;
99
100 _.map(nick.split(''), function (i) { nick_int += i.charCodeAt(0); });
101 rgb = hsl2rgb(nick_int % 255, 70, 35);
102 rgb = rgb[2] | (rgb[1] << 8) | (rgb[0] << 16);
103
104 return '#' + rgb.toString(16);
105 })(msg.nick);
106
107 msg.nick_style = 'color:' + nick_colour_hex + ';';
108
109 // Generate a hex string from the nick to be used as a CSS class name
110 nick_hex = msg.nick_css_class = '';
111 if (msg.nick) {
112 _.map(msg.nick.split(''), function (char) {
113 nick_hex += char.charCodeAt(0).toString(16);
114 });
115 msg_css_classes += ' nick_' + nick_hex;
116 }
117
118 if (prev_msg) {
119 // Time difference between this message and the last (in minutes)
120 time_difference = (msg.date.getTime() - prev_msg.date.getTime())/1000/60;
121 if (prev_msg.nick === msg.nick && time_difference < 1) {
122 msg_css_classes += ' repeated_nick';
123 }
124 }
125
126 // Build up and add the line
127 msg.msg_css_classes = msg_css_classes;
128 line_msg = '<div class="msg <%= type %> <%= msg_css_classes %>"><div class="time"><%- time %></div><div class="nick" style="<%= nick_style %>"><%- nick %></div><div class="text" style="<%= style %>"><%= msg %> </div></div>';
129 $this.append(_.template(line_msg, msg));
130
131 // Activity/alerts based on the type of new message
132 if (msg.type.match(/^action /)) {
133 this.alert('action');
134
135 } else if (is_highlight) {
136 _kiwi.app.view.alertWindow('* ' + _kiwi.global.i18n.translate('client_views_panel_activity').fetch());
137 _kiwi.app.view.favicon.newHighlight();
138 _kiwi.app.view.playSound('highlight');
139 this.alert('highlight');
140
141 } else {
142 // If this is the active panel, send an alert out
143 if (this.model.isActive()) {
144 _kiwi.app.view.alertWindow('* ' + _kiwi.global.i18n.translate('client_views_panel_activity').fetch());
145 }
146 this.alert('activity');
147 }
148
149 if (this.model.isQuery() && !this.model.isActive()) {
150 _kiwi.app.view.alertWindow('* ' + _kiwi.global.i18n.translate('client_views_panel_activity').fetch());
151 if (!is_highlight) {
152 _kiwi.app.view.favicon.newHighlight();
153 }
154 _kiwi.app.view.playSound('highlight');
155 }
156
157 // Update the activity counters
158 (function () {
159 // Only inrement the counters if we're not the active panel
160 if (this.model.isActive()) return;
161
162 var $act = this.model.tab.find('.activity');
163 $act.text((parseInt($act.text(), 10) || 0) + 1);
164 if ($act.text() === '0') {
165 $act.addClass('zero');
166 } else {
167 $act.removeClass('zero');
168 }
169 }).apply(this);
170
171 this.scrollToBottom();
172
173 // Make sure our DOM isn't getting too large (Acts as scrollback)
174 this.msg_count++;
175 if (this.msg_count > (parseInt(_kiwi.global.settings.get('scrollback'), 10) || 250)) {
176 $('.msg:first', this.$el).remove();
177 this.msg_count--;
178 }
179 },
180 chanClick: function (event) {
181 if (event.target) {
182 _kiwi.gateway.join(null, $(event.target).data('channel'));
183 } else {
184 // IE...
185 _kiwi.gateway.join(null, $(event.srcElement).data('channel'));
186 }
187 },
188
189 mediaClick: function (event) {
190 var $media = $(event.target).parents('.media');
191 var media_message;
192
193 if ($media.data('media')) {
194 media_message = $media.data('media');
195 } else {
196 media_message = new _kiwi.view.MediaMessage({el: $media[0]});
197
198 // Cache this MediaMessage instance for when it's opened again
199 $media.data('media', media_message);
200 }
201
202 media_message.open();
203 },
204
205 // Cursor hovers over a message
206 msgEnter: function (event) {
207 var nick_class;
208
209 // Find a valid class that this element has
210 _.each($(event.currentTarget).parent('.msg').attr('class').split(' '), function (css_class) {
211 if (css_class.match(/^nick_[a-z0-9]+/i)) {
212 nick_class = css_class;
213 }
214 });
215
216 // If no class was found..
217 if (!nick_class) return;
218
219 $('.'+nick_class).addClass('global_nick_highlight');
220 },
221
222 // Cursor leaves message
223 msgLeave: function (event) {
224 var nick_class;
225
226 // Find a valid class that this element has
227 _.each($(event.currentTarget).parent('.msg').attr('class').split(' '), function (css_class) {
228 if (css_class.match(/^nick_[a-z0-9]+/i)) {
229 nick_class = css_class;
230 }
231 });
232
233 // If no class was found..
234 if (!nick_class) return;
235
236 $('.'+nick_class).removeClass('global_nick_highlight');
237 },
238
239 show: function () {
240 var $this = this.$el;
241
242 // Hide all other panels and show this one
243 this.$container.children('.panel').css('display', 'none');
244 $this.css('display', 'block');
245
246 // Show this panels memberlist
247 var members = this.model.get("members");
248 if (members) {
249 $('#kiwi .memberlists').removeClass('disabled');
250 members.view.show();
251 } else {
252 // Memberlist not found for this panel, hide any active ones
253 $('#kiwi .memberlists').addClass('disabled').children().removeClass('active');
254 }
255
256 // Remove any alerts and activity counters for this panel
257 this.alert('none');
258 this.model.tab.find('.activity').text('0').addClass('zero');
259
260 _kiwi.app.panels.trigger('active', this.model, _kiwi.app.panels().active);
261 this.model.trigger('active', this.model);
262
263 _kiwi.app.view.doLayout();
264
265 this.scrollToBottom(true);
266 },
267
268
269 alert: function (level) {
270 // No need to highlight if this si the active panel
271 if (this.model == _kiwi.app.panels().active) return;
272
273 var types, type_idx;
274 types = ['none', 'action', 'activity', 'highlight'];
275
276 // Default alert level
277 level = level || 'none';
278
279 // If this alert level does not exist, assume clearing current level
280 type_idx = _.indexOf(types, level);
281 if (!type_idx) {
282 level = 'none';
283 type_idx = 0;
284 }
285
286 // Only 'upgrade' the alert. Never down (unless clearing)
287 if (type_idx !== 0 && type_idx <= this.alert_level) {
288 return;
289 }
290
291 // Clear any existing levels
292 this.model.tab.removeClass(function (i, css) {
293 return (css.match(/\balert_\S+/g) || []).join(' ');
294 });
295
296 // Add the new level if there is one
297 if (level !== 'none') {
298 this.model.tab.addClass('alert_' + level);
299 }
300
301 this.alert_level = type_idx;
302 },
303
304
305 // Scroll to the bottom of the panel
306 scrollToBottom: function (force_down) {
307 // If this isn't the active panel, don't scroll
308 if (this.model !== _kiwi.app.panels().active) return;
309
310 // Don't scroll down if we're scrolled up the panel a little
311 if (force_down || this.$container.scrollTop() + this.$container.height() > this.$el.outerHeight() - 150) {
312 this.$container[0].scrollTop = this.$container[0].scrollHeight;
313 }
314 }
315 });