omnomirc.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  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. var $o = window.OmnomIRC = window.$o = function(){
  18. return 'Version: '+$o.version
  19. },
  20. event = function(msg,type){
  21. type=typeof type == 'undefined'?'event':type;
  22. switch(type){
  23. case 'ready':type='document_ready';break;
  24. }
  25. log('['+type.toUpperCase()+'] '+msg);
  26. },
  27. log = function(){
  28. console.log.apply(console,arguments);
  29. },
  30. exists = function(object){
  31. return typeof object != 'undefined';
  32. },
  33. prevent = function(e){
  34. e.stopImmediatePropagation();
  35. e.stopPropagation();
  36. e.preventDefault();
  37. return false;
  38. },
  39. selectedTab=0,
  40. settings = {
  41. colour: false,
  42. timestamp: false,
  43. server: location.origin,
  44. autojoin: [
  45. '#omnimaga',
  46. '#omnimaga-fr',
  47. '#irp'
  48. ]
  49. },
  50. tabs = [],
  51. properties = {
  52. nick: 'User',
  53. sig: '',
  54. tabs: tabs
  55. },
  56. commands = [
  57. {
  58. cmd: 'names',
  59. fn: function(args){
  60. socket.emit('names',{
  61. name: tabs[selectedTab].name
  62. });
  63. }
  64. },
  65. {
  66. cmd: 'nick',
  67. fn: function(args){
  68. console.log(args);
  69. properties.nick = args[1];
  70. $o.auth();
  71. }
  72. },
  73. {
  74. cmd: 'help',
  75. fn: function(args){
  76. var m = 'Commands:',i;
  77. for(i in commands){
  78. m += ' '+commands[i].cmd;
  79. }
  80. $o.msg(m);
  81. }
  82. },
  83. {
  84. cmd: 'open',
  85. fn: function(args){
  86. tabs.push({
  87. name: args[1],
  88. title: args[2],
  89. topic: 'Topic for '+args[2]
  90. });
  91. $o.refreshTabs();
  92. }
  93. },
  94. {
  95. cmd: 'clear',
  96. fn: function(args){
  97. $cl.html('');
  98. }
  99. },
  100. {
  101. cmd: 'close',
  102. fn: function(args){
  103. if(args.length > 1){
  104. $o.removeTab(args[1]);
  105. }else{
  106. $o.removeTab(selectedTab);
  107. }
  108. }
  109. },
  110. {
  111. cmd: 'tabs',
  112. fn: function(args){
  113. $o.msg('tabs:');
  114. for(var i in tabs){
  115. $o.msg(' ['+i+'] '+tabs[i].name);
  116. }
  117. }
  118. }
  119. ],
  120. handles = [
  121. {
  122. on: 'authorized',
  123. fn: function(){
  124. for(var i in settings.autojoin){
  125. socket.emit('join',{
  126. name: settings.autojoin[i]
  127. });
  128. }
  129. }
  130. },
  131. {
  132. on: 'join',
  133. fn: function(data){
  134. event('joined '+data.name);
  135. var flag = tabs.length == 0;
  136. $o.addTab(data.name,data.title);
  137. if(flag){
  138. $o.selectTab(0);
  139. }
  140. }
  141. },
  142. {
  143. on: 'reconnect',
  144. fn: function(data){
  145. event('reconnected');
  146. $o.auth();
  147. }
  148. },
  149. {
  150. on: 'message',
  151. fn: function(data){
  152. event('recieved message');
  153. var date = new Date(),
  154. string,
  155. time = date.getTime(),
  156. child,
  157. i,
  158. msg = function(msg){
  159. if(!settings.timestamp){
  160. string = '<span class="cell"></span>';
  161. }else{
  162. string = '<span class="cell">[<abbr class="date date_'+time+'" title="'+date.toISOString()+'"></abbr>]</span>';
  163. }
  164. child = $('<li>').html(string+'<span class="cell">'+msg.htmlentities()+'</span>');
  165. $o.msg({html:child},data.room);
  166. };
  167. if(data.from != 0){
  168. msg(' <'+data.from+'> '+data.message);
  169. }else{
  170. msg(' * '+data.message);
  171. }
  172. $o.abbrDate('abbr.date_'+time);
  173. }
  174. }
  175. ],
  176. socket,$i,$s,$h,$cl,$tl,hht;
  177. $.extend($o,{
  178. version: '3.0',
  179. abbrDate: function(selector){
  180. if(settings.timestamp == 'fuzzy'){
  181. $(selector).timeago();
  182. }else{
  183. var timestamp = settings.timestamp,
  184. i,
  185. text='',
  186. date = new Date($(selector).attr('title'));
  187. if(timestamp == 'exact'){
  188. timestamp = 'H:m:s t';
  189. }
  190. for(i=0;i<timestamp.length;i++){
  191. switch(timestamp[i]){
  192. case 'H':text+=((date.getHours()+11)%12)+1;break;
  193. case 'h':text+=date.getHours();break;
  194. case 'm':text+=date.getMinutes();break;
  195. case 's':text+=date.getSeconds();break;
  196. case 't':text+=(date.getHours()>11)?'pm':'am';break;
  197. default:text+=timestamp[i];
  198. }
  199. }
  200. $(selector).text(text);
  201. }
  202. },
  203. connect: function(server){
  204. if($o.connected()){
  205. socket.disconnect();
  206. socket = undefined;
  207. delete $o.socket;
  208. }
  209. if(typeof server == 'undefined'){
  210. server = settings.server;
  211. }
  212. $o.socket = socket = io.connect(server);
  213. for(var i in handles){
  214. socket.on(handles[i].on,handles[i].fn);
  215. }
  216. $o.auth();
  217. },
  218. auth: function(){
  219. socket.emit('auth',{
  220. nick: properties.nick
  221. // TODO - send authorization info
  222. });
  223. },
  224. connected: function(){
  225. return typeof socket != 'undefined';
  226. },
  227. get: function(name,formatted){
  228. if(typeof formatted == 'undefined'){
  229. return exists(settings[name])?settings[name]:false;
  230. }else{
  231. var val = $o.get(name),
  232. type;
  233. switch(name){
  234. case 'autojoin':
  235. type = 'Array';break;
  236. default:
  237. type = typeof val;
  238. }
  239. return {
  240. type: type,
  241. val: val
  242. };
  243. }
  244. },
  245. set: function(name,value){
  246. if(exists(settings[name])){
  247. settings[name] = value;
  248. $.localStorage('settings',JSON.stringify(settings));
  249. return true;
  250. }else{
  251. return false;
  252. }
  253. },
  254. renderSettings: function(){
  255. var name,setting;
  256. for(name in settings){
  257. setting = $o.get(name,true);
  258. }
  259. },
  260. prop: function(name){
  261. return exists(properties[name])?properties[name]:null;
  262. },
  263. send: function(msg){
  264. if(msg !== ''){
  265. if(msg[0] == '/'){
  266. var args = msg.split(' '),
  267. cmd = args[0].substr(1),
  268. i;
  269. event(msg,'command');
  270. for(i in commands){
  271. if(commands[i].cmd == cmd){
  272. commands[i].fn(args);
  273. return;
  274. }
  275. }
  276. $o.msg(cmd+' is not a valid command.');
  277. }else{
  278. event(msg,'send');
  279. socket.emit('message',{
  280. message: msg,
  281. room: tabs[selectedTab].name,
  282. from: properties.nick
  283. });
  284. }
  285. }
  286. },
  287. msg: function(msg,tabName){
  288. var frag;
  289. if(typeof tabName == 'undefined' || tabName == tabs[selectedTab].name){
  290. frag = document.createDocumentFragment();
  291. }else{
  292. frag = tabs[$o.tabIdForName(tabName)].body;
  293. }
  294. switch(typeof msg){
  295. case 'string':
  296. $(frag).append($('<li>').html(msg.htmlentities()));
  297. break;
  298. case 'object':
  299. if(typeof msg.html == 'undefined'){
  300. $(frag).append($('<li>').html('&lt;'+msg.user+'&gt;&nbsp;'+msg.text.htmlentities()));
  301. }else{
  302. $(frag).append(msg.html);
  303. }
  304. break;
  305. }
  306. $(tabs[$o.tabIdForName(tabName)].body).append(frag);
  307. if(typeof tabName == 'undefined' || tabName == tabs[selectedTab].name){
  308. $o.selectTab(selectedTab);
  309. }
  310. },
  311. event: function(event_name,message){
  312. event(message,event_name);
  313. },
  314. selectTab: function(id){
  315. event(id+' '+tabs[id].name,'tab_select');
  316. if(id<tabs.length-1&&id>=0){
  317. selectedTab=id;
  318. }
  319. $tl.children('.clicked').removeClass('clicked');
  320. $($tl.children().get(id)).addClass('clicked');
  321. $('#title').text(tabs[id].title);
  322. $('#topic').text(tabs[id].topic);
  323. $cl.html($(tabs[id].body).clone());
  324. $o.abbrDate('abbr.date');
  325. },
  326. tabIdForName: function(name){
  327. for(var i in tabs){
  328. if(tabs[i].name == name){
  329. return i;
  330. }
  331. }
  332. return false;
  333. },
  334. tabDOM: function(id){
  335. },
  336. addTab: function(name,title){
  337. event('Tab added: '+name+' with title '+title);
  338. if(!(function(){
  339. for(var i in tabs){
  340. if(name==tabs[i].name){
  341. return true;
  342. }
  343. }
  344. return false;
  345. })()){
  346. tabs.push({
  347. name: name,
  348. title: title,
  349. body: document.createDocumentFragment()
  350. });
  351. $tl.append($o.tabObj(tabs.length-1));
  352. $o.refreshTabs();
  353. }else{
  354. event('Attempted to add an existing tab');
  355. }
  356. },
  357. removeTab: function(id){
  358. if(typeof tabs[id] != 'undefined'){
  359. event('Tab removed: '+tabs[id].name);
  360. tabs.splice(id,1);
  361. if(selectedTab==id&&selectedTab>0){
  362. selectedTab--;
  363. }
  364. }
  365. $o.refreshTabs();
  366. $cl.html(tabs[selectedTab].body);
  367. },
  368. tabObj: function(id){
  369. if(typeof id !== 'undefined'){
  370. return $('<div>')
  371. .addClass('tab')
  372. .text(tabs[id].title)
  373. .mouseup(function(e){
  374. switch(e.which){
  375. case 1: // RMB
  376. if($(this).data('id')!=selectedTab){
  377. $o.selectTab($(this).data('id'));
  378. return prevent(e);
  379. }
  380. break;
  381. case 2: // MMB
  382. $(this).children('span.close-button').click();
  383. return prevent(e);
  384. break;
  385. case 3: // LMB
  386. return prevent(e);
  387. break;
  388. default:
  389. return prevent(e);
  390. }
  391. })
  392. .append(
  393. $('<span>')
  394. .addClass('close-button')
  395. .click(function(){
  396. $o.removeTab(id);
  397. return false;
  398. })
  399. .css({
  400. 'position': 'absolute',
  401. 'background-color': 'inherit',
  402. 'top': 0,
  403. 'right': 0
  404. })
  405. .html('&times;')
  406. )
  407. .data('id',id);
  408. }
  409. },
  410. refreshTabs: function(){
  411. $tl.html('');
  412. var i,tab;
  413. for(i in tabs){
  414. tab = $o.tabObj(i);
  415. if(i==selectedTab){
  416. tab.addClass('clicked');
  417. $('#title').text(tabs[i].title);
  418. $('#topic').text(tabs[i].topic);
  419. }
  420. $tl.append(tab);
  421. }
  422. if($tl.get(0).scrollHeight-20 != $tl.scrollTop()){
  423. $('#tabs-scroll-right').removeClass('disabled');
  424. }
  425. if($tl.scrollTop() != 0){
  426. $('#tabs-scroll-left').removeClass('disabled');
  427. }
  428. }
  429. });
  430. String.prototype.htmlentities = function(){
  431. return this.replace(/&/g, '&amp;').replace(/\s/g, '&nbsp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
  432. };
  433. $(document).ready(function(){
  434. $.extend(settings,$.parseJSON($.localStorage('settings')));
  435. $.localStorage('settings',JSON.stringify(settings));
  436. $i = $('#input');
  437. $s = $('#send');
  438. $cl = $('#content-list');
  439. $tl = $('#tabs-list');
  440. $h = $('#head');
  441. $s.click(function(){
  442. if(!$s.hasClass('clicked')){
  443. $s.addClass('clicked');
  444. setTimeout(function(){
  445. $s.removeClass('clicked');
  446. },500);
  447. }
  448. $o.send($i.val());
  449. $i.val('');
  450. });
  451. $i.keypress(function(e){
  452. if(e.keyCode == 13){
  453. if(!$s.hasClass('clicked')){
  454. $s.addClass('clicked');
  455. setTimeout(function(){
  456. $s.removeClass('clicked');
  457. },500);
  458. }
  459. $o.send($i.val());
  460. $i.val('');
  461. }
  462. });
  463. $('#settings, #users').click(function(){
  464. $(this).addClass('open');
  465. $(this).children('.close-button').show();
  466. }).hover(function(){
  467. $(this).addClass('hovered');
  468. },function(){
  469. $(this).removeClass('hovered');
  470. }).children('.close-button').click(function(){
  471. $(this).parent().removeClass('open');
  472. $(this).hide();
  473. return false;
  474. }).hide();
  475. $('#users').hoverIntent({
  476. out: function(){
  477. $(this).removeClass('open');
  478. $(this).children('.close-button').hide();
  479. },
  480. timeout: 1000
  481. });
  482. $('#content').click(function(){
  483. $('#settings, #users, #head').removeClass('hovered').removeClass('open');
  484. $('#settings, #users').children('.close-button').hide()
  485. });
  486. $('.unselectable').attr('unselectable','on');
  487. $.contextMenu({
  488. selector: 'div.tab',
  489. items: {
  490. add: {
  491. name: 'New Tab',
  492. icon: 'add',
  493. callback: function(){
  494. $(this).contextMenu('hide');
  495. var title = prompt('Title');
  496. tabs.push({
  497. name: prompt('channel'),
  498. title: title,
  499. topic: 'Topic for '+title
  500. });
  501. $o.refreshTabs();
  502. }
  503. },
  504. s1: '',
  505. close: {
  506. name: 'Close',
  507. icon: 'delete',
  508. callback: function(){
  509. $(this).contextMenu('hide');
  510. $o.removeTab($(this).data('id'));
  511. }
  512. }
  513. },
  514. zIndex: 99999,
  515. trigger: 'right'
  516. });
  517. $.contextMenu({
  518. selector: '#tabs-list',
  519. items: {
  520. add: {
  521. name: 'New Tab',
  522. icon: 'add',
  523. callback: function(){
  524. $(this).contextMenu('hide');
  525. var title = prompt('Title');
  526. tabs.push({
  527. name: prompt('channel'),
  528. title: title,
  529. topic: 'Topic for '+title
  530. });
  531. $o.refreshTabs();
  532. }
  533. }
  534. },
  535. zIndex: 99999,
  536. trigger: 'right'
  537. });
  538. $('#tabs-scroll-right').click(function(){
  539. event('scroll right');
  540. $tl.scrollTop(($tl.scrollTop()||0)+20);
  541. if($tl.get(0).scrollHeight-20 == $tl.scrollTop()){
  542. $('#tabs-scroll-right').addClass('disabled');
  543. }
  544. $('#tabs-scroll-left').removeClass('disabled');
  545. });
  546. $('#tabs-scroll-left').click(function(){
  547. event('scroll left');
  548. $tl.scrollTop(($tl.scrollTop()||0)-20);
  549. if($tl.scrollTop() == 0){
  550. $('#tabs-scroll-left').addClass('disabled');
  551. }
  552. $('#tabs-scroll-right').removeClass('disabled');
  553. });
  554. (function scrollup(){
  555. $('#tabs-scroll-left').click();
  556. if($tl.scrollTop() != 0){
  557. setTimeout(scrollup,10);
  558. }
  559. })();
  560. //DEBUG
  561. /* for(var i=0;i<20;i++){
  562. tabs.push({
  563. name: '#Tab'+i,
  564. title: 'Tab '+i,
  565. topic: 'Topic for tab '+i
  566. });
  567. } */
  568. //END DEBUG
  569. $o.refreshTabs();
  570. event('Date '+new Date,'ready');
  571. $h.addClass('hovered');
  572. setTimeout(function(){
  573. $h.removeClass('hovered');
  574. },1000);
  575. $o.connect();
  576. });
  577. })(window,jQuery,io);
  578. if (!Date.prototype.toISOString) {
  579. Date.prototype.toISOString = function() {
  580. function pad(n) { return n < 10 ? '0' + n : n }
  581. return this.getUTCFullYear() + '-'
  582. + pad(this.getUTCMonth() + 1) + '-'
  583. + pad(this.getUTCDate()) + 'T'
  584. + pad(this.getUTCHours()) + ':'
  585. + pad(this.getUTCMinutes()) + ':'
  586. + pad(this.getUTCSeconds()) + 'Z';
  587. };
  588. }