Merge branch 'backbone_ui' of github.com:prawnsalad/KiwiIRC into backbone_ui
[KiwiIRC.git] / client_backbone / js / utils.js
1 /*jslint devel: true, browser: true, continue: true, sloppy: true, forin: true, plusplus: true, maxerr: 50, indent: 4, nomen: true, regexp: true*/
2 /*globals $, front, gateway, Utilityview */
3
4 // Holds anything kiwi client specific (ie. front, gateway, kiwi.plugs..)
5 /**
6 * @namespace
7 */
8 var kiwi = {};
9
10
11
12 /**
13 * Suppresses console.log
14 * @param {Boolean} debug Whether to re-enable console.log or not
15 */
16 function manageDebug(debug) {
17 return;
18 var log, consoleBackUp;
19 if (window.console) {
20 consoleBackUp = window.console.log;
21 window.console.log = function () {
22 if (debug) {
23 consoleBackUp.apply(console, arguments);
24 }
25 };
26 } else {
27 log = window.opera ? window.opera.postError : alert;
28 window.console = {};
29 window.console.log = function (str) {
30 if (debug) {
31 log(str);
32 }
33 };
34 }
35 }
36
37 /**
38 * Generate a random string of given length
39 * @param {Number} string_length The length of the random string
40 * @returns {String} The random string
41 */
42 function randomString(string_length) {
43 var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz",
44 randomstring = '',
45 i,
46 rnum;
47 for (i = 0; i < string_length; i++) {
48 rnum = Math.floor(Math.random() * chars.length);
49 randomstring += chars.substring(rnum, rnum + 1);
50 }
51 return randomstring;
52 }
53
54 /**
55 * String.trim shim
56 */
57 if (typeof String.prototype.trim === 'undefined') {
58 String.prototype.trim = function () {
59 return this.replace(/^\s+|\s+$/g, "");
60 };
61 }
62
63 /**
64 * String.lpad shim
65 * @param {Number} length The length of padding
66 * @param {String} characher The character to pad with
67 * @returns {String} The padded string
68 */
69 if (typeof String.prototype.lpad === 'undefined') {
70 String.prototype.lpad = function (length, character) {
71 var padding = "",
72 i;
73 for (i = 0; i < length; i++) {
74 padding += character;
75 }
76 return (padding + this).slice(-length);
77 };
78 }
79
80
81 /**
82 * Convert seconds into hours:minutes:seconds
83 * @param {Number} secs The number of seconds to converts
84 * @returns {Object} An object representing the hours/minutes/second conversion of secs
85 */
86 function secondsToTime(secs) {
87 var hours, minutes, seconds, divisor_for_minutes, divisor_for_seconds, obj;
88 hours = Math.floor(secs / (60 * 60));
89
90 divisor_for_minutes = secs % (60 * 60);
91 minutes = Math.floor(divisor_for_minutes / 60);
92
93 divisor_for_seconds = divisor_for_minutes % 60;
94 seconds = Math.ceil(divisor_for_seconds);
95
96 obj = {
97 "h": hours,
98 "m": minutes,
99 "s": seconds
100 };
101 return obj;
102 }
103
104
105
106
107
108
109 /**
110 * Formats a message. Adds bold, underline and colouring
111 * @param {String} msg The message to format
112 * @returns {String} The HTML formatted message
113 */
114 function formatIRCMsg (msg) {
115 var re, next;
116
117 if ((!msg) || (typeof msg !== 'string')) {
118 return '';
119 }
120
121 // bold
122 if (msg.indexOf(String.fromCharCode(2)) !== -1) {
123 next = '<b>';
124 while (msg.indexOf(String.fromCharCode(2)) !== -1) {
125 msg = msg.replace(String.fromCharCode(2), next);
126 next = (next === '<b>') ? '</b>' : '<b>';
127 }
128 if (next === '</b>') {
129 msg = msg + '</b>';
130 }
131 }
132
133 // underline
134 if (msg.indexOf(String.fromCharCode(31)) !== -1) {
135 next = '<u>';
136 while (msg.indexOf(String.fromCharCode(31)) !== -1) {
137 msg = msg.replace(String.fromCharCode(31), next);
138 next = (next === '<u>') ? '</u>' : '<u>';
139 }
140 if (next === '</u>') {
141 msg = msg + '</u>';
142 }
143 }
144
145 // colour
146 /**
147 * @inner
148 */
149 msg = (function (msg) {
150 var replace, colourMatch, col, i, match, to, endCol, fg, bg, str;
151 replace = '';
152 /**
153 * @inner
154 */
155 colourMatch = function (str) {
156 var re = /^\x03([0-9][0-5]?)(,([0-9][0-5]?))?/;
157 return re.exec(str);
158 };
159 /**
160 * @inner
161 */
162 col = function (num) {
163 switch (parseInt(num, 10)) {
164 case 0:
165 return '#FFFFFF';
166 case 1:
167 return '#000000';
168 case 2:
169 return '#000080';
170 case 3:
171 return '#008000';
172 case 4:
173 return '#FF0000';
174 case 5:
175 return '#800040';
176 case 6:
177 return '#800080';
178 case 7:
179 return '#FF8040';
180 case 8:
181 return '#FFFF00';
182 case 9:
183 return '#80FF00';
184 case 10:
185 return '#008080';
186 case 11:
187 return '#00FFFF';
188 case 12:
189 return '#0000FF';
190 case 13:
191 return '#FF55FF';
192 case 14:
193 return '#808080';
194 case 15:
195 return '#C0C0C0';
196 default:
197 return null;
198 }
199 };
200 if (msg.indexOf('\x03') !== -1) {
201 i = msg.indexOf('\x03');
202 replace = msg.substr(0, i);
203 while (i < msg.length) {
204 /**
205 * @inner
206 */
207 match = colourMatch(msg.substr(i, 6));
208 if (match) {
209 //console.log(match);
210 // Next colour code
211 to = msg.indexOf('\x03', i + 1);
212 endCol = msg.indexOf(String.fromCharCode(15), i + 1);
213 if (endCol !== -1) {
214 if (to === -1) {
215 to = endCol;
216 } else {
217 to = ((to < endCol) ? to : endCol);
218 }
219 }
220 if (to === -1) {
221 to = msg.length;
222 }
223 //console.log(i, to);
224 fg = col(match[1]);
225 bg = col(match[3]);
226 str = msg.substring(i + 1 + match[1].length + ((bg !== null) ? match[2].length + 1 : 0), to);
227 //console.log(str);
228 replace += '<span style="' + ((fg !== null) ? 'color: ' + fg + '; ' : '') + ((bg !== null) ? 'background-color: ' + bg + ';' : '') + '">' + str + '</span>';
229 i = to;
230 } else {
231 if ((msg[i] !== '\x03') && (msg[i] !== String.fromCharCode(15))) {
232 replace += msg[i];
233 }
234 i++;
235 }
236 }
237 return replace;
238 }
239 return msg;
240 }(msg));
241
242 return msg;
243 }
244
245
246
247
248
249
250
251
252 /*
253 PLUGINS
254 Each function in each object is looped through and ran. The resulting text
255 is expected to be returned.
256 */
257 var plugins = [
258 {
259 name: "images",
260 onaddmsg: function (event, opts) {
261 if (!event.msg) {
262 return event;
263 }
264
265 event.msg = event.msg.replace(/^((https?\:\/\/|ftp\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?(\.jpg|\.jpeg|\.gif|\.bmp|\.png)$/gi, function (url) {
266 // Don't let any future plugins change it (ie. html_safe plugins)
267 event.event_bubbles = false;
268
269 var img = '<img class="link_img_a" src="' + url + '" height="100%" width="100%" />';
270 return '<a class="link_ext link_img" target="_blank" rel="nofollow" href="' + url + '" style="height:50px;width:50px;display:block">' + img + '<div class="tt box"></div></a>';
271 });
272
273 return event;
274 }
275 },
276
277 {
278 name: "html_safe",
279 onaddmsg: function (event, opts) {
280 event.msg = $('<div/>').text(event.msg).html();
281 event.nick = $('<div/>').text(event.nick).html();
282
283 return event;
284 }
285 },
286
287 {
288 name: "activity",
289 onaddmsg: function (event, opts) {
290 //if (kiwi.front.cur_channel.name.toLowerCase() !== kiwi.front.tabviews[event.tabview.toLowerCase()].name) {
291 // kiwi.front.tabviews[event.tabview].activity();
292 //}
293
294 return event;
295 }
296 },
297
298 {
299 name: "highlight",
300 onaddmsg: function (event, opts) {
301 //var tab = Tabviews.getTab(event.tabview.toLowerCase());
302
303 // If we have a highlight...
304 //if (event.msg.toLowerCase().indexOf(kiwi.gateway.nick.toLowerCase()) > -1) {
305 // if (Tabview.getCurrentTab() !== tab) {
306 // tab.highlight();
307 // }
308 // if (kiwi.front.isChannel(tab.name)) {
309 // event.msg = '<span style="color:red;">' + event.msg + '</span>';
310 // }
311 //}
312
313 // If it's a PM, highlight
314 //if (!kiwi.front.isChannel(tab.name) && tab.name !== "server"
315 // && Tabview.getCurrentTab().name.toLowerCase() !== tab.name
316 //) {
317 // tab.highlight();
318 //}
319
320 return event;
321 }
322 },
323
324
325
326 {
327 //Following method taken from: http://snipplr.com/view/13533/convert-text-urls-into-links/
328 name: "linkify_plain",
329 onaddmsg: function (event, opts) {
330 if (!event.msg) {
331 return event;
332 }
333
334 event.msg = event.msg.replace(/((https?\:\/\/|ftp\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/gi, function (url) {
335 var nice;
336 // If it's any of the supported images in the images plugin, skip it
337 if (url.match(/(\.jpg|\.jpeg|\.gif|\.bmp|\.png)$/)) {
338 return url;
339 }
340
341 nice = url;
342 if (url.match('^https?:\/\/')) {
343 //nice = nice.replace(/^https?:\/\//i,'')
344 nice = url; // Shutting up JSLint...
345 } else {
346 url = 'http://' + url;
347 }
348
349 //return '<a class="link_ext" target="_blank" rel="nofollow" href="' + url + '">' + nice + '<div class="tt box"></div></a>';
350 return '<a class="link_ext" target="_blank" rel="nofollow" href="' + url + '">' + nice + '</a>';
351 });
352
353 return event;
354 }
355 },
356
357 {
358 name: "lftobr",
359 onaddmsg: function (event, opts) {
360 if (!event.msg) {
361 return event;
362 }
363
364 event.msg = event.msg.replace(/\n/gi, function (txt) {
365 return '<br/>';
366 });
367
368 return event;
369 }
370 },
371
372
373 /*
374 * Disabled due to many websites closing kiwi with iframe busting
375 {
376 name: "inBrowser",
377 oninit: function (event, opts) {
378 $('#windows a.link_ext').live('mouseover', this.mouseover);
379 $('#windows a.link_ext').live('mouseout', this.mouseout);
380 $('#windows a.link_ext').live('click', this.mouseclick);
381 },
382
383 onunload: function (event, opts) {
384 // TODO: make this work (remove all .link_ext_browser as created in mouseover())
385 $('#windows a.link_ext').die('mouseover', this.mouseover);
386 $('#windows a.link_ext').die('mouseout', this.mouseout);
387 $('#windows a.link_ext').die('click', this.mouseclick);
388 },
389
390
391
392 mouseover: function (e) {
393 var a = $(this),
394 tt = $('.tt', a),
395 tooltip;
396
397 if (tt.text() === '') {
398 tooltip = $('<a class="link_ext_browser">Open in Kiwi..</a>');
399 tt.append(tooltip);
400 }
401
402 tt.css('top', -tt.outerHeight() + 'px');
403 tt.css('left', (a.outerWidth() / 2) - (tt.outerWidth() / 2));
404 },
405
406 mouseout: function (e) {
407 var a = $(this),
408 tt = $('.tt', a);
409 },
410
411 mouseclick: function (e) {
412 var a = $(this),
413 t;
414
415 switch (e.target.className) {
416 case 'link_ext':
417 case 'link_img_a':
418 return true;
419 //break;
420 case 'link_ext_browser':
421 t = new Utilityview('Browser');
422 t.topic = a.attr('href');
423
424 t.iframe = $('<iframe border="0" class="utility_view" src="" style="width:100%;height:100%;border:none;"></iframe>');
425 t.iframe.attr('src', a.attr('href'));
426 t.div.append(t.iframe);
427 t.show();
428 break;
429 }
430 return false;
431
432 }
433 },
434 */
435
436 {
437 name: "nick_colour",
438 onaddmsg: function (event, opts) {
439 if (!event.msg) {
440 return event;
441 }
442
443 //if (typeof kiwi.front.tabviews[event.tabview].nick_colours === 'undefined') {
444 // kiwi.front.tabviews[event.tabview].nick_colours = {};
445 //}
446
447 //if (typeof kiwi.front.tabviews[event.tabview].nick_colours[event.nick] === 'undefined') {
448 // kiwi.front.tabviews[event.tabview].nick_colours[event.nick] = this.randColour();
449 //}
450
451 //var c = kiwi.front.tabviews[event.tabview].nick_colours[event.nick];
452 var c = this.randColour();
453 event.nick = '<span style="color:' + c + ';">' + event.nick + '</span>';
454
455 return event;
456 },
457
458
459
460 randColour: function () {
461 var h = this.rand(-250, 0),
462 s = this.rand(30, 100),
463 l = this.rand(20, 70);
464 return 'hsl(' + h + ',' + s + '%,' + l + '%)';
465 },
466
467
468 rand: function (min, max) {
469 return parseInt(Math.random() * (max - min + 1), 10) + min;
470 }
471 },
472
473 {
474 name: "kiwitest",
475 oninit: function (event, opts) {
476 console.log('registering namespace');
477 $(gateway).bind("kiwi.lol.browser", function (e, data) {
478 console.log('YAY kiwitest');
479 console.log(data);
480 });
481 }
482 }
483 ];
484
485
486
487
488
489
490
491 /**
492 * @namespace
493 */
494 kiwi.plugs = {};
495 /**
496 * Loaded plugins
497 */
498 kiwi.plugs.loaded = {};
499 /**
500 * Load a plugin
501 * @param {Object} plugin The plugin to be loaded
502 * @returns {Boolean} True on success, false on failure
503 */
504 kiwi.plugs.loadPlugin = function (plugin) {
505 var plugin_ret;
506 if (typeof plugin.name !== 'string') {
507 return false;
508 }
509
510 plugin_ret = kiwi.plugs.run('plugin_load', {plugin: plugin});
511 if (typeof plugin_ret === 'object') {
512 kiwi.plugs.loaded[plugin_ret.plugin.name] = plugin_ret.plugin;
513 kiwi.plugs.loaded[plugin_ret.plugin.name].local_data = new kiwi.dataStore('kiwi_plugin_' + plugin_ret.plugin.name);
514 }
515 kiwi.plugs.run('init', {}, {run_only: plugin_ret.plugin.name});
516
517 return true;
518 };
519
520 /**
521 * Unload a plugin
522 * @param {String} plugin_name The name of the plugin to unload
523 */
524 kiwi.plugs.unloadPlugin = function (plugin_name) {
525 if (typeof kiwi.plugs.loaded[plugin_name] !== 'object') {
526 return;
527 }
528
529 kiwi.plugs.run('unload', {}, {run_only: plugin_name});
530 delete kiwi.plugs.loaded[plugin_name];
531 };
532
533
534
535 /**
536 * Run an event against all loaded plugins
537 * @param {String} event_name The name of the event
538 * @param {Object} event_data The data to pass to the plugin
539 * @param {Object} opts Options
540 * @returns {Object} Event data, possibly modified by the plugins
541 */
542 kiwi.plugs.run = function (event_name, event_data, opts) {
543 var ret = event_data,
544 ret_tmp,
545 plugin_name;
546
547 // Set some defaults if not provided
548 event_data = (typeof event_data === 'undefined') ? {} : event_data;
549 opts = (typeof opts === 'undefined') ? {} : opts;
550
551 for (plugin_name in kiwi.plugs.loaded) {
552 // If we're only calling 1 plugin, make sure it's that one
553 if (typeof opts.run_only === 'string' && opts.run_only !== plugin_name) {
554 continue;
555 }
556
557 if (typeof kiwi.plugs.loaded[plugin_name]['on' + event_name] === 'function') {
558 try {
559 ret_tmp = kiwi.plugs.loaded[plugin_name]['on' + event_name](ret, opts);
560 if (ret_tmp === null) {
561 return null;
562 }
563 ret = ret_tmp;
564
565 if (typeof ret.event_bubbles === 'boolean' && ret.event_bubbles === false) {
566 delete ret.event_bubbles;
567 return ret;
568 }
569 } catch (e) {
570 }
571 }
572 }
573
574 return ret;
575 };
576
577
578
579 /**
580 * @constructor
581 * @param {String} data_namespace The namespace for the data store
582 */
583 kiwi.dataStore = function (data_namespace) {
584 var namespace = data_namespace;
585
586 this.get = function (key) {
587 return $.jStorage.get(data_namespace + '_' + key);
588 };
589
590 this.set = function (key, value) {
591 return $.jStorage.set(data_namespace + '_' + key, value);
592 };
593 };
594
595 kiwi.data = new kiwi.dataStore('kiwi');
596
597
598
599
600 /*
601 * jQuery jStorage plugin
602 * https://github.com/andris9/jStorage/
603 */
604 (function(f){if(!f||!(f.toJSON||Object.toJSON||window.JSON)){throw new Error("jQuery, MooTools or Prototype needs to be loaded before jStorage!")}var g={},d={jStorage:"{}"},h=null,j=0,l=f.toJSON||Object.toJSON||(window.JSON&&(JSON.encode||JSON.stringify)),e=f.evalJSON||(window.JSON&&(JSON.decode||JSON.parse))||function(m){return String(m).evalJSON()},i=false;_XMLService={isXML:function(n){var m=(n?n.ownerDocument||n:0).documentElement;return m?m.nodeName!=="HTML":false},encode:function(n){if(!this.isXML(n)){return false}try{return new XMLSerializer().serializeToString(n)}catch(m){try{return n.xml}catch(o){}}return false},decode:function(n){var m=("DOMParser" in window&&(new DOMParser()).parseFromString)||(window.ActiveXObject&&function(p){var q=new ActiveXObject("Microsoft.XMLDOM");q.async="false";q.loadXML(p);return q}),o;if(!m){return false}o=m.call("DOMParser" in window&&(new DOMParser())||window,n,"text/xml");return this.isXML(o)?o:false}};function k(){if("localStorage" in window){try{if(window.localStorage){d=window.localStorage;i="localStorage"}}catch(p){}}else{if("globalStorage" in window){try{if(window.globalStorage){d=window.globalStorage[window.location.hostname];i="globalStorage"}}catch(o){}}else{h=document.createElement("link");if(h.addBehavior){h.style.behavior="url(#default#userData)";document.getElementsByTagName("head")[0].appendChild(h);h.load("jStorage");var n="{}";try{n=h.getAttribute("jStorage")}catch(m){}d.jStorage=n;i="userDataBehavior"}else{h=null;return}}}b()}function b(){if(d.jStorage){try{g=e(String(d.jStorage))}catch(m){d.jStorage="{}"}}else{d.jStorage="{}"}j=d.jStorage?String(d.jStorage).length:0}function c(){try{d.jStorage=l(g);if(h){h.setAttribute("jStorage",d.jStorage);h.save("jStorage")}j=d.jStorage?String(d.jStorage).length:0}catch(m){}}function a(m){if(!m||(typeof m!="string"&&typeof m!="number")){throw new TypeError("Key name must be string or numeric")}return true}f.jStorage={version:"0.1.5.1",set:function(m,n){a(m);if(_XMLService.isXML(n)){n={_is_xml:true,xml:_XMLService.encode(n)}}g[m]=n;c();return n},get:function(m,n){a(m);if(m in g){if(g[m]&&typeof g[m]=="object"&&g[m]._is_xml&&g[m]._is_xml){return _XMLService.decode(g[m].xml)}else{return g[m]}}return typeof(n)=="undefined"?null:n},deleteKey:function(m){a(m);if(m in g){delete g[m];c();return true}return false},flush:function(){g={};c();return true},storageObj:function(){function m(){}m.prototype=g;return new m()},index:function(){var m=[],n;for(n in g){if(g.hasOwnProperty(n)){m.push(n)}}return m},storageSize:function(){return j},currentBackend:function(){return i},storageAvailable:function(){return !!i},reInit:function(){var m,o;if(h&&h.addBehavior){m=document.createElement("link");h.parentNode.replaceChild(m,h);h=m;h.style.behavior="url(#default#userData)";document.getElementsByTagName("head")[0].appendChild(h);h.load("jStorage");o="{}";try{o=h.getAttribute("jStorage")}catch(n){}d.jStorage=o;i="userDataBehavior"}b()}};k()})(window.jQuery||window.$);