joeytwiddle / Wikimedia+ for Chrome (localStorage)

// ==UserScript==
// @name          Wikimedia+ for Chrome (localStorage)
// @description   Add History box at wikimedia's leftmost column
// @version        1.1.1
// @downstreamURL http://userscripts.org/scripts/source/124389.user.js
// @include       http://*.wikipedia.org/*
// @include       http://*.wikimedia.org/wiki/*
// @include       http://ssdl-wiki.cs.technion.ac.il/wiki/*
// @include       http://wiki.greasespot.net/*
// @include       *wiki*
// @grant         none
// ==/UserScript==

/*
 * Original Wikimedia+: http://userscripts.org/scripts/show/7877
 *
 * 2012-03-09  Click "More..." for longer stored history (numToRemember/numToShow)
 *
 * 2011-??-??  Work in Chrome by using localStorage instead of GM_setValue.
 *             (localStorage is per-site, not global like GM_set/getValue.)
 *
*/

var numToRemember = 100;
var numToShow = 10;
var minHoursForBreak = 4;
var alwaysUseLocalStorage = true;   // I find this preferable because it acts per-site rather than global.

// Release notes
// =============
//        2011: Implemented GM_set/getValue for Chrome using localStorage (joey)
// 21-Mar-2007: Multiple occurrences of the same page (&edit, #section) eliminated
// 22-Mar-2007: Multiple occurrences due to a printable version of the page eliminated
// 24-Mar-2007: If no left column is present, exit quietly
// 31-May-2008: 
//    (a) Firefox 3.0 compatibility 
//    (b) Removed the edit links that were (by definition) part of the history
//    (c) Renamed the new box to "Wikimedia+"
// 19-Nov-2010: Updates to wikipedia's css style.

// BUG TODO: appears as a portal, but collapsing does not work
// DONE: ok added manual collapsing, but still some of the formatting looks different

var delayBeforeRunning = 2200;

// Fix for Chrome
// Check if GM_getValue is missing, OR is Chrome's "not supported" function.
var GM_test;
try {
   GM_test = ""+window.GM_getValue;
} catch (e) {
   // Greasemonkey: can't convert window.GM_getValue to primitive type
   // because: window.GM_getValue.toString is not a function
   // console.log("Getting GM_test: "+e);
}
if (alwaysUseLocalStorage || typeof GM_getValue !== 'function' || (""+GM_test).indexOf("not supported")>=0) {
   console.log("[Wikimedia+] Adding localStorage implementation of GMget/setValue for Chrome.");
   if (localStorage) {
      // We add and remove leading "s" to match records saved/loaded via FallbackGMAPI.
      // This stops the bookmarklet-loaded version from trashing the userscript version's values.
      GM_getValue=function (key,def) {
         return (""+localStorage.getItem(key)).replace(/^s/,'') || def;
      };
      GM_setValue=function (key,value) {
         localStorage.setItem(key, "s"+value);
         return value;
      };
   }
}

setTimeout(function()
{    
   var pref = "userscripts.org.wikimediaplus.history";
   var titleKey = pref + ".title.";
   var urlKey = pref + ".url.";
   var dateKey = pref + ".date.";
   var limit = 12;
   var read = function()
   {
      var r = new Array();
      for(var i = 0; i < numToRemember; ++i)
      {
         var o = new Object();
         o.title = GM_getValue(titleKey + i, null);
         o.url = GM_getValue(urlKey + i, null);
         o.date = parseFloat(GM_getValue(dateKey + i, null));
         if(o.title == null || o.url == null)
            continue;
         if(o.title.length == 0 || o.url.length == 0)
            continue;
         r.push(o);
      }
      return r;
   };
   var store = function(a)
   {
      for(var i = 0; i < numToRemember; ++i)
      {
         var o = a[i];
         if(!o)
         {
            o = new Object();
            o.title = "";
            o.url = "";
            o.date = 0;
         }
         GM_setValue(titleKey + i, o.title);
         GM_setValue(urlKey + i, o.url);            
         GM_setValue(dateKey + i, ""+o.date);            
      }
   };
   var addHist = function(url, title, date, a)
   {         
      var o = new Object();
      o.url = url;
      o.title = title;
      o.date = date;
      a.unshift(o);
      var b = new Array();
      for(var i in a)
      {
         var o = a[i];
         var found = false;
         for(var j in b)
         {
            var p = b[j];
            if(p.url == o.url)
               found = true;
         }
         if(!found)
            b.push(o);
      }
      return b;         
   };
   var strValue = function(o) 
   {
      if(o)
        return o.toString();
      return "";
   }
   var normalizeUrl = function(loc) 
   {      
      return strValue(loc.protocol) + "//" + strValue(loc.hostname) 
         + strValue(loc.port) + strValue(loc.pathname) + strValue(loc.search);
   };
   var recordDatesDifferEnough = function(newer,older) {
      return (newer && older && newer.date && older.date && newer.date - older.date > minHoursForBreak*1000*60*60);
   }
   var titleStr = document.title;
   var dash = titleStr.indexOf (' - ');
   titleStr = titleStr.substring(0,dash);
   var newHistoryItem = normalizeUrl(document.location);
   // Fix for when running alongside Joey's Reclaim CPU
   titleStr = titleStr.replace(/^[*#+.?] /,'');
   var hist = read();
   // console.log("[Wikimedia+] Got "+hist.length+" recent entries.");
   if(document.location.search.indexOf("&action=edit") < 0 && document.location.search.indexOf("&printable=yes") < 0)
      hist = addHist(newHistoryItem, titleStr, new Date().getTime(), hist);
   store(hist);
   function myEscape(str) {
      return str.replace('"','&quot;','g').replace('<','&lt;','g').replace('>','&gt;','g');
   }
   var listItem = function(href,name) { return '<li><a href="' + myEscape(href) + '">' + myEscape(name) + '</a></li>\n'; }
   var s = '<h3 role="navigation">Recent Pages</h3><div class="body" style="display:block;"><ul>';
   // for(var x in hist)
   for(var x=0; x<numToShow; x++)
   {
      var o = hist[x];
      if (o) {   // early users have little history
         s += listItem(o.url, o.title, o.date);
      }
      if (recordDatesDifferEnough(o,hist[x+1])) {
         s += "<hr>";
      }
   }
   s += '</ul></div>';
   var indentLaterLines = 'padding-left: 1.5em; text-indent: -1.5em;';
   // Note that MediaWiki sites (but not Wikipedia) augument this rule with: .portal ul { font-size: 95%; }
   var reduceSidebarFontSize = '/*div#mw-panel div.portal div.body ul li { font-size: 0.7em; }*/';
   //reduceSidebarFontSize += ' .portal ul { font-size: 100%; }';
   // The following .body rule applies to the whole sidebar, so none of the above is needed.
   s += '<style type="text/css"> .body { font-size: 0.85em; } div.body li { list-style-image: none; list-style-type: none; list-style-position: outside; '+indentLaterLines+' } '+reduceSidebarFontSize+' </style>';
   var e = document.createElement ("div");
   e.innerHTML = s;
   e.id = "p-history";
   e.className = "portal expanded";
   var panel = document.getElementById("mw-panel") ||
      document.getElementById("column-one") || document.getElementById("panel")
      || document.getElementById("jq-interiorNavigation");
   if (panel) {
      panel.insertBefore(e,panel.getElementsByClassName("portal")[1]);
      /* Should be handled by Wiki's own JS.
      e.getElementsByTagName("h3")[0].addEventListener("click",function(e){
            document.getElementsByClassName("pBody")[0].style.display = ( document.getElementsByClassName("body")[0].style.display ? '' : 'none' );
      },true);
      */

      if (hist.length > numToShow) {
         var openMore = document.createElement("div");
         var openMoreLink = document.createElement("a");
         openMoreLink.textContent = "More...";
         openMoreLink.href = "#";
         openMore.appendChild(openMoreLink);
         var targetUL = e.getElementsByTagName("ul")[0];
         targetUL.appendChild(openMore);
         openMore.onclick = function(evt) {
            targetUL.removeChild(openMore);
            var fragment = document.createDocumentFragment();
            for (var i=numToShow;i<hist.length;i++) {
               var o = hist[i];
               if (o) {
                  // I tried to set innerHTML of the fragment after building a
                  // big string as above, but the items did not appear.
                  var div = document.createElement("div");
                  div.innerHTML = listItem(o.url, o.title, o.date);
                  fragment.appendChild(div);
                  if (recordDatesDifferEnough(o,hist[i+1])) {
                     fragment.appendChild(document.createElement("hr"));
                  }
               }
            }
            targetUL.appendChild(fragment);
            evt.preventDefault(); // Do not follow the #
         };
      }

   } else {
      console.log("Found no sidebar to write to.");
   }
   // Uncomment next three lines if you want to remove the copy warning message from the bottom of the edit page
   // var warn = document.getElementById("editpage-copywarn");
   // if(warn) 
   //   warn.parentNode.removeChild(warn);

   function AppendCategoryTreeToSidebar() {
       try {
           var node = document.getElementById( "p-tb" )
                              .getElementsByTagName('div')[0]
                              .getElementsByTagName('ul')[0];

           var aNode = document.createElement( 'a' );
           var liNode = document.createElement( 'li' );

           aNode.appendChild( document.createTextNode( 'CategoryTree' ) );
           aNode.setAttribute( 'href' , 'http://en.wikipedia.org/wiki/Special:CategoryTree' );
           liNode.appendChild( aNode );
           liNode.className = 'plainlinks';
           node.appendChild( liNode );
           console.log("AppendCategoryTreeToSidebar(): Added "+liNode+" to "+node);
       } catch(e) {
           // lets just ignore what's happened
          console.log("Error in AppendCategoryTreeToSidebar(): "+e);
           return;
       }
   }

   AppendCategoryTreeToSidebar();

},delayBeforeRunning);