Source for "Gmail + Reader Integrator"

By jesses
Has no other scripts.


Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)

////////////////////////////////////////////////////////
// Gmail and Google Reader Integrator v3.2
// By: Jesse Stockall
// Based on earlier scripts by Nick Chirchirillo, Pradeep Chandiramani Winston Teo and Mihai Parparita (See infomation below)
////////////////////////////////////////////////////////

////////////////////////////////////////////////////////
// Changelog
// v3.4 - 2008-01-26  Fix breakage with Firefox 3.0 beta2
//                    Fix breakage with Greasemonkey 0.7.20080121.0
//                    Move the feedlist right under the mail controls
//                    Hide the embedded reader when the thread list is not displayed
// v3.3 - 2007-11-20  Update the unread count everytime the reader frame is reloaded
//                    Default to folders being displayed expanded
//                    Escape the URL so feeds with parameters (Yahoo pipes) are handled
//                    Preferences can be overridden in via about:config (filter by 'reader')
// v3.2 - 2007-11-18  Feeds not in folders are created at the top level
//                    Prevent the script from executing on embedded iframes
//                    Add the unread count back to the window title
// v3.1 - 2007-11-17  Remove all usage of unsafeWindow
//                    Fix unread indicator
// v3.0 - 2007-11-17  Compatable with new GMail interface using GmailGreasemonkey10API.
//                    Refactored script to remove unused code streamline some logic.                   
//                    Replaced lengthy changelogs from previous versions with links to the script source

////////////////////////////////////////////////////////
// Earlier Versions
//
// Gmail and Google Reader Integrator v2.3
// Splatypus
// Updates to fix rendering problem, posted at http://userscripts.org/scripts/show/8450
//
// Gmail and Google Reader Integrator v2.2
// Nick Chirchirillo
// http://userscripts.org/scripts/show/8450
//
// Gmail and Google Reader Integrator v2.1
// Pradeep Chandiramani (http://chandiramani.info)
// http://userscripts.org/scripts/show/7435
//
// Gmail and Google Reader Integrator v2.0
// Winston Teo
// http://www.winstonyw.com/2006/11/03/greasemonkey-script-gmail-and-reader-integrator/
//
// Gmail and Google Reader Integrator v1.0
// Mihai Parparita 
// http://persistent.info/archives/2006/10/13/google-reader-redux

//////////////////////////////////////////////////
// ==UserScript==
// @name          Gmail + Reader Integrator
// @namespace     http://persistent.info/
// @description   Integrates Google Reader into Gmail
// @include       http://mail.google.com/*
// @include       https://mail.google.com/*
// @include       http://www.google.com/reader/*
// ==/UserScript==

//Default Heights (in pixels) in Split-Window mode
//Change these according to your preference
const READER_HEIGHT  = 470;
const GMAIL_HEIGHT   = 320;
const EXPAND_FOLDERS = 1; // 1 = expanded 0 = collapsed 

//////////////////////////////////////////////////
//Setting of DEFAULTS
//////////////////////////////////////////////////
//Note: All Links have to end with ?embed
//		In this way, only the Reader view embedded in Gmail would be affected by the style changes,
//		and not the Reader view even at its own URL: www.google.com/reader/view
const READER_HOME_URL			= 'http://www.google.com/reader/view?embed';
const READER_LIST_URL			= 'http://www.google.com/reader/view/user/-/state/com.google/reading-list?embed';
const READER_STARRED_URL		= 'http://www.google.com/reader/view/user/-/state/com.google/starred?embed';
const READER_BROADCAST_URL		= 'http://www.google.com/reader/view/user/-/state/com.google/broadcast?embed';
const READER_LABEL_URL			= 'http://www.google.com/reader/view/user/-/label/' ;
const READER_FEED_URL			= 'http://www.google.com/reader/view/'
const UNREAD_COUNT_API			= 'http://www.google.com/reader/api/0/unread-count?all=true&output=json&client=gm';
const PREFERENCES_API			= 'http://www.google.com/reader/api/0/preference/list?output=json&client=gm';
const LABELS_LIST_API			= 'http://www.google.com/reader/api/0/tag/list?output=json&client=gm';
const SUBSCRIPTION_LIST_API		= 'http://www.google.com/reader/api/0/subscription/list?output=json&client=gm';


//Images for the subscription panel
const IMAGE_FOLDER_CLOSED		= 'http://www.google.com/reader/ui/1891922333-tree-view-folder-closed.gif';
const IMAGE_FOLDER_OPEN			= 'http://www.google.com/reader/ui/3544433079-tree-view-folder-open.gif';
const IMAGE_PLUS				= 'http://www.google.com/reader/ui/2212714735-tree-view-plus.gif';
const IMAGE_MINUS				= 'http://www.google.com/reader/ui/1027627478-tree-view-minus.gif';
const IMAGE_FEED				= 'http://www.google.com/reader/ui/4183653108-tree-view-subscription.gif';
const IMAGE_SHARED				= 'http://www.google.com/reader/ui/3178074291-icon-broadcast.png';
const IMAGE_STARRED				= 'http://www.google.com/reader/ui/575937951-star_active.png';
const IMAGE_ALL					= 'http://www.google.com/reader/ui/1754173018-icon-reading-list.gif';
const IMAGE_HOME				= 'http://www.google.com/reader/ui/3690227137-icon-overview.gif';
const IMAGE_LAUNCH				= 'http://mail.google.com/mail/images/tearoff_icon.gif';

// Keys used for storing preferences
const PREFS_READER_HEIGHT     = "readerHeight";
const PREFS_MAIL_HEIGHT_ORIG  = "mailHeightOriginal";
const PREFS_MAIL_HEIGHT_SPLIT = "mailHeightSplit";
const PREFS_READER_URL        = "READER_SOURCE_URL";
const PREFS_EXPAND_FOLDERS    = "expandFolders";


//Local Storage for ALL Labels as retrieved from Reader
//Note: GM_setValue only allows for integers, strings and booleans
var LABELS = [];

//Local Storage for all feeds 
var FEEDS = [];

//Feed object
function feed()
{
	this.title="";
	this.url="";
	this.categories=[];
}

//////////////////////////////////////////////////
//CSS Styles
//////////////////////////////////////////////////

//Styles for the Reader Frame
const READER_FRAME_STYLES =
    "#readerFrame { width: 100%; border: 0;}" +
    "#readerSpacer { height: 10px; margin: 0 -3px; background-color: white; }" + 
    "#readerLink { padding: 2px 0 0 2px; font-size: 80%; }";

// Styles for the Reader Navbox
const READER_NAVBOX_STYLES =
    " .folderFeeds { margin:0; padding: 0 0 0 15px; list-style-type:none; }" +
    " .readerCatList { margin: 0; width: 98%; padding: 0; list-style-type:none; overflow:hidden; white-space: nowrap; }" +
    " .folderName { list-style-type: none; overflow: hidden; white-space: nowrap; font-size: 12.8px; font-family: arial,sans-serif; color: #0000CC; }"+
    " .folderName img { padding: 0 3px; }"+
    " .readerFeedItem { overflow: hidden; white-space: nowrap; margin: 0; padding: 0; }"+
    " .readerFeedItem * { white-space: nowrap; font-size: 12.8px; font-family: arial,sans-serif; color: #0000CC; }"+
    " .readerFeedItem img { margin-right: 3px; vertical-align: middle; display: inline; }";

//Styles for Website (www.google.com/reader...?embed) contained in iFrame
// Removes Logo, Navigation, Menu + Displays Main Window         
const FORMAT_READER_STYLES =
    "#nav," +
    "#logo-container," +
    "#global-info," +
    "#viewer-header," +
    "#home-header," +
    ".home-header-box," +
    "#recent-activity," +   
    "#gbar, #gbh, " +
    "#nav-toggler," +
    "#footer," +
    "#search { display: none !important; }"+
    "body { background: #FFFFFF; }" +
    "#main { margin-top: 0; }" +
    "#chrome { margin-left: 0; padding-top: 0; }";

////////////////////////////////////////////////////////////////////////////////////////////////////
//Start Execution of Script
////////////////////////////////////////////////////////////////////////////////////////////////////

var gmail = null;
var navbox = null;

// Hook in using the GmailGreasemonkey10API   
// http://code.google.com/p/gmail-greasemonkey/wiki/GmailGreasemonkey10API
window.addEventListener('load', function() {
    if (unsafeWindow.gmonkey && !gmail) {
        unsafeWindow.gmonkey.load('1.0', function(g) {
            gmail = g;

            navbox = gmail.addNavModule('Reader');

            var navboxElement = navbox.getElement();
            var navboxParent = navboxElement.parentNode;
            var mailBox = navboxParent.firstChild.nextSibling.nextSibling.nextSibling;
            navboxElement.style.paddingTop = '10px';
            navboxParent.insertBefore(navboxElement, mailBox);

            window.setTimeout(startReader, 0); 

            gmail.registerViewChangeCallback(toggleViews);
            window.setInterval(updateUnreadCounts, 30 * 1000); 
        });
    }
}, true);


// Detects Google Reader (in iFrame) and updates CSS in Google Reader to hide specific elements
//
//Note: Header MUST contain: @include http://www.google.com/reader/*
//      Then, http://www.google.com/reader/* will include a copy of JS, and only execute this conditional clause.
//Note: document.location.search returns ONLY QUERY STRING.. ?embed
if (document.location.hostname == 'www.google.com' && document.location.search.indexOf('embed') != -1) {
    GM_addStyle(FORMAT_READER_STYLES);
}

// Greasemonkey 0.7.20080121.0 no longer allows certain functions 
// to be called by unsafeWindow. See http://wiki.greasespot.net/0.7.20080121.0_compatibility
function startReader() {
    storeHeight();
    loadReaderLabels();
    setReaderSourceURL();
    createReaderDiv();
    toggleViews();
}

// Only show the reader navbox and embedded reader when the thread list (messages) is active
function toggleViews() {
  var reader = gmail.getActiveViewElement().ownerDocument.getElementById('readerEmbed');
  switch (gmail.getActiveViewType()) {
    case 'tl': 
        navbox.getElement().style.setProperty('display', 'block', "important"); 
        reader.style.display = 'block';
        break;
    case 'cv':
    case 'co':
    case 'ct':
    case 's':
    default: 
        navbox.getElement().style.setProperty('display', 'none', "important");
        reader.style.display = 'none';

  }
}

//Description: Creates a div that contains a link to show/hide Google Reader embedded in an iframe.
function  createReaderDiv() {
    var doc = gmail.getActiveViewElement().ownerDocument;

    // CSS needs to be applied to the iframe we live in so GM_addStyle can't be used
    var readerFrameCSS = doc.createElement('style');
    readerFrameCSS.innerHTML = READER_FRAME_STYLES;
    doc.documentElement.appendChild(readerFrameCSS);

    var readerEmbed            = doc.createElement('div');
    readerEmbed.id             = 'readerEmbed';

    // used to create the illusion of separation between the messages and the embedded reader
    var readerSpacer           = doc.createElement('div');
    readerSpacer.id            = 'readerSpacer';
    readerEmbed.appendChild(readerSpacer);
       
    var readerLink             = doc.createElement('div');
    readerLink.id              = 'readerLink';
    readerLink.innerHTML       = '<img id="readerLinkImg" src="images/triangle.gif"></img> Google Reader';
    readerLink.addEventListener ('click', toggleReader, false);
                 
    readerEmbed.appendChild(readerLink);
    gmail.getActiveViewElement().parentNode.appendChild(readerEmbed);
}

// Builds the contents of the left hand navbox
function populateFeedList() {
  // script and css need to be applied to the iframe we live in
  var doc = navbox.getElement().ownerDocument;
  var head = doc.documentElement;
  var collapseScript = doc.createElement('script');
  collapseScript.innerHTML =	'function toggleList(label){\n'+
              								' var IMAGE_MINUS ="'+IMAGE_MINUS+'";\n'+
              								'	var IMAGE_PLUS  = "'+IMAGE_PLUS+'";\n'+
              								' var listElementStyle=document.getElementById(label+"FeedsList").style; \n'+
              								' if (listElementStyle.display=="none"){\n'+
              								'   listElementStyle.display="block"; \n'+
              								'   document.getElementById(label+"Image").src=IMAGE_MINUS; \n'+
              								'   document.getElementById(label+"Image").alt="Close feeds"; \n'+
              								' }else{\n'+
              								'   listElementStyle.display="none"; \n'+
              								'   document.getElementById(label+"Image").src=IMAGE_PLUS; \n'+
              								'   document.getElementById(label+"Image").alt="Open feeds"; \n'+
              								' }}';
  head.appendChild(collapseScript);

  var navboxCSS = doc.createElement('style');
  navboxCSS.innerHTML = READER_NAVBOX_STYLES;
  head.appendChild(navboxCSS);

  var readerFeeds		    = doc.createElement('div');
  readerFeeds.className		= 'readerFeedList';
  
  // Store is back in the prefs api using the default if it was not set  
  var expand = GM_getValue(PREFS_EXPAND_FOLDERS, EXPAND_FOLDERS);
  GM_setValue(PREFS_EXPAND_FOLDERS, expand);

  LABELS.sort();	
  for (var i = 0 ; i<LABELS.length ; i++) {
    if('uc76528q' == LABELS[i]) {
      // feeds that are not in any folders
      for (var j = 0 ; j<FEEDS.length ; j++) {
        if(FEEDS[j].categories.indexOf(LABELS[i])!=-1){
          readerFeeds.appendChild(buildFeedItem(FEEDS[j].url, FEEDS[j].title));
        }
      }
    } else {
      var folderName		= doc.createElement('li');
      folderName.className	= 'folderName';
      folderName.innerHTML  = '<img id="'+LABELS[i]+'Image" src="'+ (expand ? IMAGE_MINUS : IMAGE_PLUS) +
                              '" alt="Open feeds" onClick="toggleList(\''+LABELS[i]+'\');">';

      var folderLink        = doc.createElement('a');
      folderLink.href       = 'javascript:void(0)';
      folderLink.innerHTML  = LABELS[i];
      folderLink.title      = LABELS[i];
      folderLink.id         = 'rfl'+ LABELS[i];
      folderLink.setAttribute('URL', READER_LABEL_URL + LABELS[i] + '?embed');
      folderLink.addEventListener('click', switchView, false); 
      folderName.appendChild(folderLink);

      var folderFeeds			  = doc.createElement('li');
      folderFeeds.className		  = 'folderFeeds';
      folderFeeds.id			  = LABELS[i]+'FeedsList';
      
      folderFeeds.style.display	= expand ? 'block' : 'none';

      for (var j = 0 ; j<FEEDS.length ; j++) {
        if(FEEDS[j].categories.indexOf(LABELS[i])!=-1){
          folderFeeds.appendChild(buildFeedItem(FEEDS[j].url, FEEDS[j].title));
        }
      }
      if (folderFeeds.childNodes.length > 0) {
        readerFeeds.appendChild(folderName);
        readerFeeds.appendChild(folderFeeds);
      }
    }
  }
  //var node = navbox.getElement().ownerDocument.importNode(readerFeeds, true);
  //navbox.appendChild(node);
  navbox.appendChild(readerFeeds);
  updateUnreadCounts();
} 

// Builds a <li> element for each feed
function buildFeedItem(url, title) {
	var feedItem			= navbox.getElement().ownerDocument.createElement('li');
	feedItem.className		= 'readerFeedItem';
	feedItem.innerHTML		= '<img src="'+IMAGE_FEED+'" alt="Feed">';
	feedItem.innerHTML		+='<a href="javascript:void(0)" id="'+url+'" title="'+title+'">'+title+'</a>';
	feedItem.setAttribute('URL', READER_FEED_URL + escape(url) + '?embed');
	feedItem.addEventListener('click', switchView, false); 
	return feedItem;
}

//Method Name: switchView(event)
//Description: Switches Reader view in iFrame according to selection in Reader Labels Selection
// calls updateUnreadCounts so the read/unread indicators are correct
function switchView(event) {
  updateUnreadCounts();
  GM_setValue(PREFS_READER_URL, this.getAttribute('URL'));   
  displayReaderFrame();
  event.stopPropagation();   
}

// Toggles Reader Frame visibility 
function toggleReader(event) {

  // check to see if the frame exists
  var doc = gmail.getActiveViewElement().ownerDocument;
  var readerFrame = doc.getElementById('readerFrame');
  if (readerFrame == null) {
    displayReaderFrame();
  } else {
    // Reader has been toggled off
    doc.getElementById('readerLinkImg').src = 'images/triangle.gif';

    // remove the iframe 
    doc.getElementById('readerEmbed').removeChild(readerFrame);
    readerFrame = null;

    // restore the height of the the message view full size again
    var elementStyle = gmail.getActiveViewElement().firstChild.style;
    elementStyle.setProperty('height', GM_getValue(PREFS_MAIL_HEIGHT_ORIG) + 'px', 'important');
    elementStyle.overflow = 'auto';
    elementStyle.marginBottom = '0';
  }
  event.stopPropagation();
}

// Creates a vew frame or return the existing one
// Populates the iframe with the saved url
// Resizes the GMail thread list with the defined split height
function displayReaderFrame() {

  var doc = gmail.getActiveViewElement().ownerDocument;
  var readerFrame = doc.getElementById('readerFrame');
  if (readerFrame == null) {
    readerFrame      = doc.createElement('iframe');
    readerFrame.id   = 'readerFrame';
    readerFrame.name = 'readerFrame';
    
    doc.getElementById('readerLinkImg').src = 'images/opentriangle.gif';
    doc.getElementById('readerEmbed').appendChild(readerFrame);

    // shrink the messages view
    var elementStyle = gmail.getActiveViewElement().firstChild.style;
    elementStyle.setProperty('height', GM_getValue(PREFS_MAIL_HEIGHT_SPLIT) + 'px', 'important');
    elementStyle.overflow = 'auto';
    elementStyle.marginBottom = '5px';
  }
  readerFrame.style.setProperty('height', GM_getValue(PREFS_READER_HEIGHT) + 'px', 'important');  
  readerFrame.src = GM_getValue(PREFS_READER_URL);
}

// Determines Height for Gmail Thread List and Reader Frame and stores it via preferences api
function storeHeight() {

  // Store the height in the prefs api using the default if it was not set
  GM_setValue(PREFS_READER_HEIGHT, GM_getValue(PREFS_READER_HEIGHT, READER_HEIGHT));

  // Store the height in the prefs api using the default or the full height 
  // if the specified size is larger than full size
  var origHeight        = gmail.getActiveViewElement().firstChild.clientHeight;
  var splitHeight       = GM_getValue(PREFS_MAIL_HEIGHT_SPLIT, GMAIL_HEIGHT);
  GM_setValue(PREFS_MAIL_HEIGHT_ORIG, origHeight);
  GM_setValue(PREFS_MAIL_HEIGHT_SPLIT, origHeight < splitHeight ? origHeight : splitHeight);  
}

// Retrieves Reader's settings page through API @ PREFERENCES_API, and uses value for start-page, 
// as set by User in Google Reader, as start-page for viewReader() method call
function setReaderSourceURL() {
   
  GM_xmlhttpRequest({
    method: 'GET',
    url: PREFERENCES_API,
    onload: function(responseDetails) {

      var data  =  eval ("(" + responseDetails.responseText + ")");
      for (var i = 0 ; i<data.prefs.length ; i++) {
        var prefsPair = data.prefs[i]
        if (prefsPair.id.indexOf("start-page") != -1) {
          if (prefsPair.value.indexOf('home') != -1) {
            GM_setValue(PREFS_READER_URL, READER_HOME_URL);
          } else if (prefsPair.value.indexOf('reading-list') != -1) {
            GM_setValue(PREFS_READER_URL, READER_LIST_URL);
          } else if (prefsPair.value.indexOf('starred') != -1) {
            GM_setValue(PREFS_READER_URL, READER_STARRED_URL);                           
          } else if (prefsPair.value.indexOf('broadcast') != -1) {
            GM_setValue(PREFS_READER_URL, READER_BROADCAST_URL);
          } else if (prefsPair.value.indexOf('label') != -1) {
              var label = prefsPair.value.substring(prefsPair.value.lastIndexOf('/')+1, prefsPair.value.length);
              GM_setValue(PREFS_READER_URL, READER_LABEL_URL + label + '?embed');
          }
        }
      }
    }
  });
}

//Method Name: loadReaderLabels()
//Description: Retrieves Labels from JSON API and stores as Global Variables  
function loadReaderLabels() {
  GM_xmlhttpRequest({
    method: "GET",
    url: LABELS_LIST_API,
    onload: function(responseDetails) {

      var data = eval("(" + responseDetails.responseText + ")");
      for (var i = 0 ; i<data.tags.length ; i++) {
        var tagsPair = data.tags[i]
        if (tagsPair.id.indexOf("label") != -1){
          var LABEL = tagsPair.id.substring(tagsPair.id.lastIndexOf('/')+1, tagsPair.id.length);
          LABELS.push(LABEL);
        }
      }
      LABELS.push('uc76528q');
      loadSubscriptions();
    }
  });
}

//Method Name: loadSubscriptions()
//Description: Retrieves Subscriptions from JSON API and stores as Global Variables  
  function loadSubscriptions() {
  GM_xmlhttpRequest({
    method: "GET",
    url: SUBSCRIPTION_LIST_API,
    onload: function(responseDetails) {

      var data = eval("(" + responseDetails.responseText + ")");
      for (var i = 0 ; i<data.subscriptions.length ; i++) {
        var subscription   = new feed();
        subscription.title = data.subscriptions[i].title;
        subscription.url   = data.subscriptions[i].id;
        for (var j = 0 ; j<data.subscriptions[i].categories.length ; j++) {
          subscription.categories.push(data.subscriptions[i].categories[j].label);
        }
        if(0==data.subscriptions[i].categories.length){
          subscription.categories.push("uc76528q")
        }
        FEEDS.push(subscription);
      }
      populateFeedList();
    }
  });
}

// Uses google reader api to obtain unread counts
// Entried in the navbox with unread items have their text set to bold
function updateUnreadCounts() {

  GM_xmlhttpRequest({
    method: "GET",
    url: UNREAD_COUNT_API,
    onload: function(responseDetails) {

      var doc = navbox.getElement().ownerDocument;
      var data = eval("(" + responseDetails.responseText + ")");
      for (var i = 0, unreadCountPair ; unreadCountPair = data.unreadcounts[i] ; i++) {
        var count = unreadCountPair.count;

        if (unreadCountPair.id.indexOf("reading-list") != -1) {
          // update the window title
          if(-1!=top.document.title.indexOf(' Reader'))
            top.document.title=top.document.title.substring(0,top.document.title.indexOf(' Reader')) 

          top.document.title+=' Reader' + ' (' + count + (count == data.max ? '+' : '') + ') '; 					
        } else if (unreadCountPair.id.indexOf("label") != -1){
          // update the folder
          var LABEL = unreadCountPair.id.substring(unreadCountPair.id.lastIndexOf('/')+1, unreadCountPair.id.length);
          var rflLabel = doc.getElementById("rfl"+LABEL);
          if(!rflLabel) continue;

          if (count > 0)
            rflLabel.style.fontWeight = 'bold';
          else
            rflLabel.style.fontWeight = 'normal';
        } else {
          // update the feed
          var rflFeed = doc.getElementById(unreadCountPair.id);
          if (!rflFeed) continue;

          if(count > 0)
            rflFeed.style.fontWeight = 'bold';
          else
            rflFeed.style.fontWeight = 'normal';
        }
      }
    }
  });
}