Google Reader - Unobtrusive Colorful List View

By emilise Last update May 28, 2009 — Installed 670 times. Daily Installs: 5, 2, 3, 2, 2, 2, 2, 2, 4, 2, 3, 2, 4, 2, 4, 3, 3, 2, 5, 2, 2, 2, 2, 3, 5, 2, 3, 2, 2, 3, 2, 2
// ==UserScript==
// @name           Google Reader - Colorful List View
// @namespace      http://google.reader.colorful.list.view/kepp
// @include        http://www.google.com/reader/*
// @include        https://www.google.com/reader/*
// @description    Colorizes the item headers in Google Reader list view 
// ==/UserScript==

/**
 * 20090528
 * Replaced background-color with right and left border
 *
 * 20081214
 * Prefs split out into independent items and updated to apply instantly.
 *  Pref notification messages are also fixed to work properly.
 * Fix for script not working if Google Gears was installed (my bad design).
 * Works on expanded view too now. Possible/easier with Google Reader now using
 *  CSS for rounded borders.
 *
 * 20081104 
 * Added settings for coloring read/unread items
 * Adjusted things in the settings
 *
 * 20080730
 * Fixed css mistake of read items not being colored.
 * Added https:// url to the include list
 * Added coloring option on settings page, added settings page to the exclude list
 **/


const BASE_CSS =
".gm-color-ev .card\
{\
  background-color: transparent !important;\
}\
.gm-color-lv .collapsed\
{\
  border-color: transparent !important;\
}\
#entries.list.gm-color-lv #current-entry .collapsed\
{\
  border: 2px solid #8181DC !important;\
}\
#entries.list.gm-color-ui #current-entry.expanded .collapsed\
{\
  border-bottom-color: transparent !important;\
  border-width: 2px 0 !important;\
}\
#entries .entry\
{\
  padding: 5px 0;\
}";

// used to keep track of all the calculated colors
var colors = {};

function $x( q, c )
{
  // doc = iframe contentDoc || context node's owner doc || the context node / document
  var doc = c ? ( c.contentDocument || c.ownerDocument || c ) : document;
  c = ( c && c.contentDocument ) ? c.contentDocument : c; // if c is an iframe, set c to its contentDoc element
  return doc.evaluate( q, ( c || doc ), null, 
         XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue;
}

function $xa( q, c )
{
  var doc = c ? ( c.contentDocument || c.ownerDocument || c ) : document;
  c = ( c && c.contentDocument ) ? c.contentDocument : c;
  var r = doc.evaluate( q, ( c || doc ), null,
          XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null );
  var e, a = [];
  while ( e = r.iterateNext() )
    a.push( e );

  return a;
}



// calculate item hue
function getHue( title )
{
    var h = 0;

    for each ( var c in title )
      h += c.charCodeAt( 0 );
    h = h % 360;

    colors[title] = h;
    return h;
}

function getColorCss( title )
{
  var hue = getHue( title );

  return "\
  .gm-color-ev.gm-color-ui div[ colored='" + title + "' ] .card,\
  .gm-color-lv.gm-color-ui div[ colored='" + title + "' ] .collapsed\
  {\
    border-left: solid 15px hsl( " + hue + ", 70%, 60% ) !important;\
	border-right: solid 15px hsl( " + hue + ", 70%, 60% ) !important;\
  }\
  .gm-color-ev.gm-color-ui div[ colored='" + title + "' ]:hover .card,\
  .gm-color-lv.gm-color-ui div[ colored='" + title + "' ]:hover .collapsed\
  {\
	border-left: solid 15px hsl( " + hue + ", 90%, 65% ) !important;\
	border-right: solid 15px hsl( " + hue + ", 90%, 65% ) !important;\
  }\
  .gm-color-ev.gm-color-ui .read[ colored='" + title + "' ] .card,\
  .gm-color-lv.gm-color-ui .read[ colored='" + title + "' ] .collapsed\
  {\
	border-left: auto ;\
	border-right: auto;/* force read items to not be colored. */\
  }\
  .gm-color-ev.gm-color-ui .read[ colored='" + title + "' ]:hover .card,\
  .gm-color-lv.gm-color-ui .read[ colored='" + title + "' ]:hover .collapsed\
  {\
	border-left: auto ;\
	border-right: auto;\ /* force read items to not be colored. */\
  }\
  .gm-color-ev.gm-color-ri div.read[ colored='" + title + "' ] .card,\
  .gm-color-lv.gm-color-ri div.read[ colored='" + title + "' ] .collapsed\
  {\
    /* color read items. overrides the unread item setting. */\
	  border-left: solid 15px hsl( " + hue + ", 50%, 70% ) !important;\
	  border-right: solid 15px hsl( " + hue + ", 50%, 70% ) !important;\
  }\
  .gm-color-ev.gm-color-ri div[ colored='" + title + "' ].read:hover .card,\
  .gm-color-lv.gm-color-ri div.read[ colored='" + title + "' ]:hover\
    .collapsed\
  {\
    /* color read items. overrides the unread item setting. */\
	  border-left: solid 15px hsl( " + hue + ", 70%, 75% ) !important;\
	  border-right: solid 15px hsl( " + hue + ", 70%, 75% ) !important;\
  }"
}

// inject color css into the page
function setColor( entries )
{
  var uncolored = $x( "id( 'entries' )/" +
                  "div[contains( @class,'entry' )][not( @colored )]" );
  if ( !uncolored )
    return;

  var title = $x( ".//*[contains( @class,'entry-source-title' )][not( * )]",
              uncolored ).textContent.replace( /\W/g, "-" );

  uncolored.setAttribute( "colored", title );

  if ( colors[title] == undefined )
    GM_addStyle( getColorCss( title ) );
}


var settings = 
{
  toID: 0,
  entries: null,

  init: function() // insert page color options into the settings page
  {
    this.entries = $x( "id('entries')", frameElement.ownerDocument );
    var extras = $x( "id( 'setting-extras-body' )" );
    var e = document.createElement( "div" );
    e.className = "extra";

    e.innerHTML = "<div class=\"extra-header\">Colors</div>";
    extras.appendChild( e );

    this.addPref( e, "gm-color-lv",
                    "Color items when browsing in list view." );
    this.addPref( e, "gm-color-ev", 
                    "Color items when browsing in expanded view." );
    this.addPref( e, "gm-color-ri", 
                    "Color items that have been marked as read." );
    this.addPref( e, "gm-color-ui",
                    "Color items that are unread." );
  },

  addPref: function ( e, id, text ) // create and insert setting
  {
    var label = document.createElement( "label" );
    label.innerHTML = "<input id=\"" + id + "\" type=\"checkbox\" " +
                      ( GM_getValue( id, id + " " ) ? "checked=\"on\"" : "" ) +
                      "\"/>" + text;
    e.appendChild( label );
    e.appendChild( document.createElement( "br" ) );

    var self = this;
    label.firstChild.addEventListener( "change", function( e )
    {
      self.toggleColors( e )
    }, false );
  },

  toggleColors: function( e )
  {
    var id = e.target.id;
    var pref = GM_getValue( id, id + " " );

    var msg, newPref = "", cn = "";
    if ( !pref )
    {
      newPref = id + " ";
      cn = newPref;
      msg = "<em>will</em>";
    }
    else
    {
      msg = "<em>will not</em>";
    }

    var re = new RegExp( id + " |^", "g" );
    this.entries.className = this.entries.className.replace( re, cn );
    GM_setValue( id, newPref );
    this.setMessage( id, msg );
  },

  setMessage: function( id, msg )
  {
    clearTimeout( this.toID );
    var ma = $x( "id( 'message-area-inner' )" );
    var mo = $x( "id( 'message-area-outer' )" );

    // get the message string to insert into the page
    var type = ( id == "gm-color-lv" ) ? "List view items " :
               ( id == "gm-color-ev" ) ? "Expanded view items " :
               ( id == "gm-color-ui" ) ? "Unread items " :
               ( id == "gm-color-ri" ) ? "Read items " : "unknown";

    ma.innerHTML = type + msg + " be colored.";

    // make the message area visible, overriding the built in method
    mo.setAttribute( "style", "display: block !important" ); // override
    mo.className = "info-message"; // this is the built in method

    this.toID = setTimeout( function()
    {
      mo.removeAttribute( "style" );
      if ( ma.innerHTML == type + msg + " be colored." )
      {
        mo.className = mo.className.replace( / hidden|$/, " hidden" );
      }
    }, 7*1000 );
  },

  getColorPrefs: function()
  {
    var prefs = "";

    prefs += GM_getValue( "gm-color-lv", "gm-color-lv " );
    prefs += GM_getValue( "gm-color-ev", "gm-color-ev " );
    prefs += GM_getValue( "gm-color-ui", "gm-color-ui " );
    prefs += GM_getValue( "gm-color-ri", "gm-color-ri " );

    return prefs;
  }
};



function watchLoading( chrome )
{
  // pull this out here so context GM functions are called from is ok
  var prefs = settings.getColorPrefs();

  function setup( e )
  {
    var entries = $x( "id( 'entries' )" );
    if ( entries )
    {
      chrome.removeEventListener( "DOMNodeInserted", setup, false );

      // initial setup and toggling of settings
      entries.className = prefs + entries.className; 
      GM_addStyle( BASE_CSS );
      entries.addEventListener( "DOMNodeInserted", setColor, false );
    }
  }

  chrome.addEventListener( "DOMNodeInserted", setup, false );
}

(function()
{
  var chrome = $x( "id( 'chrome' )" );

  if ( chrome )
    watchLoading( chrome ); // watch for the loading of rss entries
  else
    settings.init(); // add settings to the settings page

})();