Sorry, there are no scripts tagged wars;msg5422

Google Reader - Colorful List View

By kepp Last update Nov 22, 2009 — Installed 24,866 times. Daily Installs: 41, 25, 29, 40, 19, 38, 35, 40, 33, 37, 24, 35, 34, 54, 28, 40, 45, 24, 50, 41, 27, 37, 45, 19, 31, 20, 80, 370, 217, 107, 296, 366

There are 11 previous versions of this script.

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

// ==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/*
// @include        http://userscripts.org/scripts/source/8782.meta.js
// @description    Colorizes the item headers in Google Reader list view and the entries in expanded view.
// @version        20091122
// @jsversion      1.6
// ==/UserScript==

/**
 * 
 * XXXXXXXX Not done yet
 * Fix settings messages being partially hidden in Chrome.
 * Fix for updates not showing in Chrome
 * Added prefs for customizable color scheme.
 * - Selectable background color hue, lightness ranges and saturation.
 * - Selectable text color hue, lightness ranges and saturation.
 *
 * 20091122
 * Fix for breakage on expanded view.
 * CSS modified to increase selector priority to ensure colors get applied.
 *
 * 20091120
 * Fix pref settings when DOM Storage is used.
 * Fix for GM_getValue detection on Google Chrome dev channel.
 * Fix for not working after encountering a shared item. Script also works on
 *  the "Your stuff", Shared Items and Notes pages with this.
 * Switch code bracing style.
 *
 * 20091117
 * Added prefs to select what is colored in expanded view, entry body or
 *  outline.
 * Fix for update version check being in the wrong direction.
 * Fix for coloring unread items only in list view.
 * Added Google Chrome support.
 *
 * 20090822
 * Fix to ensure that all items get colored.
 * Fixes for Google Reader update.
 * Added script update notification to the settings page.
 * Added Opera compatibility.
 * - Remove use of "for each".
 * - Add alternatives to GM_ functions (GM_addStyle, GM_setValue, GM_getValue).
 * - Modify coloring CSS.
 * Cleaned up some code.
 * Also added DOM Storage fallback option.
 * 
 *
 * 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.
 **/

// var script = document.createElement("script");
// script.innerHTML = "(" + 
(function() {

  // info used to check for script updates
  const SCRIPT_INFO = {
    version:    "20091122",
    date:       "Sat Nov 22 2009 00:00:00 GMT",
    updateUrl:  "http://userscripts.org/scripts/source/8782.meta.js",
    installUrl: "http://userscripts.org/scripts/source/8782.user.js"
  };

  // CSS to allow items to be colored
  const BASE_CSS = "\
    #enctires.list .entry-likers, /* like count */\
    #entries.list .collapsed .entry-source-title,\
    #entries.list .collapsed .entry-secondary,\
    #entries.list .collapsed .entry-title {\
      background-color: transparent !important;\
    }\
    .gm-color-lv .collapsed /* list view headers */ {\
      border-color: transparent !important;\
    }\
    #entries.list.gm-color-lv #current-entry .collapsed {\
      border: 2px solid #8181DC !important;\
    }\
    #entries.list.gm-color-lv #current-entry.expanded .collapsed {\
      border-bottom-color: transparent !important;\
      border-width: 2px 0 !important;\
    }\
    #entries .entry {\
      padding: 5px 0;\
    }\
    #entries.list .collapsed {\
      line-height: 2.4ex !important; /* hide entry snippet 2nd line */\
    }";

  const STRINGS = {
    // pref labels
    color:       "Color these items:",
    list:        "List view headers.",
    expanded:    "Expanded view entry bodies.",
    frame:       "Expanded view entry frames.",
    read:        "Read items.",
    unread:      "Unread items.",

    // pref messages
    msgWill:     "will",
    msgWillNot:  "will not",
    msgColored:  " be colored.",
    msgList:     "List view items ",
    msgExpanded: "Expanded view entry bodies ",
    msgFrame:    "Expanded view entry frames ",
    msgUnread:   "Unread items ",
    msgRead:     "Read items ",
    msgUndef:    "Undefined",

    scheme:      "Color Scheme: ",
    def:         "Default",
    custom:      "Custom",
    
    update:      "Userscript Update Available",
    install:     "Install"
  };


//=============================================================================


  function $id( id ) {
    return document.getElementById( id );
  }

  function $x( query, context ) {
    var doc = ( context ) ? context.ownerDocument : document
    return doc.evaluate( query, ( context || doc ), null, 
           XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue;
  }

  function $xa( query ) {
    var res = document.evaluate( query, document, null,
              XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null );
    var element, array = [];
    while ( element = res.iterateNext() )
      array.push( element );

    return array;
  }

  function addStyle( css ) {
    var style = document.createElement( "style" );
    style.type = "text/css";
    style.textContent = css; // innerHTML throws NO_MODIFICATION_ALLOWED_ERR
    document.getElementsByTagName( "head" )[0].appendChild( style );
    return style;
  }
  

//=============================================================================


  // script updater
  var updater = {
    loader: null,
    version: 0,
    homeUrl: "",
    updateUrl: "",
    installUrl: "",

    init: function() {
      for ( var prop in SCRIPT_INFO )
        this[ prop ] = SCRIPT_INFO[ prop ];

      // test if this is the script meta info page that loaded
      if ( location.href == SCRIPT_INFO.updateUrl ) {
        // running on userscripts.org domain

        document.body.setAttribute( "style", "visibility: hidden;\
                                              overflow: hidden;" );

        // there's an update, update link not inserted yet
        if ( !document.getElementsByTagName( "a" ).length ) {
          if ( this.parseMetaInfo() )
            // this will reload the page
            this.insertUpdateLink( this.installUrl ); 
        } 
        else {
          document.body.setAttribute( "style", "visibility: visible;\
            overflow: hidden;font-family: Arial, sans-serif;color: #2244BB;" );
        }
        return true; // notify that this was the script meta page
      }

      var loader = document.createElement( "iframe" );
      loader.setAttribute( "style", "position: absolute;\
                                     height: 0; width: 0;" );
      this.loader = document.body.appendChild( loader );
    },

    parseMetaInfo: function() {
      var scriptInfo = document.body.innerHTML;
      var updateAvailable;

      // compare script versions
      if ( /@version\s*([\S]+)/.test( scriptInfo ) )
        updateAvailable = this.version < RegExp.$1;

      // compare script dates
      else if ( /@uso:timestamp\s*(\S.+)/.test( scriptInfo ) )
        updateAvailable = new Date( this.date ) < new Date( RegExp.$1 );

      return updateAvailable;
    },

    insertUpdateLink: function( url ) { // insert link to newer script version
        document.open( "text/html" );
        document.write( "<html><head><meta http-equiv=\"Content-Type\"\
                        content=\"text/html; charset=windows-1251\"></head>\
                        <body>" + STRINGS.update + ": <a href=\"" +
                        url + "\" target=\"_blank\">" +
                        STRINGS.install + "</a><body></html>" );

        document.close();
    },


    check: function() { // runs on google.com domain
      var lastCheck = storage.getItem( "last-check", 0 );
      if ( new Date().getTime() - lastCheck < 3*24*60*60*1000 ) // 3 days
        return false;

      this.loader.setAttribute( "style", "visibility: visible;\
                                          overflow: hidden;\
                                          position: absolute; right: 2em;\
                                          height: 2em; width: 20em;" );
      this.loader.src = this.updateUrl;

      // just check it every time the settings page is opened
      // storage.setItem( "last-check", new Date().getTime() + "" );
      return this.loader;
    }
  };

  // user interface for script settings added on settings page
  var settings = {
    timeoutID: 0,
    entries: null,

    init: function() { // insert page color options into the settings page
      // ascend out of iframe
      this.entries = frameElement.ownerDocument.getElementById( "entries" );

      var sect = this.addPrefs();

      // check for userscript updates
      // comment this section out if you want to disable update checks
      var check = updater.check();
      if ( check )
        sect.insertBefore( check, sect.firstChild );
    },

    addPrefs: function() {
      var sect = document.createElement( "div" );
      sect.className = "extra";

      // two column list, yay <_<
      sect.innerHTML = "<div class=\"extra-header\">Colors</div>" +
                       STRINGS.color +
                       "<div style=\"width: 30em; margin: 0pt 0pt 1em 1em;\">\
                       <ul style=\"list-style-type: none; padding-left: 0;\
                                   float: right;\">\
                       </ul>\
                       <ul style=\"list-style-type: none; padding-left: 0;\">\
                       </ul></div>";// + STRINGS.scheme;

      $id( "setting-extras-body" ).appendChild( sect );
      var lists = sect.getElementsByTagName( "ul" );

      var me = this;
      function tc( event ) {
        me.toggleColors( event.target.id, event.target.checked );
      }
      this.addColorPref( lists[ 0 ], "gm-color-ri", STRINGS.read, tc );
      this.addColorPref( lists[ 0 ], "gm-color-ui", STRINGS.unread, tc );
      this.addColorPref( lists[ 1 ], "gm-color-lv", STRINGS.list, tc );
      this.addColorPref( lists[ 1 ], "gm-color-ev", STRINGS.expanded, tc );
      this.addColorPref( lists[ 1 ], "gm-color-ef", STRINGS.frame, tc, 0 );

      // this.addSchemePref( sect );
      return sect;
    },

    addColorPref: function ( list, id, text, handler, def ) {
      var pref = document.createElement( "li" );
      var selected = storage.getItem( id, ( def == undefined ) ? 1 : def );
      pref.innerHTML = "<label><input id=\"" + id + "\" type=\"checkbox\" " +
                       ( ( selected ) ? "checked=\"on\"" : "" ) +
                       "\"/>" + text + "</label>";
      list.appendChild( pref );

      var label = pref.firstChild.firstChild;
      label.addEventListener( "change", handler, false );
    },

    toggleColors: function( id, curr ) {
      var msg, newPref = "", cName = "";
      if ( curr ) {
        newPref = id;
        cName = id + " ";
        msg = "<em>" + STRINGS.msgWill + "</em>";
      }
      else {
        msg = "<em>" + STRINGS.msgWillNot + "</em>";
      }

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

    togglePref: function( event ) {
      if ( this.value == STRINGS.custom ) {
        this.style.fontStyle =  "italic";
        addButton.removeAttribute( "disabled" );
      }
      else {
        this.style.fontStyle = "";
        addButton.setAttribute( "disabled", "true" );
      }
    },

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

      // get the message string to insert into the page
      var type = ( id == "gm-color-lv" ) ? STRINGS.msgList :
                 ( id == "gm-color-ev" ) ? STRINGS.msgExpanded :
                 ( id == "gm-color-ef" ) ? STRINGS.msgFrame :
                 ( id == "gm-color-ui" ) ? STRINGS.msgUnread :
                 ( id == "gm-color-ri" ) ? STRINGS.msgRead : STRINGS.msgUndef;

      var newMsg = type + msg + STRINGS.msgColored; 
      inner.innerHTML = newMsg; // set the message
      
      // force display and set position and width
      outer.setAttribute( "style", "display: block !important;" +
                          "margin-left:" +
                          Math.round( inner.offsetWidth/-2 ) + "px;" +
                          "width:" + (inner.offsetWidth + 10) + "px;" );
      outer.className = "message-area info-message";

      this.timeoutID = setTimeout( function() {
        outer.style.display = "";

        // test if the same message is still showing.
        // force lowercase to handle any (tag name) capitalization change
        if ( inner.innerHTML.toLowerCase() == newMsg.toLowerCase() )
          outer.className = outer.className.replace( / hidden|$/, " hidden" );

      }, 7*1000 );
    },

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

      prefs += storage.getItem( "gm-color-lv", "gm-color-lv" ) + " ";
      prefs += storage.getItem( "gm-color-ev", "gm-color-ev" ) + " ";
      prefs += storage.getItem( "gm-color-ef", "" ) + " ";
      prefs += storage.getItem( "gm-color-ui", "gm-color-ui" ) + " ";
      prefs += storage.getItem( "gm-color-ri", "gm-color-ri" ) + " ";

      return prefs;
    }
  };

  // provide local data storage
  var storage = {
    cookie: {},

    init: function() { // initialize methods for data storage access
      // Google Chrome dev channel stubs GM_ functions with error messages
      // test it's the real deal by looking for "arguments" in stingified version
      if ( typeof GM_getValue != "undefined" &&
           /arguments/.test( GM_getValue.toString() ) ) {
        this.getItem = GM_getValue;
        this.setItem = GM_setValue;
        return;
      }

      // Google Chrome gives null for localStorage if not enabled by switch,
      // Opera gives undefined
      // http://www.w3.org/TR/webstorage/#the-storage-interface
      if ( typeof localStorage != "undefined" && localStorage != null ) {

        this.getItem = function( key, def ) {
          var value = localStorage.getItem( key );
          return ( value == null ) ? def : value;
        };

        this.setItem = function( key, value ) {
          localStorage.setItem( key, value );
        };
        return;
      }

      var pairs = {};
      if ( /gm-color=([^;]*)/.test( unescape( document.cookie ) ) ) {
        var cookie = RegExp.$1;

        cookie.split( "/" ).forEach( function( pair ) {
          var set = pair.split( ":" );
          pairs[ set[ 0 ] ] = set[ 1 ];
        } );
      }

      this.cookie = pairs;
    },

    getItem: function( name, def ) {
      var cookieVal = this.cookie[ name ];
      return ( typeof cookieVal == "undefined" ) ? def : cookieVal;
    },

    setItem: function( name, value ) {
      this.cookie[ name ] = value;
      var strCookie = "gm-color=";

      for ( var prop in this.cookie )
        strCookie += prop + ":" + this.cookie[ prop ] + "/";

      var future = new Date( ( new Date().getTime() + 10*365*24*60*60*1000 ) );
      strCookie += ";path=/reader;expires=" + future.toGMTString();

      document.cookie = strCookie;
    },
  };

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


//=============================================================================


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

    for ( var i = 0, ch; ch = title[ i ]; i++ )
      hue += ch.charCodeAt( 0 );
    hue %= 360;

    colors[ title ] = hue;
    return hue;
  }

  function getColorCss( title ) {
    var hue = getHue( title );
    return getLvCss( title, hue ) + getEvCss( title, hue ) +
           getEfCss( title, hue );
  }

  function getLvCss( ttl, hue ) { // css for coloring items in list view
    // this selector should be take priority over any other selector
    var lvUi = "#entries.gm-color-lv.gm-color-ui div[ colored='";
    var lvRi = "#entries.gm-color-lv.gm-color-ri div[ colored='";
    return "" +
      lvUi + ttl + "' ] .collapsed {\
        background-color: hsl(" + hue + ", 70%, 80% ) !important;\
      }" +
      lvUi + ttl + "' ]:hover .collapsed {\
        background-color: hsl(" + hue + ", 90%, 85% ) !important;\
      }" +
      lvUi + ttl + "' ].read .collapsed," +
      lvUi + ttl + "' ].read:hover .collapsed {\
        background-color: white !important; /* override to force no color */\
      }" +
      lvRi + ttl + "' ].read .collapsed {\
        /* color read items. overrides the unread item setting. */\
        background-color: hsl(" + hue + ", 50%, 90% ) !important;\
      }" +
      lvRi + ttl + "' ].read:hover\
      .collapsed {\
        /* color read items. overrides the unread item setting. */\
        background-color: hsl(" + hue + ", 70%, 95% ) !important;\
      }";
  }

  function getEvCss( ttl, hue ) { // css for coloring expanded view item bodies
    var evUi = "#entries.gm-color-ev.gm-color-ui div[ colored='";
    var evRi = "#entries.gm-color-ev.gm-color-ri div[ colored='";
    return "" +
      evUi + ttl + "' ] .card," +
      /* .ccard, .t2, .t3 in Opera expanded view */
      evUi + ttl + "' ] .ccard," +
      evUi + ttl + "' ] .t2," +
      evUi + ttl + "' ] .t3 {\
        background-color: hsl(" + hue + ", 70%, 80% ) !important;\
      }" +
      evUi + ttl + "' ]:hover .card," +
      evUi + ttl + "' ]:hover .ccard," +
      evUi + ttl + "' ]:hover .t2," +
      evUi + ttl + "' ]:hover .t3 {\
        background-color: hsl(" + hue + ", 90%, 85% ) !important;\
      }" +
      evUi + ttl + "' ].read .card," +
      evUi + ttl + "' ].read .ccard," +
      evUi + ttl + "' ].read .t2," +
      evUi + ttl + "' ].read .t3," +
      evUi + ttl + "' ].read:hover .card," +
      evUi + ttl + "' ].read:hover .ccard," +
      evUi + ttl + "' ].read:hover .t2," +
      evUi + ttl + "' ].read:hover .t3 {\
        background-color: white !important; /* override to force no color */\
      }" +
      evRi + ttl + "' ].read .card," +
      evRi + ttl + "' ].read .ccard," +
      evRi + ttl + "' ].read .t2," +
      evRi + ttl + "' ].read .t3 {\
        /* color read items. overrides the unread item setting. */\
        background-color: hsl(" + hue + ", 50%, 90% ) !important;\
      }" +
      evRi + ttl + "' ].read:hover .card," +
      evRi + ttl + "' ].read:hover .ccard," +
      evRi + ttl + "' ].read:hover .t2," +
      evRi + ttl + "' ].read:hover .t3 {\
        /* color read items. overrides the unread item setting. */\
        background-color: hsl(" + hue + ", 70%, 95% ) !important;\
      }";
  }

  function getEfCss( ttl, hue ) { // css for coloring expanded view item frames
    var efUi = "#entries.gm-color-ef.gm-color-ui div[ colored='";
    var efRi = "#entries.gm-color-ef.gm-color-ri div[ colored='";
    return "" +
      efUi + ttl + "' ] {\
        background: hsl(" + hue + ", 70%, 80% ) !important;\
      }" +
      efUi + ttl + "' ]:hover {\
        background: hsl(" + hue + ", 90%, 85% ) !important;\
      }" +
      efUi + ttl + "' ].read," +
      efUi + ttl + "' ].read:hover {\
        background: #F3F5FC !important; /* override to force no color */\
      }" +
      efRi + ttl + "' ].read {\
        /* color read items. overrides the unread item setting. */\
        background: hsl(" + hue + ", 50%, 90% ) !important;\
      }" +
      efRi + ttl + "' ].read:hover {\
        /* color read items. overrides the unread item setting. */\
        background: hsl(" + hue + ", 70%, 95% ) !important;\
      }";
  }

  // inject color css into the page
  function setColor() {
    // pick up all uncolored entries, including ones missed previously
    var nocolor = $xa( "id( 'entries' )/div[ contains( @class, 'entry' ) ]" +
                       "[ not( @colored ) ]" );

    if ( !nocolor.length )
      return;

    nocolor.forEach( function( nc ) {

      // source in header is an "<a>" for expanded view, "<span>" for list view
      // if "Shared by [xxx]" is there this will grab that
      // search for a node that has 'entry-source-title' class name
      var src = $x( ".//*[ contains(" +
                    "concat( ' ', normalize-space( @class ), ' ')," +
                    "' entry-source-title ' ) ]", nc )
      src = src.textContent.replace( /\W/g, "-" );

      nc.setAttribute( "colored", src );
      if ( colors[ src ] == undefined )
        addStyle( getColorCss( src ) );
    } );
  }

  function watchLoading( chrome ) {
    // pull this out here out of unsafeWindow context
    var prefs = settings.getColorPrefs();

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

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

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

  (function() {
    var chrome = $id( "chrome" );
    storage.init();

    if ( chrome )
      watchLoading( chrome ); // watch for the loading of rss entries

    else { // settings and script meta info page have no "chrome" element
      if ( updater.init() ) // script meta info page
        return;

      settings.init();
    }

    addStyle( BASE_CSS );
  } )();

})();

// .toString() + ")();";

// document.body.appendChild(script);


/* IN PROGRESS CODE


// units in px
const FULL_WIDTH = 360;
const BORDER =     1;
*/

// const STYLE = "\
// .arrow-bg {\
  // width: 0;\
  // border-bottom: 4px solid #000000; /* 1/2*handle width */\
  // border-left: 4px solid transparent;\
  // border-right: 4px solid transparent;\
  // float: left;\
  // margin: 1px 0 0 1px; /* handle border widths */\
// }\
// .arrow-border {\
  // width: 0;\
  // border-bottom: 5px solid black; /* arrow-bg + border */\
  // border-left: 5px solid transparent;\
  // border-right: 5px solid transparent;\
// }\
// .handle {\
  // height: 8px;\
  // width: 8px; /* handle width */\
  // border: 1px solid black;\
// }\
// #gm-grad-colors-box {\
  // width: 366px; /* width + 2*border + handle width */\
// }\
// #gm-grad-box {\
  // height: 20px;\
  // width: 360px; /* width */\
  // border: 1px solid black; /* border */\
  // margin-left: 4px; /* handle width/2 */\
// }";

/*
function addStyle( css ) {
  var style = document.createElement( "style" );
  style.textContent = css;
  document.getElementsByTagName( "head" )[0].appendChild( style );  
}

function bind( func, thisArg ) {
  var args = Array.prototype.slice.call( arguments, 2 );
  return function() {
    func.apply( thisArg, args.concat( arguments ) );
  }
}

function Color( rgbArr, posFraction, callback ) {
  // for signalling the gradient should be updated
  this.callback = bind( callback, callback );
  this.posFraction = posFraction;

  var el = document.createElement( "div" );
  el.innerHTML = "<div class=\"arrow-bg\"></div>\
    <div class=\"arrow-border\"></div>\
    <div class=\"handle\" style=\"background: rgb(" + rgbArr + ");\">\
    <div style=\"float: left; width: 6px; height: 6px; border: 1px solid white;\"></div></div>";
  el.setAttribute( "style", "position: absolute; margin-left:" +
                             FULL_WIDTH*posFraction + ";" );

  var divs = el.getElementsByTagName( "div" );
  this.bg = divs[0];
  this.handle = divs[2];

  el.addEventListener( "mouseup", bind( this.endDrag, this ), false );
  el.addEventListener( "mouseout", bind( this.endDrag, this ), false );
  el.addEventListener( "mousedown", bind( this.startDrag, this ), false );

  this.element = el;
}

Color.prototype = {
  selColor: "rgb( 200, 200, 200 )",
  unselColor: "rgb( 0, 0, 0 )",

  startDrag: function( event ) {
    this.bg.style.borderBottomColor = this.selColor;
    this.callback( "select", this );
    this.element.addEventListener( "mousemove", this.followDrag, false );
  },

  endDrag: function( event ) {
    this.element.removeEventListener( "mousemove", this.followDrag, false );
  },

  followDrag: function( event ) {
    this.callback( "move", this );
  }

};

// ============================================================================

function Gradient() {
  this.editor = document.createElement( "div" );
  this.editor.id = "gm-grad-editor";
  this.editor.innerHTML = "<div id=\"gm-grad-box\"></div>\
                           <div id=\"gm-grad-colors-box\"></div>";  
  this.gradBox = this.editor.firstChild; // displays the generated gradient
  this.colorBox = this.editor.lastChild; // holds the color stops
}

Gradient.prototype = {
  gradBox: null,
  colorBox: null,

  selected: null,
  colors: [],

  addColor: function( rgbArr, posFraction, update ) {
    var color = new Color( rgbArr, posFraction, bind( this.update, this ) );
    this.colors.push( color );
    this.colorBox.appendChild( color.element );
    if ( update )
      this.update.call(this, "add", color );
  },

  update: function( msg, msgSrc ) {alert(msg);
    switch( msg ) {
      case "select":
        if ( this.selected != msgSrc ) {
          this.selected.bg.style.borderBottomColor = this.selected.unselColor;
          this.selected = msgSrc;
        }
        break;
      case "add":
      case "move":
      default:  // "change"
        this.colors.sort( this.posCompare );
        this.rebuild();
        break;
    }
  },

  rebuild: function() {
  
  },

  posCompare: function( color1, color2 ) {
    return color1.posFraction - color2.posFraction;
  },

  insert: function( node ) {
    node.appendChild( this.editor );
  }
};
var grad = new Gradient();

// ============================================================================

addStyle( STYLE );
grad.addColor( [0, 0, 0], 0 )
grad.addColor( [255, 255, 255], 1 );
grad.insert( document.body );

*/
/* OLD CODE
    addSchemePref: function( sect ) {
      var sel = document.createElement("select");
      var addButton = document.createElement( "input" );

      sel.addEventListener( "change", this.togglePref, false )
      sel.innerHTML = "<option style=\"font-style: normal;\">" +
                         STRINGS.def + "</option>\
                       <option style=\"font-style: italic;\">" +
                         STRINGS.custom + "</option>";

      addButton.type = "button";
      addButton.disabled = "true";
      addButton.value = "Save";
      addButton.style.marginLeft = "1em";

      sect.appendChild( sel );
      sect.appendChild( addButton );
      this.addColorPickers( sect );
    },

    addColorPickers: function( section ) {
      var bgRange = this.makeColorRange();
      var fontRange = this.makeColorRange();

      section.appendChild( bgRange );
     // section.appendChild( fontRange );
    },

    makeColorRange: function() {
      var range = document.createElement( "div" );
      range.setAttribute( "style", "width: 360px; height: 10px;\
                          margin-top: 1em; border: 1px solid #FFCC66;" );

      for ( var i = 360; i; i-- ) {
        var color = document.createElement( "div" );
        color.setAttribute( "style", "float: left; width: 1px; height: 100%; \
                            background-color: hsl(" + i + ",100%,50%" );
        range.appendChild( color );
      }

      var rangeContainer = document.createElement( "div" );
      rangeContainer.setAttribute( "style", "height: 19px; width: 362px;" );
      
      var rangeBegin = document.createElement( "div" );
      rangeBegin.setAttribute( "style", "position: absolute; width: 1px; \
                               height: 13px; border: 1px solid black; \
                               background: transparent;" );
      
      var rangeEnd = rangeBegin.cloneNode( true );
      rangeBegin.style.marginLeft = "-1px";
      rangeEnd.style.marginLeft = "359px";
      rangeBegin.style.borderLeftWidth = "2px";
      rangeEnd.style.borderRightWidth = "2px";

      rangeContainer.appendChild( rangeBegin );
      rangeContainer.appendChild( rangeEnd );
      rangeContainer.appendChild( range );

      return rangeContainer;
    },
*/