omnomirc.js 28 KB

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