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