text: // Copyright 2006 Mihai Parparita. All Rights Reserved.
  
  // ==UserScript==
  // @name           Gmail + Google Reader
  // @namespace      http://persistent.info/
  // @description    Embeds Google Reader into Gmail by adding a "Feeds" link
  // @include        http://mail.google.com/*
  // @include        https://mail.google.com/*
  // @include        http://www.google.com/reader/*
  // ==/UserScript==
  
  var readerFrameNode = null;
  var feedsContainerNode = null;
  var hiddenNodes = [];
  
  const GMAIL_STYLES = [
    "#reader-frame {",
    "  width: 100%;",
    "  border: 0;",
    "}",
    
    ".reader-embedded #ft {",
      "display: none",
    "}",
    
    // Make currently selected item appear normal
    ".reader-embedded table.cv * {",
    "  background: #fff;",
    "  font-weight: normal",
    "}",
    
    // Make the feeds link appear selected
    ".reader-embedded #feeds-container {",
    "  background: #c3d9ff;",
    "  -moz-border-radius: 5px 0 0 5px;",
    "  font-weight: bold;",
    "  color: #00c;",
    "}",
  ].join("\n");
  
  const READER_STYLES = [
    "body {",
    "  background: #fff",  
    "}",
    
    "#nav,",
    "#logo-container,",
    "#global-info,",
    "#viewer-header {",
    "  display: none !important;",
    "}",
    "",
    "#main {",
    "  margin-top: 0;",
    "}",
    "",
    "#chrome {",
    "  margin-left: 0;",
    "}"
  ].join("\n");
  
  const READER_UNREAD_COUNT_URL = 
    "http://www.google.com/reader/api/0/unread-count?" +
    "all=true&output=json&client=gm";
  
  const READER_LIST_VIEW_URL =
    "http://www.google.com/reader/view/user/-/state/com.google/reading-list?" +
    "gmail-embed=true&view=list";
  
  if (document.location.hostname == "mail.google.com") {
    injectGmail();
  } else if (document.location.hostname == "www.google.com" &&
             document.location.search.indexOf("gmail-embed") != -1) {
    injectReader();
  }           
  
  function injectGmail() {
    if (!getNode("nds") || !getNode("ds_inbox")) return;
    
    GM_addStyle(GMAIL_STYLES);
    
    var feedsNode = newNode("span");
    feedsNode.className = "lk";
    feedsNode.innerHTML = 
        'Feeds ' +
        '<span id="reader-unread-count"></span>';
    feedsNode.onclick = showReaderFrame;
    
    feedsContainerNode = newNode("div");
    feedsContainerNode.className = "nl";
    feedsContainerNode.id = "feeds-container";
    feedsContainerNode.appendChild(feedsNode);
    
    var navNode = getNode("nds");
    
    navNode.insertBefore(feedsContainerNode, navNode.childNodes[2]);
    
    window.addEventListener("resize", resizeReaderFrame, false);
    
    updateUnreadCount();
    
    window.setInterval(updateUnreadCount, 5 * 60 * 1000); 
    
    window.setInterval(checkFeedsParent, 1000);
  }
  
  function checkFeedsParent() {
    var navNode = getNode("nds");
    
    if (feedsContainerNode.parentNode != navNode) {
      navNode.insertBefore(feedsContainerNode, navNode.childNodes[2]);
    }  
  }
  
  function updateUnreadCount() {
    var unreadCountNode = getNode("reader-unread-count");
    
    if (!unreadCountNode) return;
    
    GM_xmlhttpRequest({
      method: "GET",
      url: READER_UNREAD_COUNT_URL,
      onload: function(details) {
        var data = eval("(" + details.responseText + ")");
        var isUnread = false;
        
        for (var i = 0, unreadCountPair; 
             unreadCountPair = data.unreadcounts[i]; 
             i++) {
          if (unreadCountPair.id.indexOf("reading-list") != -1) {
            var count = unreadCountPair.count;
            if (count == 0) break;
            
            unreadCountNode.innerHTML = 
                " (" + count + (count == data.max ? "+" : "") + ") ";
            isUnread = true;
            break;
          }
        }
        
        if (!isUnread) {
          unreadCountNode.innerHTML = "";
        }
      }
    });
  }
  
  function resizeReaderFrame() {
    if (!readerFrameNode) return;
    
    readerFrameNode.style.height = 
        (window.innerHeight - readerFrameNode.offsetTop) + "px";  
  }
  
  function showReaderFrame(event) {
    var container = getNode("co");
    
    addClass(document.body, "reader-embedded");
    
    hiddenNodes = [];
    
    for (var i = container.firstChild; i; i = i.nextSibling) {
      hiddenNodes.push(i);
      i.style.display = "none";
    }
    
    readerFrameNode = newNode("iframe");
    readerFrameNode.src = READER_LIST_VIEW_URL;
    readerFrameNode.id = "reader-frame";
    
    container.appendChild(readerFrameNode);
    
    container.parentNode.style.paddingRight = "0";
    container.parentNode.style.paddingBottom = "0";
    
    resizeReaderFrame();
    
    // Make clicks outside the content area hide it  
    getNode("nav").addEventListener("click", hideReaderFrame, false);
    
    // Since we're in a child of the "nav" element, the above handler will get
    // triggered immediately unless we stop this event from propagating
    event.stopPropagation();  
    
    return false;
  }
  
  function hideReaderFrame() {
    var container = getNode("co");
  
    container.removeChild(readerFrameNode);    
    readerFrameNode = null;
    
    for (var i=0; i < hiddenNodes.length; i++) {
      hiddenNodes[i].style.display = "";
    }
    getNode("nav").removeEventListener("click", hideReaderFrame, false);  
                                       
    removeClass(document.body, "reader-embedded");      
    
    container.parentNode.style.paddingRight = "1ex";
    container.parentNode.style.paddingBottom = "1ex";
    
    return true;
  }
  
  function injectReader() {
    GM_addStyle(READER_STYLES);  
  }
  
  // Shorthand
  function newNode(type) {return unsafeWindow.document.createElement(type);}
  function newText(text) {return unsafeWindow.document.createTextNode(text);}
  function getNode(id) {return unsafeWindow.document.getElementById(id);}
  
  function hasClass(node, className) {
    return className in getClassMap(node);
  }
  
  function addClass(node, className) {
    if (hasClass(node, className)) return;
    
    node.className += " " + className;
  }
  
  function removeClass(node, className) {
    var classMap = getClassMap(node);
  
    if (!(className in classMap)) return;
    
    delete classMap[className];
    var newClassList = [];
    
    for (var className in classMap) {
      newClassList.push(className);
    }
    
    node.className = newClassList.join(" ");
  }
  
  function getClassMap(node) {
    var classMap = {};
    var classNames = node.className.split(/\s+/);
    
    for (var i = 0; i < classNames.length; i++) {
      classMap[classNames[i]] = true;
    }
    
    return classMap;
  }