SalonOne

By Rod McGuire Last update Feb 28, 2007 — Installed 207 times.
// ==UserScript==
// @name           Salon Letters on One Page
// @namespace      ~rmm
// @description    Put all Salon comment 'letters' on one page. This also can do the same for all letters from any sub-page onwards. Occasionally this script does not recognize that a sub page has loaded and you will see an unconverted frame - in this case there will be a pink message you can click on to reload such sub-pages. When letters are displayed in oldest first order, there will be a pink message near the bottom you can click on to check for new comments. This action will just reload the last sub-page inline and avoid having to reload all the other sub-pages which would happen if you refreshed the top level page. This can save a good deal of time if there are lots of sub-pages. Every page seen by this script gets a small purple bar placed at the top of the page. Try reloading the page if the bar is not present. There seem to be a few bugs in Firefox or Greasemonkey which are causing the above problems -- they are being investigated
// @include        http://letters.salon.com/*
// @include        http://www.salon.com/*
// ==/UserScript==

// Copyright (c) 2007 Roderick M McGuire

Debug = true; // set this to "true" to see some messages in the Error Console.

function myGM_log(arg){
 if (Debug) GM_log(arg);
}
function Main(){
 var host = document.location.host;
 var hash = document.location.hash;
 if (host == 'www.salon.com'){
  slapNoticeOnTopOfPage('Greasemonkey active on a <b>www.salon.com</b> page', 'salonLoad',
   'font-size: 7pt; background-color: rgb(230, 220, 255)');
  mungSalonPage();
  return;
  }
 if (hash == '#asIframe'){
  slapNoticeOnTopOfPage('Loaded as an IFRAME', 'iFrameNote',
   'font-size: 20pt; background-color: rgb(230, 220, 255)');
  unsafeWindow.iframeLoaded = true;
  return;
  }
 if (host == 'letters.salon.com'){
  slapNoticeOnTopOfPage('Greasemonkey active on a <b>letters.salon.com</b> page',
   'lettersLoad', 'font-size: 7pt; background-color: rgb(230, 220, 255)');
  if (hash == '#lastPage'){
   var lastNo = findLastPageNo(document);
   if (lastNo != 1) return document.location.pathname = pageHref(lastNo); // will cause a reload
   }
  mungSideBar();
  if (!XP_firstNode(document, LetterPageToolsP)) return;  // not a real 'letters' page.
  mungLettersPage();
  }
}
// this lets me see if Greasemonkey has seen a page.
function slapNoticeOnTopOfPage(text, class, style){
 var ddiv = document.createElement('div');
 ddiv.innerHTML = '<div id="' + class + '">' + text + '</div>';
 GM_addStyle('#' + class + ' {' + style + '}');
 document.body.insertBefore(ddiv.firstChild, document.body.firstChild);
}

// this sort of delay sometimes seems to prevent some mysterious problems 
// though I shouldn't have to do this.
window.addEventListener('load', Main, false);

function newDiv(){
 return document.createElement('div');
}
function XP_orderedSnapshot(node, xpath, namespaceResolver){
 return document.evaluate(xpath,
  node, namespaceResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
}
function XP_firstNode(node, xpath, namespaceResolver){
 return document.evaluate(xpath,
  node, namespaceResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}
//========================
//
// www.salon.com STUFF
//
//=======================

function mungSalonPage(){
 var sidebarLIsP 
  ="//div[@id='content_sidebar_column']/div[@class='greenwald_sidebar_group' and @id='current_greenwald']/ul/li";
 var sSnap = XP_orderedSnapshot(document, sidebarLIsP);
 for (var i=0; i<sSnap.snapshotLength; i++){
  var li = sSnap.snapshotItem(i);
  li.appendChild(showLettersDiv(li));
 }
 GM_addStyle('.showLetters {background-color: rgb(255, 220, 255)}'); // pink
}
function showLettersDiv(li){
 var href = XP_firstNode(li, './a/@href').nodeValue;
 href = href.replace('index.html','view/index1.html');
 href = href.replace(/http\:\/\/www.salon.com/, '');
 href = 'http://letters.salon.com' + href;
// if(!confirm('href = ' + href)) throw "bail out";
 var dumd = newDiv();
 dumd.innerHTML = '<div>'  
  + '<a class="showLetters" href="' + href + '?show=all&order=asc">'
  + 'All</a> or <a class="showLetters" href="' + href + '?show=ec&order=asc'
  + '">Editors\' choices</a> or '
  + '<a class="showLetters" href="' + href + '?show=all&order=asc#lastPage">Last</a> comments</div>';
 return dumd.firstChild;
}
//------------------------------
//
// letters.salon.com STUFF
//
//------------------------------
// the sitewise url for the first letter page can be any of 
//  http://letters.salon.com/opinion/greenwald/2007/02/14/neoconservatism/view/?show=all
//  http://letters.salon.com/opinion/paglia/2007/02/14/return/view/index.html
//  http://letters.salon.com/opinion/paglia/2007/02/14/return/view/index1.html

function topHref(){
 var path = document.location.pathname;
 path = path.replace(/\/view\/$/, '/view/index1.html');
 path = path.replace('/index.html', '/index1.html');
 return path;
}
LetterPageToolsP = "//div[@id='main_content']/div[@class='letter_page_tools']";

function giveLPTsIds (){ // so we can distinguish the top from the bottom.
 var snap =  XP_orderedSnapshot(document, LetterPageToolsP);

 var ids = ['topLPT', 'bottomLPT'];
 for (i=0; i<=1; i++){
  lptD = snap.snapshotItem(i);
  lptD.setAttribute('id', ids[i]);
  }
}
function mungSideBar(){
 var lisSnap 
  = XP_orderedSnapshot(document, "//div[@class='sidebar_group' and @id='current']/ul/li");
 for (var i=0; i<lisSnap.snapshotLength; i++){
  var li = lisSnap.snapshotItem(i);
  var d =  showLettersDiv(li);
  li.replaceChild(d, li.lastChild);
 }
 GM_addStyle('.showLetters {background-color: rgb(255, 220, 255)}'); // pink
}
function mungLettersPage(){
 var firstLetter = XP_firstNode(document, '//div[@class="letter_entry" and .//p]');
 if (!firstLetter) return;                     // incase not a real 'letters' page
 giveLPTsIds(); // give the <div class='letter_page_tools'>s ids.
 if (document.location.hash == '#singlePage') return;
 GM_addStyle('.subPageHead {background-color: rgb(220, 200, 255);}'); // purple
 GM_addStyle('.spReload    {background-color: rgb(255, 220, 255);}'); // pink
 GM_addStyle('.spReload    {margin-right: 2em; margin-left: 2em;}');
 GM_addStyle('#checkNew    {margin-bottom: 1em}');

 updateLPT(findLastPageNo(document)); // this is here just to make sure this function works.

 var dumd = newDiv();
 var link = topHref();
 var pageNo = urlPageNo(link);
 dumd.innerHTML
  = '<div class="subPage" id="topPage">'
  +  '<div class="subPageHead"> Show '
  +   '<a href="' + link + '#singlePage"><b>Page ' + pageNo + ' (only)</b></a>'
  +    ' or <a href="' + link + '"><b>Page ' + pageNo + '+</b></a>'
  +    ' (which is this page)' 
  +    '<span class="spReload">[[click to reload unconverted subpages]]'
  +   '</span></div>';
 var spDiv = dumd.firstChild;
 addClickFunction(spDiv, subPageClick);
 firstLetter.parentNode.insertBefore(spDiv, firstLetter);
 addClickFunction(spDiv, subPageClick);
 addFollowingPages();
}
function urlPageNo(href){
 var no = 1;
 var mat = href.match(/\/.*\/index(\d+)\.html/);
 if(mat) no = Number(mat[1]);
 return no;
}
function findLastPageNo(doc){
 var pageNo = urlPageNo(topHref());
 var lptD = XP_firstNode(doc, LetterPageToolsP);
 if (!lptD) return 1;                            // might not be a 'real' letters page
 var snap = XP_orderedSnapshot(lptD, './/a/@href');
 for (var i=0; i<snap.snapshotLength; i++){
  var no = urlPageNo(snap.snapshotItem(i).nodeValue);
  if (no > pageNo) pageNo = no;
  }
 return pageNo;
}
// this will update the <div class='letter_page_tools'>s for when a subpage says
// there are new subpages.
function updateLPT(lastPageNo){
 var lptSnap = XP_orderedSnapshot(document, LetterPageToolsP);
 for (var j=0; j<lptSnap.snapshotLength; j++){
  var lptDiv = lptSnap.snapshotItem(j);
  var newLPT 
   = rebuildLPT(topHref(), urlPageNo(topHref()), lastPageNo);
  lptDiv.replaceChild(newLPT, lptDiv.firstChild);
  }
}

function rebuildLPT(href, thisPageNo, lastPageNo){
 function buildPageA (pageNo,hrefPre, text){
   var dum = document.createElement('div');
   dum.innerHTML = '<a href="' + hrefPre + pageNo + '.html"> ' + text + ' </a>';
   return dum.firstChild;
   }
 var hrefPrefix = topHref().replace(/\d*.html/, '');
 var ldiv = document.createElement('div');
 ldiv.innerHTML = 'Page ';
 if (thisPageNo != 1)
  ldiv.appendChild(buildPageA(thisPageNo-1, hrefPrefix, " << Prev \nPage ")); 
 for (var i=1; i<= lastPageNo; i++){
  if (i == thisPageNo){
   var dum = document.createElement('div');
   dum.innerHTML = '<span class="thispagenum"> ' + i + ' </span>';
   ldiv.appendChild(dum.firstChild);
   }
  else ldiv.appendChild(buildPageA(i, hrefPrefix, i));
 }
 if (thisPageNo != lastPageNo)
  ldiv.appendChild(buildPageA(thisPageNo+1, hrefPrefix, " Next Page >> "));
 return ldiv;
}
// are letters in ascending order?
function lettersAscend(){
 var button = XP_firstNode(document, "//input[@id='asc_letters_radio']/@checked");
 if (button) return (button.nodeValue == 'checked');
 if (topHref().match(/\/author\//)) return false;  // if on a author page
 return true;
}
LastSubPageDiv = null; // nothing untill any subpages are added.

function addFollowingPages(){
 var bottomToolsDiv = XP_firstNode(document, '//div[@id="bottomLPT"]');
 if (!bottomToolsDiv) return;  // we may not be on a 'real' letters page.

 // insert a SubPages div
 var ddiv = document.createElement('div');
 ddiv.innerHTML = '<div id="SubPages">';
 SubPagesDiv = ddiv.firstChild;  // GLOBAL
 bottomToolsDiv.parentNode.insertBefore(SubPagesDiv, bottomToolsDiv);
 addNewCommentCheck();

 // insert Iframes that will eventually be converted to plain comments.
 // Glenn's letters = http://letters.salon.com/ed5ca3007d7ae628dd365be552a7b6eb/author/index.html
 var topPageNo = urlPageNo(topHref());
 var lastPageNo = findLastPageNo(document);
 var askConfirm = topHref().match(/\/author\//); // confirm showing all of an author's letters
 for (var i=topPageNo+1; i<=lastPageNo; i++){
  if (askConfirm){
   askConfirm = false;
   if(!confirm('show following pages of this author\'s letters?')) return convertSubPages();
   }

  addSubPage(pageHref(i), i);
  }
 convertSubPages();
}

function addNewCommentCheck(){
 var dumDiv = document.createElement('div');
 var href = topHref();
 var pageNo = urlPageNo(href);
 dumDiv.innerHTML 
  = '<div id="checkNew" class="subPageHead">'
  + '<span class="spReload">[[click here to check for new comments]]</span>'
  + '(this link will show '
  + '<a href="' + href +'#singlePage"><b>Page ' + pageNo + ' (only)</b></a>)</div>';
 var checkDiv = dumDiv.firstChild;
 SubPagesDiv.parentNode.insertBefore(checkDiv, SubPagesDiv.nextSibling);
 addClickFunction(checkDiv, newCommentsClick, !lettersAscend());
}
function addClickFunction(spDiv, funct, hide){
 var reloadS = XP_firstNode(spDiv, './/span[@class="spReload"]');
 reloadS.setAttribute('title', 'click here');
 reloadS.addEventListener('click', funct, false);
 if (hide) reloadS.style.display = 'none';
}
function addSubPage(link, pageNo){
 var dumDiv = document.createElement('div');
 dumDiv.innerHTML 
  = '<div class="subPage">'
  +  '<div class="subPageHead"> Show '
  +   '<a href="' + link + '#singlePage"><b>Page ' + pageNo + ' (only)</b></a>'
  +    ' or <a href="' + link + '"><b>Page ' + pageNo + '+</b></a>'
  +    '<span class="spReload">[[click to reload unconverted subpages]]</span></div>';
  var spDiv = dumDiv.firstChild;
  spDiv.setAttribute('href', link);
  SubPagesDiv.appendChild(spDiv);
  initSubPageContents(spDiv, link);
  addClickFunction(spDiv, subPageClick);
  LastSubPageDiv = spDiv;
  SuckDelay = 1;
}
function newCommentsClick(){
 if (LastSubPageDiv){
  ClickYScroll = window.scrollY; // remember pos before reloaded 
  initSubPageContents(LastSubPageDiv);
  setReloadVisable('');
  SuckDelay = 1;
  convertSubPages();
 }
 else document.location.reload();  // could put scrollY into the hash.
}
ClickYScroll = -1;  // place to scroll back to init undefined

function subPageClick(){
 myGM_log('\subPageClick - a.k.a trying to force sub-pages to convert');
 ClickYScroll = window.scrollY; // remember pos before reloaded
 try {
  convertSubPages(); // convert now if waiting for timeout 
  }
 catch (e){
//  alert(e);
  }
// Above does not work if using window.iframeLoaded flag. if error message was 
//   [Exception... "'Permission denied to get property Window.iframeLoaded' when calling method: [nsIDOMEventListener::handleEvent]" nsresult: "0x8057001e (NS_ERROR_XPC_JS_THREW_STRING)" location: "<unknown>" data: no]
// then that will just recause the error.
// Reloading the iframes seems to fix the problem.
// using iframe.contentDocument.getElementById can give error message:
//  uncaught exception: Permission denied to get property HTMLDocument.getElementById
 var snap = XP_orderedSnapshot(SubPagesDiv, './div[@class="subPage" and .//iframe]');
 var nIframes = snap.snapshotLength;
 for (var i=0; i<snap.snapshotLength; i++){
  initSubPageContents(snap.snapshotItem(i))
  }
 SuckDelay = 1;
 convertSubPages();
}
function initSubPageContents(subPage){
 var href = subPage.getAttribute('href');
 var oldC = XP_firstNode(subPage, './div[@class = "spContent"]');
 if (oldC) subPage.removeChild(oldC);
 var dum = document.createElement('div');
 dum.innerHTML 
  = '<div class="spContent"><div>**WAITING**<iframe src="http://letters.salon.com' + href + '#asIframe"></div></div>';
// myGM_log('\n want an IFRAME: href = ' + href);
 var cDiv = dum.firstChild;
 subPage.appendChild(cDiv);
 logIframeLoad(XP_firstNode(cDiv, './/iframe'));
 return cDiv;
}
function logIframeLoad(iframe){
 iframe.addEventListener('load', function(){iframeLoaded(iframe);}, false);
}
function iframeLoaded(iframe){
 myGM_log('\niframe loaded: ' + iframe.src);
 convertSubPages();
}
SuckTimeout = null;  // initially there is no active timeout
SuckDelay = 1;       // 1 second initial delay for re-sucking

function pageHref(n){
 return topHref().replace(/\/index\d+.html/, '/index' + n + '.html');
}
function convertSubPages(){
 try {convertSubPages_1();}
 catch (e) {
//  alert('convertSubPages caught Exception: ' + e);
  myGM_log('\n convertSubPages caught: ' + e);
 }
}
function convertSubPages_1(){
 if (SuckTimeout) clearTimeout(SuckTimeout);
 var snap = XP_orderedSnapshot(SubPagesDiv, './/div[@class="spContent" and .//iframe]');
 var nIframes = snap.snapshotLength;
 var nConverted = 0;
 var newPages = false; // no new comment pages found.
// if (!confirm('sucking ' + nIframes + ' iframes')) return;
 for (var i=0; i<snap.snapshotLength; i++){
  var spContent = snap.snapshotItem(i);
  var iframe = XP_firstNode(spContent, './div/iframe');
//  if (iframe.contentWindow.wrappedJSObject.iframeLoaded){
  if (iframe.contentDocument.getElementById('iFrameNote')){
   cloneLetters(iframe.contentDocument, spContent);
   var currentLastPage = findLastPageNo(document);
   var spLastPage = findLastPageNo(iframe.contentDocument);
   if (spLastPage > currentLastPage){
//    if(!confirm('new pages: current = ' + currentLastPage + ', new: ' + spLastPage)) throw "get out";
    for (var j=currentLastPage+1; j<=spLastPage; j++){
     addSubPage(pageHref(j), j, false);
     }
    updateLPT(spLastPage);
    newPages = true;
    SuckTimeout = 1;
    }
   spContent.removeChild(iframe.parentNode);
   nConverted += 1;
   }
  }
 // if done then scroll to previous position and quit.
 if (!newPages && (nIframes == nConverted)){
  setReloadVisable('none');
  if (ClickYScroll > 0){
   window.scrollTo(0, ClickYScroll);
   ClickYScroll = -1;
   }
  return;
  }
 // else continue to wait for IFRAMEs and suck them in.
 SuckDelay = SuckDelay*1.5;
 myGM_log('\n '+ SuckDelay +' seconds until next check on ' + topHref());
 SuckTimeout = setTimeout(convertSubPages, SuckDelay*1000); // try again in n seconds
}
function setReloadVisable(vis){
 var msnap = XP_orderedSnapshot(SubPagesDiv, '//span[@class="spReload"]');
 msL = msnap.snapshotLength;
 for (var i=0; i<msL-1; i++){
  msnap.snapshotItem(i).style.display = vis;
 }
} 
function cloneLetters(doc, lettersDiv){
 var cloneP 
  = "//div[@id='main_content']/div[(@class='letter_entry' and .//p) or @class='ad_content']";
 var snap = XP_orderedSnapshot(doc, cloneP);
 for (var i=0; i<snap.snapshotLength; i++){
  // I don't know if I really have to clone nodes out of an iframe or just take them
  lettersDiv.appendChild(snap.snapshotItem(i).cloneNode(true));  
  }
}
// FINIS