omnomirc.js 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370
  1. /*
  2. OmnomIRC COPYRIGHT 2010,2011 Netham45
  3. OmnomIRC3 rewrite COPYRIGHT 2013 Nathaniel 'Eeems' van Diepen
  4. This file is part of OmnomIRC.
  5. OmnomIRC is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation, either version 3 of the License, or
  8. (at your option) any later version.
  9. OmnomIRC is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with OmnomIRC. If not, see <http://www.gnu.org/licenses/>.
  15. */
  16. (function(window,$,io,undefined){
  17. "use strict";
  18. var $o = window.OmnomIRC = window.$o = function(){
  19. return 'Version: '+version;
  20. },
  21. event = function(msg,type){
  22. if(settings.debug){
  23. type=exists(type)?type:'event';
  24. switch(type){
  25. case 'ready':type='document_ready';break;
  26. }
  27. log('['+type.toUpperCase()+'] '+msg);
  28. }
  29. },
  30. emit = function(type,data){
  31. if($o.chat.connected()){
  32. socket.emit.apply(socket,arguments);
  33. }else{
  34. if(tabs.length > 0){
  35. $o.msg('Disconnected, cannot do anything');
  36. }
  37. }
  38. },
  39. noop = function(){},
  40. log = function(){
  41. console.log.apply(console,arguments);
  42. },
  43. exists = function(object){
  44. return typeof object != 'undefined';
  45. },
  46. prevent = function(e){
  47. e.stopImmediatePropagation();
  48. e.stopPropagation();
  49. e.preventDefault();
  50. return false;
  51. },
  52. selectedTab=0,
  53. settings = {},
  54. settingsConf = {},
  55. tabs = [],
  56. properties = {
  57. nick: 'User',
  58. sig: '',
  59. tabs: tabs,
  60. themes: []
  61. },
  62. commands = [
  63. { // names
  64. cmd: 'names',
  65. fn: function(args){
  66. emit('names',{
  67. name: $o.ui.tabs.current().name
  68. });
  69. }
  70. },
  71. { // me
  72. cmd: 'me',
  73. help: 'Say something in third person',
  74. fn: function(args){
  75. var i,ret='';
  76. for(i=1;i<args.length;i++){
  77. ret += ' '+args[i];
  78. }
  79. emit('message',{
  80. from: 0,
  81. message: properties.nick+' '+ret,
  82. room: $o.ui.tabs.current().name
  83. });
  84. }
  85. },
  86. { // connect
  87. cmd: 'connect',
  88. fn: function(){
  89. if(!$o.chat.connected()){
  90. $o.chat.connect();
  91. }
  92. }
  93. },
  94. { // disconnect
  95. cmd: 'disconnect',
  96. fn: function(){
  97. $o.disconnect();
  98. }
  99. },
  100. { // nick
  101. cmd: 'nick',
  102. fn: function(args){
  103. $o.set('nick',args[1]);
  104. }
  105. },
  106. { // help
  107. cmd: 'help',
  108. fn: function(args){
  109. if(!exists(args[1])){
  110. var m = 'Commands:',i;
  111. for(i in commands){
  112. m += ' '+commands[i].cmd;
  113. }
  114. $o.msg(m);
  115. }else{
  116. var i,cmd;
  117. for(i in commands){
  118. cmd = commands[i];
  119. if(cmd.cmd == args[1] && exists(cmd.help)){
  120. $o.msg('Command /'+cmd.cmd+': '+cmd.help);
  121. return;
  122. }
  123. }
  124. }
  125. }
  126. },
  127. { // open
  128. cmd: 'open',
  129. fn: function(args){
  130. $o.ui.tabs.add(args[1]);
  131. }
  132. },
  133. { // clear
  134. cmd: 'clear',
  135. fn: function(args){
  136. $o.ui.tabs.current().clear();
  137. }
  138. },
  139. { // close
  140. cmd: 'close',
  141. fn: function(args){
  142. if(args.length > 1){
  143. $o.ui.tabs.remove(args[1]);
  144. }else{
  145. $o.ui.tabs.remove(selectedTab);
  146. }
  147. }
  148. },
  149. { // tabs
  150. cmd: 'tabs',
  151. fn: function(args){
  152. $o.msg('tabs:');
  153. for(var i in tabs){
  154. $o.msg(' ['+i+'] '+tabs[i].name);
  155. }
  156. }
  157. }
  158. ],
  159. handles = [
  160. { // names
  161. on: 'names',
  162. fn: function(data){
  163. var tab = $o.ui.tabs.tab(data.room),
  164. users = tab.users,
  165. i;
  166. tab.users = data.names;
  167. if($o.ui.tabs.idForName(data.room) == selectedTab){
  168. $o.ui.render.users();
  169. }
  170. $(users).each(function(i,v){
  171. if(v != null){
  172. if(tab.users.indexOf(v.trim()) == -1){
  173. emit('echo',{
  174. room: data.room,
  175. message: v+' left the room',
  176. from: 0
  177. });
  178. runHook('part',[
  179. v,
  180. data.room
  181. ]);
  182. }
  183. }
  184. });
  185. $(tab.users).each(function(i,v){
  186. if(v != null){
  187. if(users.indexOf(v.trim()) == -1){
  188. runHook('join',[
  189. v,
  190. data.room
  191. ]);
  192. }
  193. }
  194. });
  195. }
  196. },
  197. { // authorized
  198. on: 'authorized',
  199. fn: function(data){
  200. event('Authorized');
  201. properties.nick = data.nick;
  202. for(var i in settings.autojoin){
  203. emit('join',{
  204. name: settings.autojoin[i]
  205. });
  206. }
  207. runHook('authorized');
  208. }
  209. },
  210. { // join
  211. on: 'join',
  212. fn: function(data){
  213. event('joined '+data.name);
  214. var flag = tabs.length == 0;
  215. $o.ui.tabs.add(data.name);
  216. if(flag){
  217. $o.ui.tabs.select(0);
  218. }
  219. }
  220. },
  221. { // reconnect
  222. on: 'reconnect',
  223. fn: function(data){
  224. event('reconnected');
  225. properties.connected = true;
  226. $o.chat.auth();
  227. runHook('reconnect');
  228. emit('echo',{
  229. room: $o.ui.tabs.current().name,
  230. from: 0,
  231. message: 'reconnected'
  232. });
  233. }
  234. },
  235. { // connect
  236. on: 'connect',
  237. fn: function(data){
  238. event('connected');
  239. properties.connected = true;
  240. $o.chat.auth();
  241. runHook('connect');
  242. emit('echo',{
  243. room: $o.ui.tabs.current().name,
  244. from: 0,
  245. message: 'connected'
  246. });
  247. }
  248. },
  249. { // disconnect
  250. on: 'disconnect',
  251. fn: function(data){
  252. event('disconnected');
  253. properties.connected = false;
  254. runHook('disconnected');
  255. $o.msg('* disconnected');
  256. }
  257. },
  258. { // message
  259. on: 'message',
  260. fn: function(data){
  261. event('recieved message');
  262. var date = new Date(),
  263. string,
  264. time = date.getTime(),
  265. child,
  266. i,
  267. msg = function(msg){
  268. string = '<span class="cell date_cell">[<abbr class="date date_'+time+'" title="'+date.toISOString()+'"></abbr>]</span>';
  269. child = $('<li>').html(string+'<span class="cell">'+
  270. msg
  271. .htmlentities()
  272. .replace(
  273. /(https?:\/\/(([-\w\.]+)+(:\d+)?(\/([\w/_\.]*(\?\S+)?)?)?))/g,
  274. "<a href=\"$1\" title=\"\">$1</a>"
  275. )
  276. +'</span>');
  277. $o.msg({html:child},data.room);
  278. };
  279. if(data.from != 0){
  280. msg(' <'+data.from+'> '+data.message);
  281. }else{
  282. msg(' * '+data.message);
  283. }
  284. abbrDate('abbr.date_'+time);
  285. if(settings.timestamp == ''){
  286. $('.date_cell').css('visibility','hidden');
  287. }else{
  288. $('.date_cell').css('visibility','visible');
  289. }
  290. runHook('message',[
  291. data.message,
  292. data.from,
  293. data.room
  294. ]);
  295. }
  296. }
  297. ],
  298. hooks = [
  299. { // setting - setting
  300. type: 'setting',
  301. hook: 'setting',
  302. fn: function(name){
  303. return name != 'colour';
  304. }
  305. }
  306. ],
  307. plugins = [],
  308. pluginSandbox = {
  309. $: window.jQuery,
  310. jQuery: window.jQuery,
  311. $o: $o,
  312. },
  313. currentPlugin = 0,
  314. Sandbox = function(sandbox){
  315. var i,o = {};
  316. for(i in window){
  317. o[i] = undefined;
  318. }
  319. o.window = o;
  320. for(i in sandbox){
  321. o[i] = sandbox[i];
  322. }
  323. return o;
  324. },
  325. runHook = function(name,args){
  326. var i,r=true,hook,fn,sandbox = {
  327. jQuery: window.jQuery,
  328. $: window.jQuery,
  329. $o: window.OmnomIRC,
  330. OmnomIRC: window.OmnomIRC
  331. };
  332. args=exists(args)?args:[];
  333. for(i in hooks){
  334. hook = hooks[i];
  335. if(hook.hook == name){
  336. r = runInSandbox(hook.fn,sandbox,args,true);
  337. }
  338. if(r == false){
  339. break;
  340. }
  341. }
  342. return r;
  343. },
  344. runInSandbox = function(fn,sandbox,args,isFn){
  345. args = exists(args)?args:[];
  346. sandbox = sandbox instanceof Sandbox?sandbox:new Sandbox(sandbox);
  347. var r = false;
  348. fn = (fn+'').replace(/\/\/.+?(?=\n|\r|$)|\/\*[\s\S]+?\*\//g,'').replace(/\"/g,'\\"').replace(/\n/g,'').replace(/\r/g,'').replace(/\\n/g,'\\\\n');
  349. if(!exists(isFn) || !isFn){
  350. fn = 'function(){'+fn+'}';
  351. }
  352. fn = 'var ret = true;eval("with(this){ret = ('+fn+').apply(this,arguments);}");return ret;';
  353. try{
  354. r = (new Function(fn)).apply(sandbox,args);
  355. }catch(e){
  356. event('Sandboxed function failed to run: '+e+"\nFunction that ran: "+fn,'sandbox_error');
  357. }
  358. return r;
  359. },
  360. version = '3.0',
  361. abbrDate = function(selector){
  362. if(settings.timestamp == 'fuzzy'){
  363. $(selector).timeago();
  364. }else{
  365. $(selector).each(function(){
  366. var timestamp = settings.timestamp,
  367. i,
  368. text='',
  369. date = new Date($(this).attr('title'));
  370. if(timestamp == 'exact'){
  371. timestamp = 'H:m:s t';
  372. }
  373. for(i=0;i<timestamp.length;i++){
  374. switch(timestamp[i]){
  375. case 'H':text+=((date.getHours()+11)%12)+1;break;
  376. case 'h':text+=date.getHours();break;
  377. case 'm':text+=(date.getMinutes()>9?'':'0')+date.getMinutes();break;
  378. case 's':text+=(date.getSeconds()>9?'':'0')+date.getSeconds();break;
  379. case 't':text+=(date.getHours()>11)?'pm':'am';break;
  380. default:text+=timestamp[i];
  381. }
  382. }
  383. $(this).text(text);
  384. }).timeago('dispose');
  385. }
  386. },
  387. socket,$i,$s,$h,$cl,$c,$tl,hht;
  388. $.extend($o,{
  389. version: function(){
  390. return version;
  391. },
  392. register: {
  393. theme: function(name){
  394. if(-1==$.inArray(properties.themes,name)){
  395. properties.themes.push(name);
  396. runHook('theme',[name]);
  397. return true;
  398. }
  399. return false;
  400. },
  401. command: function(name,fn,help){
  402. if(-1==$.inArray(commands,name)){
  403. var o = {
  404. cmd: name,
  405. fn: fn
  406. };
  407. if(exists(help)){
  408. o.help = help;
  409. }
  410. commands.push(o);
  411. return true;
  412. }
  413. return false;
  414. },
  415. plugin: function(name){
  416. if(!exists(plugins[name])){
  417. plugins[name] = {
  418. name: name,
  419. loaded: false,
  420. started: false
  421. };
  422. return true;
  423. }
  424. return false;
  425. },
  426. setting: function(name,type,val,validate,values,callback){
  427. if(!exists(settings[name])){
  428. validate = exists(validate)?validate:function(){};
  429. values = exists(values)?values:undefined;
  430. callback = exists(callback)?callback:function(){};
  431. settings[name] = val;
  432. settingsConf[name] = {
  433. validate: validate,
  434. callback: callback,
  435. type: type,
  436. values: values,
  437. default: val,
  438. name: name
  439. }
  440. return true;
  441. }else{
  442. return false;
  443. }
  444. },
  445. hook: function(event,fn,type){
  446. type=exists(type)?type:'hook';
  447. hooks.push({
  448. hook: event,
  449. fn: fn,
  450. type: type
  451. })
  452. }
  453. },
  454. plugin: {
  455. register: function(name){
  456. return $o.register.plugin(name);
  457. },
  458. start: function(name){
  459. if(exists(plugins[name])){
  460. var plugin = plugins[name];
  461. if(plugin.started){
  462. $o.plugin.stop(name);
  463. }
  464. event('Starting plugin '+name);
  465. pluginSandbox.$o.hook = function(){
  466. var h = arguments[0],
  467. f = arguments[1],
  468. fn;
  469. if( h == 'start' || h == 'stop'){
  470. fn = function(){
  471. if(arguments[0] == name){
  472. f.apply(this,arguments)
  473. }
  474. }
  475. }else{
  476. fn = f;
  477. }
  478. $o.hook.apply($o,[h,fn]);
  479. };
  480. if(!plugin.loaded){
  481. $.ajax('data/plugins/'+name+'/script.js',{
  482. dataType: 'text',
  483. success: function(data){
  484. plugin.script = data;
  485. runInSandbox(data,pluginSandbox);
  486. plugin.started = true;
  487. runHook('start',[name]);
  488. }
  489. });
  490. }else{
  491. runInSandbox(plugin.script,pluginSandbox);
  492. plugin.started = true;
  493. }
  494. return true;
  495. }else{
  496. return false;
  497. }
  498. },
  499. stop: function(name){
  500. if(exists(plugins[name])){
  501. event('Stopping plugin '+name);
  502. runHook('stop',[name]);
  503. var i;
  504. for(i in hooks){
  505. if(hooks[i].type == 'plugin' && hooks[i].plugin == name){
  506. hooks.spice(i,1);
  507. }
  508. }
  509. }else{
  510. return false;
  511. }
  512. },
  513. remove: function(name){
  514. if($o.plugin.stop(name)){
  515. plugin[name] = undefined;
  516. delete plugin[name];
  517. return true;
  518. }else{
  519. return false;
  520. }
  521. },
  522. dir: function(name){
  523. return 'data/plugins/'+name+'/';
  524. }
  525. },
  526. hook: function(event,fn){
  527. $o.register.hook(event,fn);
  528. },
  529. ui: {
  530. render: {
  531. settings: function(){
  532. var name,setting,frag = document.createDocumentFragment(),item;
  533. for(name in settings){
  534. setting = $o.get(name,true);
  535. switch(setting.type){
  536. case 'select':
  537. item = $('<select>')
  538. .attr('id','setting_'+name)
  539. .change(function(){
  540. $o.set(this.id.substr(8),$(this).find(':selected').text().trim(),false);
  541. });
  542. for(var i in setting.values){
  543. item.append(
  544. $('<option>')
  545. .text(setting.values[i])
  546. );
  547. }
  548. item.find(':contains('+setting.val+')').attr('selected','selected');
  549. break;
  550. case 'array':
  551. item = $('<input>')
  552. .attr({
  553. type: 'text',
  554. id: 'setting_'+name
  555. })
  556. .val(setting.val)
  557. .change(function(){
  558. $o.set(this.id.substr(8),$(this).val().split(','),false);
  559. });
  560. break;
  561. case 'boolean':
  562. item = $('<input>')
  563. .attr({
  564. type: 'checkbox',
  565. id: 'setting_'+name
  566. })
  567. .change(function(){
  568. $o.set(this.id.substr(8),$(this).is(':checked'),false);
  569. });
  570. if(setting.val){
  571. item.attr('checked','checked');
  572. }
  573. break;
  574. case 'number':
  575. case 'string':default:
  576. item = $('<input>')
  577. .attr({
  578. type: 'text',
  579. id: 'setting_'+name
  580. })
  581. .val(setting.val)
  582. .change(function(){
  583. $o.set(this.id.substr(8),$(this).val(),false);
  584. });
  585. }
  586. $(frag).append(
  587. $('<li>')
  588. .addClass('row')
  589. .append(
  590. $('<span>')
  591. .text(name)
  592. .addClass('cell')
  593. )
  594. .append(
  595. $('<span>')
  596. .append(item)
  597. .addClass('cell')
  598. )
  599. );
  600. }
  601. if(settings.debug){
  602. $(frag).append(
  603. $('<li>')
  604. .addClass('row')
  605. .attr('id','console-log-controls')
  606. .append(
  607. $('<span>')
  608. .text('Debug Log')
  609. .addClass('cell')
  610. )
  611. .append(
  612. $('<span>')
  613. .addClass('cell')
  614. .append(
  615. $('<button>')
  616. .text('Show')
  617. .click(function(){
  618. $(this).text($('#console-log').is(':visible')?'Show':'Hide');
  619. $('#console-log').toggle();
  620. $('#console-log-clear').toggle();
  621. $('#content').toggle();
  622. })
  623. )
  624. .append(
  625. $('<button>')
  626. .text('Clear')
  627. .attr('id','console-log-clear')
  628. .hide()
  629. .click(function(){
  630. $('#console-log-pre').html('');
  631. })
  632. )
  633. )
  634. );
  635. }else{
  636. $('#console-log-pre').html('');
  637. $('#console-log').hide();
  638. $('#content').show();
  639. }
  640. $('#settings-list').html(frag);
  641. },
  642. users: function(){
  643. event('Rendering userlist');
  644. var $ul = $('#user-list').html(''),
  645. i,
  646. names = $o.ui.tabs.current().users;
  647. for(i in names){
  648. $ul.append(
  649. $('<li>').text(names[i])
  650. );
  651. }
  652. },
  653. tab: function(){
  654. $cl.html($($o.ui.tabs.current().body).clone());
  655. },
  656. tablist: function(){
  657. $tl.html('');
  658. var i,tab;
  659. for(i in tabs){
  660. tab = $o.ui.tabs.obj(i);
  661. if(i==selectedTab){
  662. tab.addClass('clicked');
  663. $('#title').text(tabs[i].name);
  664. $('#topic').text(tabs[i].topic);
  665. }
  666. $tl.append(tab);
  667. }
  668. if($tl.get(0).scrollHeight-20 != $tl.scrollTop()){
  669. $('#tabs-scroll-right').removeClass('disabled');
  670. }
  671. if($tl.scrollTop() != 0){
  672. $('#tabs-scroll-left').removeClass('disabled');
  673. }
  674. }
  675. },
  676. tabs: {
  677. add: function(name){
  678. event('Tab added: '+name);
  679. if(!(function(){
  680. for(var i in tabs){
  681. if(name==tabs[i].name){
  682. return true;
  683. }
  684. }
  685. return false;
  686. })()){
  687. var scroll = $.localStorage('tabs'),
  688. i,
  689. frag = document.createDocumentFragment(),
  690. id = tabs.length;
  691. for(i in scroll){
  692. if(scroll[i].name == name){
  693. scroll[i].body = $(scroll[i].body).slice(-10);
  694. $(frag)
  695. .append(scroll[i].body)
  696. .append(
  697. $('<li>').html('<span class="to_remove">-- loaded old scrollback for '+scroll[i].date+' --</span>')
  698. )
  699. .children()
  700. .children('.remove')
  701. .remove();
  702. $(frag)
  703. .children()
  704. .children('.to_remove')
  705. .removeClass('to_remove')
  706. .addClass('remove');
  707. event('loading old tab scrollback for '+name+' last saved +'+scroll[i].date);
  708. }
  709. }
  710. tabs.push({
  711. name: name,
  712. body: frag,
  713. date: new Date(),
  714. send: function(msg){
  715. $o.chat.send(msg,$o.ui.tabs.tab(id).name);
  716. },
  717. close: function(){
  718. $o.ui.tabs.remove(id);
  719. },
  720. users: [],
  721. names: function(){
  722. emit('names',{
  723. name: $o.ui.tabs.tab(id).name
  724. });
  725. },
  726. select: function(){
  727. $o.ui.tabs.select(id);
  728. },
  729. clear: function(){
  730. $cl.html('');
  731. $o.ui.tabs.tab(id).body = document.createDocumentFragment();
  732. emit('echo',{
  733. room: $o.ui.tabs.tab(id).name,
  734. message: 'messages cleared',
  735. from: 0
  736. });
  737. }
  738. });
  739. $tl.append($o.ui.tabs.obj(id));
  740. $o.ui.render.tablist();
  741. $o.ui.render.users();
  742. runHook('addtab',[$o.ui.tabs.tab(id)]);
  743. }else{
  744. event('Attempted to add an existing tab');
  745. }
  746. },
  747. remove: function(name){
  748. if(typeof name == 'number'){
  749. name = tabs[name].name;
  750. }
  751. for(var id=0;id<tabs.length;id++){
  752. if($o.ui.tabs.tab(id).name == name){
  753. runHook('removetab',[$o.ui.tabs.tab(id)]);
  754. event('Tab removed: '+$o.ui.tabs.tab(id).name);
  755. emit('part',{
  756. name: $o.ui.tabs.tab(id).name
  757. });
  758. tabs.splice(id,1);
  759. if(selectedTab==id&&selectedTab>0){
  760. selectedTab--;
  761. }
  762. break;
  763. }
  764. }
  765. $o.ui.render.tablist();
  766. $cl.html($o.ui.tabs.current().body);
  767. $o.ui.render.users();
  768. },
  769. selected: function(){
  770. return selectedTab;
  771. },
  772. idForName: function(name){
  773. for(var i in tabs){
  774. if(tabs[i].name == name){
  775. return i;
  776. }
  777. }
  778. return false;
  779. },
  780. tab: function(id){
  781. if(typeof id == 'string' && !id.isNumber()){
  782. id = $o.ui.tabs.idForName(id);
  783. if(!id) return false;
  784. }
  785. return exists(tabs[id])?tabs[id]:false;
  786. },
  787. dom: function(id){
  788. if(typeof id == 'string' && !id.isNumber()){
  789. id = $o.ui.tabs.idForName(id);
  790. if(!id) return false;
  791. }
  792. return typeof tabs[id] == 'undefined'?false:tabs[id].body;
  793. },
  794. obj: function(id){
  795. if(exists(id)){
  796. if(typeof id == 'string' && !id.isNumber()){
  797. id = $o.ui.tabs.idForName(id);
  798. if(!id) return;
  799. }
  800. return $('<div>')
  801. .addClass('tab')
  802. .text($o.ui.tabs.tab(id).name)
  803. .mouseup(function(e){
  804. switch(e.which){
  805. case 1: // RMB
  806. if($(this).data('id')!=selectedTab){
  807. $o.ui.tabs.select($(this).data('id'));
  808. return prevent(e);
  809. }
  810. break;
  811. case 2: // MMB
  812. $(this).children('span.close-button').click();
  813. return prevent(e);
  814. break;
  815. case 3: // LMB
  816. return prevent(e);
  817. break;
  818. default:
  819. return prevent(e);
  820. }
  821. })
  822. .append(
  823. $('<span>')
  824. .addClass('close-button')
  825. .click(function(){
  826. $o.ui.tabs.remove(id);
  827. return false;
  828. })
  829. .css({
  830. 'position': 'absolute',
  831. 'background-color': 'inherit',
  832. 'top': 0,
  833. 'right': 0
  834. })
  835. .html('&times;')
  836. )
  837. .data('id',id);
  838. }
  839. },
  840. select: function(id){
  841. if(typeof id == 'string' && !id.isNumber()){
  842. id = $o.ui.tabs.idForName(id);
  843. if(!id){
  844. return false;
  845. }
  846. }
  847. event(id+' '+$o.ui.tabs.tab(id).name,'tab_select');
  848. if(id<tabs.length&&id>=0){
  849. runHook('tabswitch',[$o.ui.tabs.tab(id),$o.ui.tabs.current()]);
  850. selectedTab=id;
  851. }
  852. $tl.children('.clicked').removeClass('clicked');
  853. $($tl.children().get(id)).addClass('clicked');
  854. $('#title').text($o.ui.tabs.tab(id).name);
  855. $('#topic').text($o.ui.tabs.tab(id).topic);
  856. $cl.html($($o.ui.tabs.tab(id).body).clone());
  857. abbrDate('abbr.date');
  858. $o.ui.render.users();
  859. setTimeout(function scrollContent(){
  860. if($c.scrollTop()+$c.height() < $c.prop('scrollHeight')){
  861. $c.scrollTop($c.scrollTop()+1);
  862. setTimeout(scrollContent,settings.scrollspeed);
  863. }else{
  864. event('scrolling stopped');
  865. }
  866. },settings.scrollspeed);
  867. },
  868. current: function(){
  869. if(tabs.length > 0 && tabs.length > selectedTab){
  870. return tabs[selectedTab];
  871. }else{
  872. return {
  873. name: '',
  874. body: document.createDocumentFragment(),
  875. date: new Date(),
  876. send: noop,
  877. close: noop,
  878. users: [],
  879. names: noop,
  880. select: noop,
  881. clear: noop
  882. }
  883. }
  884. }
  885. },
  886. },
  887. chat: {
  888. connect: function(server){
  889. if($o.chat.connected()){
  890. $o.disconnect();
  891. }
  892. event('Connecting');
  893. if(!exists(server)){
  894. server = settings.server;
  895. }
  896. socket = window.socket = io.connect(server);
  897. for(var i in handles){
  898. socket.on(handles[i].on,handles[i].fn);
  899. }
  900. $o.chat.auth();
  901. },
  902. disconnect: function(){
  903. if($o.chat.connected()){
  904. event('Disconnecting');
  905. socket.disconnect();
  906. }
  907. },
  908. connected: function(){
  909. return exists(socket)?properties.connected:false;
  910. },
  911. send: function(msg,room){
  912. if(!exists(room)){
  913. room = $o.ui.tabs.current().name;
  914. }
  915. if(msg !== ''){
  916. if(msg[0] == '/' && msg[1] != '/'){
  917. var args = msg.split(' '),
  918. cmd = args[0].substr(1),
  919. i;
  920. event(msg,'command');
  921. for(i in commands){
  922. if(commands[i].cmd == cmd){
  923. commands[i].fn(args);
  924. return;
  925. }
  926. }
  927. $o.msg(cmd+' is not a valid command.');
  928. }else{
  929. if(msg[0]+msg[1] == '//'){
  930. msg = msg.substr(1);
  931. }
  932. event(msg,'send');
  933. if(runHook('send',[msg,room])){
  934. emit('message',{
  935. message: msg,
  936. room: room,
  937. from: properties.nick
  938. });
  939. }
  940. }
  941. }
  942. },
  943. auth: function(){
  944. if(settings.nick == ''){
  945. $o.set('nick','User');
  946. return;
  947. }
  948. emit('auth',{
  949. nick: settings.nick
  950. // TODO - send authorization info
  951. });
  952. }
  953. },
  954. get: function(name,formatted){
  955. if(!exists(formatted)){
  956. return exists(settings[name])?settings[name]:false;
  957. }else{
  958. if(exists(settingsConf[name]) && exists(settings[name])){
  959. var r = $.extend({},settingsConf[name]);
  960. r.val = settings[name];
  961. r.validate = undefined;
  962. r.callback = undefined;
  963. delete r['validate'];
  964. delete r['callback'];
  965. return r;
  966. }else{
  967. return false;
  968. }
  969. }
  970. },
  971. set: function(name,value,render){
  972. if(exists(settings[name])){
  973. var setting;
  974. if(exists(settingsConf[name])){
  975. setting = $.extend({},settingsConf[name]);
  976. }else{
  977. setting = {
  978. val: setting[name],
  979. callback: function(){},
  980. validate: function(){},
  981. values: undefined,
  982. type: typeof setting[name]
  983. }
  984. }
  985. if(
  986. (exists(setting.values) && $.inArray(value,setting.values) == -1) ||
  987. setting.validate(settings[name],value,setting.values,name) == false ||
  988. !runHook('setting',[
  989. name,
  990. settings[name],
  991. value,
  992. $o.get(name,true).values
  993. ])
  994. ){
  995. if(exists(render)){
  996. $o.ui.render.settings();
  997. }
  998. return false;
  999. }
  1000. settings[name] = value;
  1001. $.localStorage('settings',JSON.stringify(settings));
  1002. setting.callback(value,name,exists(render));
  1003. if(exists(render)){
  1004. $o.ui.render.settings();
  1005. }
  1006. return true;
  1007. }else{
  1008. if(exists(render)){
  1009. $o.ui.render.settings();
  1010. }
  1011. return false;
  1012. }
  1013. },
  1014. prop: function(name){
  1015. return exists(properties[name])?properties[name]:null;
  1016. },
  1017. send: function(msg){
  1018. $o.chat.send(msg);
  1019. },
  1020. msg: function(msg,tabName){
  1021. var frag;
  1022. if(!exists(tabName) || tabName == $o.ui.tabs.current().name || !exists($o.ui.tabs.tab(tabName).body)){
  1023. frag = document.createDocumentFragment();
  1024. }else{
  1025. frag = $o.ui.tabs.tab(tabName).body;
  1026. }
  1027. try{
  1028. switch(typeof msg){
  1029. case 'string':
  1030. $(frag).append($('<li>').html(msg.htmlentities()));
  1031. break;
  1032. case 'object':
  1033. if(!exists(msg.html)){
  1034. $(frag).append($('<li>').html('&lt;'+msg.user+'&gt;&nbsp;'+msg.text.htmlentities()));
  1035. }else{
  1036. $(frag).append(msg.html);
  1037. }
  1038. break;
  1039. }
  1040. }catch(e){event('Failed to add message','error')}
  1041. if(tabs.length > 0){
  1042. $($o.ui.tabs.tab(tabName).body || $o.ui.tabs.current().body).append(frag);
  1043. }
  1044. var scroll = [],i,html;
  1045. for(i in tabs){
  1046. html = '';
  1047. $(tabs[i].body).children().each(function(){
  1048. html += this.outerHTML;
  1049. });
  1050. scroll.push({
  1051. name: tabs[i].name,
  1052. body: html,
  1053. date: new Date().toString()
  1054. });
  1055. }
  1056. $.localStorage('tabs',scroll);
  1057. if(!exists(tabName) || tabName == $o.ui.tabs.current().name){
  1058. $o.ui.tabs.select(selectedTab);
  1059. }
  1060. },
  1061. event: function(event_name,message){
  1062. event(message,event_name);
  1063. }
  1064. });
  1065. (function(settings){
  1066. var i,s;
  1067. for(i in settings){
  1068. s = settings[i];
  1069. $o.register.setting(i,s.type,s.val,s['validate'],s['values'],s['callback']);
  1070. }
  1071. })({
  1072. colour: {
  1073. type: 'boolean',
  1074. val: false
  1075. },
  1076. debug: {
  1077. type: 'boolean',
  1078. val: false,
  1079. callback: function(v,s,r){
  1080. if(r){
  1081. $o.ui.render.settings();
  1082. }
  1083. }
  1084. },
  1085. timestamp: {
  1086. type: 'string',
  1087. val: 'exact',
  1088. callback: function(v,s){
  1089. abbrDate('abbr.date');
  1090. if(v == ''){
  1091. $('.date_cell').css('visibility','hidden');
  1092. }else{
  1093. $('.date_cell').css('visibility','visible');
  1094. }
  1095. }
  1096. },
  1097. server: {
  1098. type: 'string',
  1099. val: location.origin
  1100. },
  1101. autoconnect: {
  1102. type: 'boolean',
  1103. val: true
  1104. },
  1105. autojoin: {
  1106. type: 'array',
  1107. val: [
  1108. '#omnimaga',
  1109. '#omnimaga-fr',
  1110. '#irp'
  1111. ]
  1112. },
  1113. scrollspeed: {
  1114. type: 'number',
  1115. val: 10
  1116. },
  1117. theme: {
  1118. type: 'select',
  1119. val: 'default',
  1120. values: properties.themes,
  1121. validate: function(o,n,v,s){
  1122. runHook('untheme',[o,n]);
  1123. },
  1124. callback: function(v,s,r){
  1125. if($('link[id="theme-style"]').attr('href') != 'data/themes/'+v+'/style.css'){
  1126. event('Loading theme '+v);
  1127. runHook('theme',[v]);
  1128. $('link[id="theme-style"]').attr({
  1129. id: 'theme-style',
  1130. rel: 'stylesheet',
  1131. href: 'data/themes/'+v+'/style.css'
  1132. });
  1133. var i,h;
  1134. for(i in hooks){
  1135. h = hooks[i];
  1136. if(h.type == 'style'){
  1137. hooks.splice(i,1);
  1138. }
  1139. }
  1140. $.ajax('data/themes/'+v+'/script.js',{
  1141. dataType: 'text',
  1142. success: function(data){
  1143. var sandbox = {
  1144. load: function(fn){
  1145. fn();
  1146. },
  1147. unload: function(fn){
  1148. $o.register.hook('untheme',"function(o,n){if(o == '"+v+"'){("+(fn+'').replace(/\/\/.+?(?=\n|\r|$)|\/\*[\s\S]+?\*\//g,'').replace(/\"/g,'\\"').replace(/\n/g,'').replace(/\r/g,'')+")();}}",'style');
  1149. },
  1150. $: window.jQuery,
  1151. $o: window.OmnomIRC,
  1152. OmnomIRC: window.OmnomIRC,
  1153. };
  1154. runInSandbox(data,sandbox);
  1155. runHook('themechange',[v]);
  1156. }
  1157. });
  1158. }
  1159. }
  1160. },
  1161. nick: {
  1162. type: 'string',
  1163. val: 'User',
  1164. callback: function(){
  1165. $o.chat.auth();
  1166. }
  1167. }
  1168. });
  1169. $.extend(pluginSandbox,$o);
  1170. String.prototype.htmlentities = function(){
  1171. return this
  1172. .replace(/&/g, '&amp;')
  1173. .replace(/</g, '&lt;')
  1174. .replace(/>/g, '&gt;')
  1175. .replace(/\n/g,'<br/>')
  1176. .replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;')
  1177. .replace(/\s/g, '&nbsp;')
  1178. .replace(/"/g, '&quot;');
  1179. };
  1180. $(document).ready(function(){
  1181. $.extend(settings,$.parseJSON($.localStorage('settings')));
  1182. $.localStorage('settings',JSON.stringify(settings));
  1183. settingsConf['theme'].callback(settings['theme'],'theme',true);
  1184. $i = $('#input');
  1185. $s = $('#send');
  1186. $cl = $('#content-list');
  1187. $c = $('#content');
  1188. $tl = $('#tabs-list');
  1189. $h = $('#head');
  1190. $s.click(function(){
  1191. if(!$s.hasClass('clicked')){
  1192. $s.addClass('clicked');
  1193. setTimeout(function(){
  1194. $s.removeClass('clicked');
  1195. },500);
  1196. }
  1197. $o.send($i.val());
  1198. $i.val('');
  1199. });
  1200. $i.keypress(function(e){
  1201. if(e.keyCode == 13){
  1202. if(!$s.hasClass('clicked')){
  1203. $s.addClass('clicked');
  1204. setTimeout(function(){
  1205. $s.removeClass('clicked');
  1206. },500);
  1207. }
  1208. $o.send($i.val());
  1209. $i.val('');
  1210. }
  1211. });
  1212. $('#settings, #users').click(function(){
  1213. $(this).addClass('open');
  1214. $(this).children('.close-button').show();
  1215. }).hover(function(){
  1216. $(this).addClass('hovered');
  1217. },function(){
  1218. $(this).removeClass('hovered');
  1219. }).children('.close-button').click(function(){
  1220. $(this).parent().removeClass('open');
  1221. $(this).hide();
  1222. return false;
  1223. }).hide();
  1224. $('#users').hoverIntent({
  1225. out: function(){
  1226. $(this).removeClass('open');
  1227. $(this).children('.close-button').hide();
  1228. },
  1229. timeout: 1000
  1230. });
  1231. $('#content').click(function(){
  1232. $('#settings, #users, #head').removeClass('hovered').removeClass('open');
  1233. $('#settings, #users').children('.close-button').hide()
  1234. });
  1235. $('.unselectable').attr('unselectable','on');
  1236. $.contextMenu({
  1237. selector: 'div.tab',
  1238. items: {
  1239. add: {
  1240. name: 'New Tab',
  1241. icon: 'add',
  1242. callback: function(){
  1243. $(this).contextMenu('hide');
  1244. $o.ui.tabs.add(prompt('Channel'));
  1245. }
  1246. },
  1247. s1: '',
  1248. close: {
  1249. name: 'Close',
  1250. icon: 'delete',
  1251. callback: function(){
  1252. $(this).contextMenu('hide');
  1253. $o.ui.tabs.remove($(this).data('id'));
  1254. }
  1255. }
  1256. },
  1257. zIndex: 99999,
  1258. trigger: 'right'
  1259. });
  1260. $.contextMenu({
  1261. selector: '#tabs-list',
  1262. items: {
  1263. add: {
  1264. name: 'New Tab',
  1265. icon: 'add',
  1266. callback: function(){
  1267. $(this).contextMenu('hide');
  1268. $o.ui.tabs.add(prompt('channel'));
  1269. }
  1270. }
  1271. },
  1272. zIndex: 99999,
  1273. trigger: 'right'
  1274. });
  1275. $('#tabs-scroll-right').click(function(){
  1276. event('scroll right');
  1277. $tl.scrollTop(($tl.scrollTop()||0)+20);
  1278. if($tl.get(0).scrollHeight-20 == $tl.scrollTop()){
  1279. $('#tabs-scroll-right').addClass('disabled');
  1280. }
  1281. $('#tabs-scroll-left').removeClass('disabled');
  1282. });
  1283. $('#tabs-scroll-left').click(function(){
  1284. event('scroll left');
  1285. $tl.scrollTop(($tl.scrollTop()||0)-20);
  1286. if($tl.scrollTop() == 0){
  1287. $('#tabs-scroll-left').addClass('disabled');
  1288. }
  1289. $('#tabs-scroll-right').removeClass('disabled');
  1290. });
  1291. (function scrollup(){
  1292. $('#tabs-scroll-left').click();
  1293. if($tl.scrollTop() != 0){
  1294. setTimeout(scrollup,10);
  1295. }
  1296. })();
  1297. event('Date '+new Date,'ready');
  1298. $h.addClass('hovered');
  1299. setTimeout(function(){
  1300. $h.removeClass('hovered');
  1301. },1000);
  1302. $o.ui.render.settings();
  1303. if(settings.autoconnect){
  1304. $o.chat.connect();
  1305. }
  1306. // Check for script updates and update if required
  1307. (function checkScripts(){
  1308. for(var i in document.scripts){
  1309. (function(el,src){
  1310. if(exists(src) && el.innerHTML == ''){
  1311. $.ajax(src,{
  1312. success: function(source){
  1313. if(exists($(el).data('source')) && $(el).data('source') != source){
  1314. event('Reloading','update');
  1315. location.reload();
  1316. }
  1317. $(el).data('source',source);
  1318. },
  1319. dataType: 'text'
  1320. });
  1321. }
  1322. })(document.scripts[i],document.scripts[i].src);
  1323. }
  1324. var s = $('link');
  1325. for(i in s){
  1326. (function(el,href){
  1327. if(exists(href) && el.innerHTML == ''){
  1328. $.ajax(href,{
  1329. success: function(source){
  1330. if(exists($(el).data('source')) && $(el).data('source') != source){
  1331. event('Updating CSS','update');
  1332. if(!exists($(el).data('href'))){
  1333. $(el).data('href',href);
  1334. }
  1335. if($(el).data('href') != href){
  1336. href = $(el).data('href');
  1337. }
  1338. el.href += '?reload='+new Date().getTime();
  1339. }
  1340. $(el).data('source',source);
  1341. },
  1342. dataType: 'text'
  1343. });
  1344. }
  1345. })(s[i],s[i].href);
  1346. }
  1347. setTimeout(checkScripts,1000*30);
  1348. })();
  1349. runHook('load');
  1350. });
  1351. window.io = null;
  1352. })(window,jQuery,io);
  1353. if (!Date.prototype.toISOString) {
  1354. Date.prototype.toISOString = function() {
  1355. function pad(n) { return n < 10 ? '0' + n : n }
  1356. return this.getUTCFullYear() + '-'
  1357. + pad(this.getUTCMonth() + 1) + '-'
  1358. + pad(this.getUTCDate()) + 'T'
  1359. + pad(this.getUTCHours()) + ':'
  1360. + pad(this.getUTCMinutes()) + ':'
  1361. + pad(this.getUTCSeconds()) + 'Z';
  1362. };
  1363. }
  1364. if(!String.prototype.isNumber){
  1365. String.prototype.isNumber = function(){return /^\d+$/.test(this);}
  1366. }