omnomirc.js 14 KB

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