Home | History | Annotate | Download | only in servlet
      1 <html>
      2   <head>
      3     <title>ChromiumIRC</title>
      4     <link rel="stylesheet" type="text/css" href="styles.css"> 
      5     <script src="jstemplate/util.js" type="text/javascript"></script>
      6     <script src="jstemplate/jsevalcontext.js" type="text/javascript"></script>
      7     <script src="jstemplate/jstemplate.js" type="text/javascript"></script> 
      8     <script src="util.js" type="text/javascript"></script>
      9     <script lang="JavaScript" src="irc.js"></script>
     10     <script>
     11 
     12 var ircConnections = {};
     13 
     14 // The server & channel configutation data is stored in localStorage.servers.
     15 // These are setters and getters for this structure.
     16 function servers() {
     17   return JSON.parse(localStorage.servers || "[]");
     18 }
     19 function setServers(servers) {
     20   localStorage.servers = JSON.stringify(servers);
     21 }
     22 
     23 // Channel list is a sorted list of "server#channel" strings. This maps to
     24 // channel slides as represented in the UI.
     25 function channelList() {
     26   var channelList = [];
     27   servers().forEach(function(server) {
     28     server.channels = server.channels || [];
     29     server.channels.forEach(function(channel) {
     30       channelList.push(server.name + channel);
     31     });
     32   });
     33 
     34   channelList.sort();
     35   return channelList;
     36 }
     37 
     38 window.onload = function() {
     39   // Setup notifications.
     40   window.onfocus = function() {
     41     windowHasFocus = true;
     42     clearNotifications();
     43   }
     44   window.onblur = function() {
     45     windowHasFocus = false;
     46   }
     47 
     48   syncChannelList();
     49 
     50   // Setup channel navigation and message entry.
     51   function handleBodyKeyDown(event) {
     52     switch (event.keyCode) {
     53       case 37: // left arrow
     54         slideTo(-1);
     55         break;
     56       case 39: // right arrow
     57         slideTo(1);
     58         break;
     59     }
     60   }
     61   document.body.addEventListener('keydown', handleBodyKeyDown, false);
     62 
     63   // We don't want left & right arrow inside the text entry to move the channel
     64   // slides.
     65   $('typingDiv').addEventListener('keydown', function(event) {
     66     event.stopPropagation();
     67   });
     68   $('entryText').addEventListener('keydown', function(event) {
     69     if (event.keyCode == 13) { // RETURN key.
     70       processEntryMessage();
     71     }
     72   });
     73 
     74   servers().forEach(addServerConnection);
     75 };
     76 
     77 window.onunload = function() {
     78   ircConnections.forEach(function(irc) {
     79     irc.disconnect();  
     80   });
     81 }
     82 
     83 function addServerConnection(server) {
     84   var ws = new WebSocket("ws://" + location.host + "/ws");
     85   var irc = new IRCConnection(server.name, server.port, server.nick,
     86                               ws.send.bind(ws), // sendFunc
     87                               ws.close.bind(ws)); // closeFunc
     88   ws.onopen = irc.onOpened.bind(irc);
     89   ws.onclose = irc.onClosed.bind(irc);
     90   ws.onmessage = function(message) {
     91     irc.onMessage(message.data);  
     92   };
     93   irc.onConnect = function(message) {
     94     server.channels.forEach(function(channel) {
     95       ircConnections[server.name].joinChannel(channel);
     96     });
     97   };
     98   irc.onDisconnect = function(message) {
     99   };
    100   irc.onText = function(channel, nick, message) {
    101     checkForNickReference(server, channel, nick, message);
    102     addMessage(server.name, channel, nick, new Date(), message);
    103   };
    104 
    105   ircConnections[server.name] = irc;
    106 }
    107 
    108 function joinChannel(serverName, channelName) {
    109   ircConnections[serverName].joinChannel(channelName);
    110 }
    111 
    112 function removeChannelListener(channelName) {
    113   return function(event) {
    114     event.stopPropagation();
    115     
    116     var servers = servers();
    117     servers.forEach(function(server) {
    118       if (channelName.indexOf(server.name) == 0) {
    119         for (var i = 0; server.channels.length; i++) {
    120           if (channelName == server.name + server.channels[i]) {
    121             ircConnections[server.name].quitChannel(server.channels[i]);
    122             server.channels.splice(i, 1);
    123             break;
    124           }
    125         }
    126       }
    127     });
    128 
    129     setServers(servers);
    130     syncChannelList();
    131   };
    132 }
    133 
    134 function syncChannelList() {
    135   var channels = channelList();
    136   var channelSlides = $('channelSlides');
    137   var channelSlideProto = $('channelSlideProto');
    138 
    139   var channelIndex = 0;
    140   var slideIndex = 0;
    141 
    142   while(channelIndex < channels.length || 
    143         channels.length != channelSlides.children.length) {
    144     var channel = channels[channelIndex];
    145     var slide = channelSlides.children[slideIndex];
    146 
    147     if (slideIndex == channelSlides.children.length ||
    148         channel < slideChannel(slide)) {
    149       // Add a new slide.
    150       var newSlide = channelSlideProto.cloneNode(true);
    151       jstProcess(new JsEvalContext({ name: channel }), newSlide);
    152       newSlide.setAttribute("id", "channel-" + channel);
    153       newSlide.style.display = "";
    154       if (slideIndex == channelSlides.children.length) {
    155         channelSlides.appendChild(newSlide);
    156       } else {
    157         channelSlides.insertBefore(newSlide, slide);
    158       }
    159       newSlide.addEventListener('click', onClickMoveSlide);
    160       childNodeWithClass(newSlide, "removeButton")
    161           .addEventListener('click', removeChannelListener(channel));
    162       
    163       slide = newSlide;
    164     } else if (!channel || channel > slideChannel(slide)) {
    165       // Delete a removed slide.
    166       
    167       // If the removed slide is the current slide, we have to pick a new
    168       // current slide.
    169       if (localStorage.currentSlide == slideChannel(slide)) {
    170         if (slide.nextSibling) {
    171           localStorage.currentSlide = slideChannel(slide.nextSibling);
    172         } else if (channels.length == 0) {
    173           localStorage.currentSlide = "";
    174         } else if (slideIndex < channelSlides.children.length) {
    175           localStorage.currentSlide =
    176               slideChannel(channelSlides.children[slideIndex - 1]);
    177         }
    178       }
    179       channelSlides.removeChild(slide);  
    180     } else {
    181       channelIndex++;
    182       slideIndex++;
    183     }
    184 
    185     slide.setAttribute("slide", "" + slideIndex - 1);
    186   }
    187 
    188   slideTo();
    189 }
    190 
    191 function processEntryMessage() {
    192   var message = $('entryText').value;
    193   $('entryText').value = "";
    194 
    195   if (!localStorage.currentSlide) {
    196     alert('No current channel');
    197     return;
    198   }
    199   
    200   var server;
    201   var channel;
    202   var nick;
    203   servers().forEach(function(s) {
    204     if (localStorage.currentSlide.indexOf(s.name) == 0) {
    205       server = s.name;
    206       nick = s.nick;
    207       s.channels.forEach(function(c) {
    208         if (localStorage.currentSlide == s.name + c) {
    209           channel = c;
    210         }
    211       });
    212     }
    213   });
    214 
    215   addMessage(server, channel, nick, new Date(), message);
    216   ircConnections[server].sendMessage([channel], message);
    217 }
    218 
    219 function addMessage(server, channel, nick, time, body) {
    220   messageLine = childNodeWithClass($('channelSlideProto'), "messageLine");
    221   var newMessageLine = messageLine.cloneNode(true);
    222 
    223   jstProcess(new JsEvalContext({ 
    224     'nick': nick,
    225     'time': time,
    226     'body': body
    227   }), newMessageLine);
    228   newMessageLine.style.display = "";
    229 
    230   var messageList =
    231       childNodeWithClass($("channel-" + server + channel), "messageList");
    232   messageList.appendChild(newMessageLine);
    233 }
    234 
    235 function formatTime(time) {
    236   return "";
    237 }
    238 
    239 /**
    240  * Slide Navigation. 
    241  */
    242  
    243 // Returns the server#channel string value for a given |slide| element.
    244 function slideChannel(slide) {
    245   return childNodeWithClass(slide, "channel").innerText;
    246 }
    247 
    248 // Handler for clicking on the visible portions of the previous & next slides.
    249 function onClickMoveSlide() {
    250   if (localStorage.currentSlide != slideChannel(this)) {
    251     localStorage.currentSlide = slideChannel(this);
    252     slideTo();
    253   }  
    254 }
    255 
    256 // Handles navigating between the channel slides. If |slideDelta| is given,
    257 // it should specify the number of slides to move left (negative value) or right
    258 // positive value. If |slideDelta| is not provided, It ensures that
    259 // |localStorage.currentSlide| is navigated to.
    260 function slideTo(slideDelta) {
    261   var slide;
    262   var slideNumber;
    263 
    264   if (localStorage.currentSlide) {
    265     slide = document.getElementById("channel-" + localStorage.currentSlide);
    266     if (slide) {
    267       slideNumber = parseInt(slide.getAttribute("slide"));
    268     }
    269   }
    270   if (isNaN(slideNumber) || !slide) {
    271     slideNumber = 0;
    272   }
    273   if (typeof(slideDelta) == "number") {
    274     slideNumber += slideDelta;
    275   }
    276 
    277   var slides = document.getElementsByClassName("channelSlide");
    278   if (slideNumber < 0 || slideNumber == slides.length - 1) {
    279     return;
    280   }
    281 
    282   for (var i = 0; i < slides.length; i++) {
    283     var slide = slides[i];
    284     var slideIndex = parseInt(slide.getAttribute("slide")) - slideNumber;
    285     
    286     if (slideIndex <= -2) {
    287       slide.className = "channelSlide far-left";
    288     }
    289     if (slideIndex >= 2) {
    290       slide.className = "channelSlide far-right";
    291     }
    292     
    293     switch(slideIndex) {
    294       case -1:
    295         slide.className = "channelSlide left";
    296         break;
    297       case 0:
    298         slide.className = "channelSlide center";
    299         localStorage.currentSlide = slideChannel(slide);
    300         break;
    301       case 1:
    302         slide.className = "channelSlide right";
    303         break;
    304     }
    305   }
    306 
    307   clearNotifications();
    308 }
    309 
    310 /**
    311  * Notifications
    312  */
    313 var windowHasFocus = false;
    314 var notifications = {};
    315 
    316 function clearNotifications() {
    317   for (property in notifications) {
    318     notifications[property].cancel();
    319   }
    320 
    321   notifications = {};
    322 }
    323 
    324 function checkForNickReference(server, channel, nick, message) {
    325   if (windowHasFocus || !message || message.indexOf(server.nick) < 0) {
    326     return;
    327   }
    328 
    329   // Notifications will be enabled by the app install. Otherwise, don't notity.
    330   if (window.webkitNotifications.checkPermission() != 0) {
    331     return;
    332   }
    333   
    334   // Remove a previous notification from the same channel. Show the newer one.
    335   if (notifications[server.name + channel]) {
    336     notifications[server.name + channel].cancel();
    337   }
    338 
    339   var title = "On " + server.name + channel;
    340   var icon = "http://www.google.com/favicon.ico";
    341   var text = nick + ": " + message;
    342   var url = location.protocol + "//" + location.host + "/notification.html";
    343   url += "?title=" + encodeURIComponent(title) +
    344          "&content=" + encodeURIComponent(text);
    345 
    346   var n = window.webkitNotifications.createHTMLNotification(url);
    347   n.ondisplay = function() {};
    348   n.onclose = function() {
    349     delete notifications[server.name + channel];
    350   };
    351   n.show();
    352 
    353   notifications[server.name + channel] = n;
    354 }
    355     
    356   
    357   
    358     
    359     
    381     
    382     
    411   
    412